美文网首页具体自定义控件Android 自定义view
Android 自定义ViewGroup 流式布局

Android 自定义ViewGroup 流式布局

作者: darryrzhong | 来源:发表于2019-12-11 17:26 被阅读0次

    自定义View的基本方法

    自定义View的最基本的三个方法分别是: onMeasure()、onLayout()、onDraw();
    View在Activity中显示出来,要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout和draw。

    • 测量:onMeasure()决定View的大小;
    • 布局:onLayout()决定View在ViewGroup中的位置;
    • 绘制:onDraw()决定绘制这个View。

    自定义控件分类

    • 自定义View: 只需要重写onMeasure()和onDraw()
    • 自定义ViewGroup: 则只需要重写onMeasure()和onLayout()

    自定义View基础

    View的分类

    视图View主要分为两类

    类别 解释 特点
    单一视图 即一个View,如TextView 不包含子View
    视图组 即多个View组成的ViewGroup,如LinearLayout 包含子View

    View类简介

    • View类是Android中各种组件的基类,如View是ViewGroup基类
    • View表现为显示在屏幕上的各种视图

    Android中的UI组件都由View、ViewGroup组成。

    • View的构造函数:共有4个
    // 如果View是在Java代码里面new的,则调用第一个构造函数
     public CarsonView(Context context) {
            super(context);
        }
    
    // 如果View是在.xml里声明的,则调用第二个构造函数
    // 自定义属性是从AttributeSet参数传进来的
        public  CarsonView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
    // 不会自动调用
    // 一般是在第二个构造函数里主动调用
    // 如View有style属性时
        public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        //API21之后才使用
        // 不会自动调用
        // 一般是在第二个构造函数里主动调用
        // 如View有style属性时
        public  CarsonView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }
    
    

    AttributeSet与自定义属性

    系统自带的View可以在xml中配置属性,对于写的好的自定义View同样可以在xml中配置属性,为了使自定义的View的属性可以在xml中配置,需要以下4个步骤:

    1. 通过<declare-styleable>为自定义View添加属性
    2. 在xml中为相应的属性声明属性值
    3. 在运行时(一般为构造函数)获取属性值
    4. 将获取到的属性值应用到View

    代码如下

    package com.zthx.deviceui.view;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;
    import android.widget.LinearLayout;
    
    import com.zthx.deviceui.R;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 类名称: FlowLayout.java<p>
     * 类描述: 自定义流式ViewGroup<p>
     * 
     *
     * @author darryrzhong
     * @since 2019/12/11 9:37
     */
    public class FlowLayout extends ViewGroup {
    
        /**
         * 每一行的子view
         * */
        private List<View> lineView;
    
        /**
         * 整个布局的所有行
         * */
        private List<List<View>> lines;
    
        /**
         * 每一行的高度
         * */
        private List<Integer> heights;
    
        public FlowLayout(Context context) {
            super(context);
        }
    
        public FlowLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        private void init(){
            lineView = new ArrayList<>();
            lines = new ArrayList<>();
            heights = new ArrayList<>();
    
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            //测量自身
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //获取限制信息的 mode和 size
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            //记录当前行的宽度个高度
    
            //宽度是当前行子view的宽度之和
            int lineWidth = 0;
            //高度是当前行所有子view中高度的最大值
            int lineHeight = 0;
    
            //整个流式布局的宽度和高度
    
            //所有行中宽度的最大值
            int flowLayoutWidth = 0;
            //所有行的高度叠加
            int flowLayoutHeight = 0;
    
            //初始化参数列表
            init();
    
            //遍历所有的子view,对子view进行measure,分配到具体的行
            int countView = getChildCount();
            for (int i = 0;i<countView;i++){
                View child = this.getChildAt(i);
                //测量子View 获取到当前子View的测量的宽度 /高度
                measureChild(child,widthMeasureSpec,heightMeasureSpec);
                int childWidth = child.getMeasuredWidth();
                int childHeight = child.getMeasuredHeight();
    
                LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //看下当前的行的剩余的宽度是否可以容纳下一个子View
                //如果放不下,换行 保存当前行的所有子View,累加行高,
                //将当前行的 高度 和宽度 置零
                if (lineWidth+childWidth > widthSize){
                    //换行
                  lines.add(lineView);
                  lineView = new ArrayList<>();
                  flowLayoutWidth = Math.max(flowLayoutWidth,lineWidth);
                  flowLayoutHeight +=lineHeight;
                  heights.add(lineHeight);
                  lineHeight = 0;
                  lineWidth = 0;
                }
                lineView.add(child);
                lineWidth +=childWidth;
                lineHeight = Math.max(lineHeight,childHeight);
    
            }
    
            //FlowLayout最终宽高
            setMeasuredDimension(widthMode == MeasureSpec.EXACTLY? widthSize:flowLayoutWidth,
                    heightMode == MeasureSpec.EXACTLY?heightSize:flowLayoutHeight);
    
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
             int lineCount = lines.size();
    
             int currX = 0;
             int currY = 0;
    
             //所有的子view,一行一行的布局
             for (int i = 0;i<lineCount;i++){
                 //取出一行
                 List<View> lineViews = lines.get(i);
                 //取出这一行的高度值
                 int lineHeight = heights.get(i);
                 int lineSize = lineViews.size();
                 //遍历当前行的子View
                 for (int j=0;j<lineSize;j++){
                     View child = lineViews.get(j);
                     int left = currX;
                     int top = currY;
                     int right = left+child.getMeasuredWidth();
                     int bottom = top+child.getMeasuredHeight();
                     child.layout(left,top,right,bottom);
                     //确定下一个view的left
                     currX +=child.getMeasuredWidth();
                 }
                 currY += lineHeight;
                 currX = 0;
             }
        }
    
    
        @Override
        protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
            return new LayoutParams(p);
        }
    
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new LayoutParams(getContext(),attrs);
        }
    
        @Override
        protected LayoutParams generateDefaultLayoutParams() {
            return new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
        }
    
        @Override
        protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
            return super.checkLayoutParams(p) && p instanceof LayoutParams;
        }
    
        public static class LayoutParams extends MarginLayoutParams{
    
            public int gravity = -1;
    
            public LayoutParams(Context c, AttributeSet attrs) {
                super(c, attrs);
    
                TypedArray array = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
                try {
                    gravity = array.getInt(R.styleable.FlowLayout_android_gravity,-1);
                }finally {
                    array.recycle();
                }
    
            }
    
            public LayoutParams(int width, int height) {
                super(width, height);
            }
    
            public LayoutParams(ViewGroup.LayoutParams source) {
                super(source);
            }
    
            @Override
            public String toString() {
                return "LayoutParams{" +
                        "gravity=" + gravity +
                        ", bottomMargin=" + bottomMargin +
                        ", leftMargin=" + leftMargin +
                        ", rightMargin=" + rightMargin +
                        ", topMargin=" + topMargin +
                        ", height=" + height +
                        ", layoutAnimationParameters=" + layoutAnimationParameters +
                        ", width=" + width +
                        '}';
            }
        }
    
    }
    
    

    布局如下:

    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        xmlns:tools="http://schemas.android.com/tools">
    
        <com.zthx.deviceui.view.FlowLayout xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:app="http://schemas.android.com/apk/res-auto"
            xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MainActivity">
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="55dp"
                android:text="view1" />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="第二个view" />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="三个三个view"
                android:layout_gravity="bottom"/>
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="match_parent"
                android:text="生活不止眼前的苟且,还有诗和远方" />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="90dp"
                android:text="哈哈哈" />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="不知道是谁" />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="Hello hi ..." />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="啦啦啦啦啦" />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="85dp"
                android:text="滴答滴答"
                android:layout_gravity="bottom"/>
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="生活不止眼前的苟且,还有诗和远方" />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="哈哈哈" />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="啦啦啦啦啦" />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="45dp"
                android:text="Hello hi ..." />
    
            <Button
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="他是谁,我是谁" />
    
        </com.zthx.deviceui.view.FlowLayout>
    </ScrollView>
    
    
    效果如下

    欢迎关注作者darryrzhong,更多干货等你来拿哟.

    请赏个小红心!因为你的鼓励是我写作的最大动力!

    更多精彩文章请关注

    相关文章

      网友评论

        本文标题:Android 自定义ViewGroup 流式布局

        本文链接:https://www.haomeiwen.com/subject/wgcagctx.html