Android自定义View实现流式布局

实现效果如图

Android自定义View实现流式布局_第1张图片

大概流程图

Android自定义View实现流式布局_第2张图片

实现分析

1.在FlowLayout的构造里面获取自定义属性,让其支持横向间距和纵向间距

  public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
        horizontal_space = typedArray.getDimension(R.styleable.FlowLayout_width_space, 0);
        vertical_space = typedArray.getDimension(R.styleable.FlowLayout_height_space, 0);
        typedArray.recycle();
    }

2.在OnMeasure()里面进行测量

  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mLines.clear();
        mCurrentLine = null;

        //获取总的宽度
        int width = MeasureSpec.getSize(widthMeasureSpec);
        //计算最大的宽度
        mMaxWidth = width - getPaddingRight() - getPaddingLeft();

        //测量孩子
        int childCount = this.getChildCount();

        for (int i = 0; i < childCount; i++) {

            View childView = getChildAt(i);

            measureChild(childView, widthMeasureSpec, heightMeasureSpec);

            //测量完成之后将孩子添加 到管理行进行管理
            if (mCurrentLine == null) {

                //第一次添加孩子的时候
                mCurrentLine = new Line(mMaxWidth,horizontal_space);

                //添加孩子
                mCurrentLine.addView(childView);
                //.添加行
                mLines.add(mCurrentLine);
            }else{

                //先判断行里是否可以继续添加孩子
                if(mCurrentLine.canAddView(childView)){

                    mCurrentLine.addView(childView);

                }else{
                    //换到下一行

                    mCurrentLine=new Line(mMaxWidth ,horizontal_space);
                    mCurrentLine.addView(childView);
                    mLines.add(mCurrentLine);
                }
            }
        }


        //完成了测量孩子,那么就来测量自己
        //计算自己只需要测量自己的高度,宽度肯定回被填充
        int height = getPaddingTop() + getPaddingBottom();
        for(int i=0;i//所有的行高
            height+=mLines.get(i).height;
        }

        //所有竖直方向的间距
        height+=(mLines.size()-1)*vertical_space;

        //测量

        setMeasuredDimension(width,height);
    }

3.在OnLayout()里面遍历获取所有的行,让行去指定孩子的位置,指定行的高度。

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {


        //这里只负责具体 高度位置,具体 的 宽度和子孩子的高度让具体 的行去管理
        l=getPaddingLeft();
        t=getPaddingTop();

        for(int j=0 ;j//获取 行
            Line line = mLines.get(j);

            line.onLayout(t,l);

            //更新高度
            t+=line.height;
            if(j!=mLines.size()-1){

                //不是最后一条 添加间距

                t+=vertical_space;

            }


        }

    }

4.在内部类里面对孩子进行管理,包括添加子view,子view的位置摆放

/**
     * 内部类,行管理器,管理每一行的孩子
     */
    public static class Line {

        private float horizontalSpace;
        private int   maxWidth;
        //定义一个行的集合来装子View
        List views = new ArrayList<>();
        private int usedWidth;
        private int height;

        public Line(int maxWidth, float horizontalSpace) {

            this.maxWidth = maxWidth;
            this.horizontalSpace = horizontalSpace;
        }

        public void addView(View view) {

            int childWidth = view.getMeasuredWidth();
            int childHeight = view.getMeasuredHeight();

            //更新行里面 的宽度与高度
            if (views.size() == 0) {
                //集合里面没有孩子的时候
                if (childWidth > maxWidth) {

                    usedWidth = maxWidth;
                    height = childHeight;

                } else {

                    usedWidth = childWidth;
                    height = childHeight;

                }

            } else {

                usedWidth += childWidth + horizontalSpace;
                height = childHeight > height ? childHeight : height;

            }

            //添加孩子到集合
            views.add(view);
        }


        public boolean canAddView(View view) {
            //集合里面添加孩子
            if (views.size() == 0) {
                return true;
            }

            //最后一个孩子的宽度大于剩余的宽度返回false
            if (view.getMeasuredWidth() > (maxWidth - usedWidth - horizontalSpace)) {

                return false;
            }


            return true;
        }


        /**
         * 指定孩子的显示位子
         */

        public void onLayout(int t, int l) {

            //平分剩余的空间
            int avg = (maxWidth - usedWidth) / views.size();

            //循环指定孩子的位子

            for (View view : views) {

                //获取宽高
                int measuredWidth = view.getMeasuredWidth();
                int measuredHeight = view.getMeasuredHeight();

                //重新测量
                view.measure(MeasureSpec.makeMeasureSpec(measuredWidth + avg, MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY));


                //重新获取宽高
                measuredWidth = view.getMeasuredWidth();

                //指定左上右下
                int top = t;
                int left = l;
                int right = measuredWidth + left;
                int bottom = measuredHeight + top;

                view.layout(left, top, right, bottom);

                //跟新数据
                l += measuredWidth + horizontalSpace;

            }


        }

    }

下面是完整代码

package com.zyh.jsondemo;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;
import java.util.List;

/**
 * @创建者 Administrator
 * @创建时间 2016/10/16 10:32
 * @描述 ${TODO}
 */
public class FlowLayout extends ViewGroup {


    /**
     * 储存行的集合
     */
    private List mLines = new ArrayList<>();

    // 当前行的指针
    private Line mCurrentLine;


    //水平方向的间距
    private float horizontal_space;
    //垂直方向的间距
    private float vertical_space;
    private int   mMaxWidth;

    public FlowLayout(Context context) {
        this(context, null);
    }

    public FlowLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FlowLayout);
        horizontal_space = typedArray.getDimension(R.styleable.FlowLayout_width_space, 0);
        vertical_space = typedArray.getDimension(R.styleable.FlowLayout_height_space, 0);
        typedArray.recycle();
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        mLines.clear();
        mCurrentLine = null;

        //获取总的宽度
        int width = MeasureSpec.getSize(widthMeasureSpec);
        //计算最大的宽度
        mMaxWidth = width - getPaddingRight() - getPaddingLeft();

        //测量孩子
        int childCount = this.getChildCount();

        for (int i = 0; i < childCount; i++) {

            View childView = getChildAt(i);

            measureChild(childView, widthMeasureSpec, heightMeasureSpec);

            //测量完成之后将孩子添加 到管理行进行管理

            if (mCurrentLine == null) {

                //第一次添加孩子的时候
                mCurrentLine = new Line(mMaxWidth, horizontal_space);

                //添加孩子
                mCurrentLine.addView(childView);
                //.添加行
                mLines.add(mCurrentLine);
            } else {

                //先判断行里是否可以继续添加孩子
                if (mCurrentLine.canAddView(childView)) {

                    mCurrentLine.addView(childView);

                } else {
                    //换到下一行

                    mCurrentLine = new Line(mMaxWidth, horizontal_space);
                    mCurrentLine.addView(childView);
                    mLines.add(mCurrentLine);
                }
            }
        }


        //完成了测量孩子,那么就来测量自己
        //计算自己只需要测量自己的高度,宽度肯定回被填充
        int height = getPaddingTop() + getPaddingBottom();
        for (int i = 0; i < mLines.size(); i++) {
            //所有的行高
            height += mLines.get(i).height;
        }

        //所有竖直方向的间距
        height += (mLines.size() - 1) * vertical_space;

        //测量

        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {


        //这里只负责具体 高度位置,具体 的 宽度和子孩子的高度让具体 的行去管理
        l = getPaddingLeft();
        t = getPaddingTop();

        for (int j = 0; j < mLines.size(); j++) {

            //获取 行
            Line line = mLines.get(j);

            line.onLayout(t, l);

            //更新高度
            t += line.height;
            if (j != mLines.size() - 1) {

                //不是最后一条 添加间距

                t += vertical_space;

            }


        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    }


    /**
     * 内部类,行管理器,管理每一行的孩子
     */
    public static class Line {

        private float horizontalSpace;
        private int   maxWidth;
        //定义一个行的集合来装子View
        List views = new ArrayList<>();
        private int usedWidth;
        private int height;

        public Line(int maxWidth, float horizontalSpace) {

            this.maxWidth = maxWidth;
            this.horizontalSpace = horizontalSpace;
        }

        public void addView(View view) {

            int childWidth = view.getMeasuredWidth();
            int childHeight = view.getMeasuredHeight();

            //更新行里面 的宽度与高度
            if (views.size() == 0) {
                //集合里面没有孩子的时候
                if (childWidth > maxWidth) {

                    usedWidth = maxWidth;
                    height = childHeight;

                } else {

                    usedWidth = childWidth;
                    height = childHeight;

                }

            } else {

                usedWidth += childWidth + horizontalSpace;
                height = childHeight > height ? childHeight : height;

            }

            //添加孩子到集合
            views.add(view);
        }


        public boolean canAddView(View view) {
            //集合里面添加孩子
            if (views.size() == 0) {
                return true;
            }

            //最后一个孩子的宽度大于剩余的宽度返回false
            if (view.getMeasuredWidth() > (maxWidth - usedWidth - horizontalSpace)) {

                return false;
            }


            return true;
        }


        /**
         * 指定孩子的显示位子
         */

        public void onLayout(int t, int l) {

            //平分剩余的空间
            int avg = (maxWidth - usedWidth) / views.size();

            //循环指定孩子的位子

            for (View view : views) {

                //获取宽高
                int measuredWidth = view.getMeasuredWidth();
                int measuredHeight = view.getMeasuredHeight();

                //重新测量
                view.measure(MeasureSpec.makeMeasureSpec(measuredWidth + avg, MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY));


                //重新获取宽高
                measuredWidth = view.getMeasuredWidth();

                //指定左上右下
                int top = t;
                int left = l;
                int right = measuredWidth + left;
                int bottom = measuredHeight + top;

                view.layout(left, top, right, bottom);

                //跟新数据
                l += measuredWidth + horizontalSpace;

            }


        }

    }


}

2.自定义属性

<resources>
    <declare-styleable name="FlowLayout">
        <attr name="width_space" format="dimension">attr>
        <attr name="height_space" format="dimension">attr>

    declare-styleable>

resources>

3.xml文件

   <com.zyh.jsondemo.FlowLayout
        android:id="@+id/fl"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:width_space="5dp"
        app:height_space="5dp"
        />

4.MainActivity代码

public class MainActivity2 extends Activity{

    private FlowLayout mFl;

    /**
     * 显示的文字
     */
    private String[] mDatas = new String[]{"QQ",
            "视频",
            "放开那三国",
            "电子书",
            "酒店",
            "单机",
            "小说",
            "斗地主",
            "优酷",
            "网游",
            "WIFI万能钥匙",
            "播放器",
            "捕鱼达人2",
            "机票",
            "游戏",
            "熊出没之熊大快跑",
            "美图秀秀",
            "浏览器",
            "单机游戏",
            "我的世界",
            "电影电视",
            "QQ空间",
            "旅游",
            "免费游戏",
            "2048",
            "刀塔传奇",
            "壁纸",
            "节奏大师",
            "锁屏",
            "装机必备",
            "天天动听",
            "备份",
            "网盘",
            "海淘网",
            "大众点评",
            "爱奇艺视频",
            "腾讯手机管家",
            "百度地图",
            "猎豹清理大师",
            "谷歌地图",
            "hao123上网导航",
            "京东",
            "有你",
            "万年历-农历黄历",
            "支付宝钱包"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);



        setContentView(R.layout.activity_main2);


        initView();
        initData();


    }

    private void initView() {

        mFl = (FlowLayout) findViewById(R.id.fl);

    }

    private void initData() {
        Random random = new Random();

        for(String name:mDatas){


            final TextView view = new TextView(this);
            view.setText(name);
            view.setTextColor(Color.WHITE);
            view.setPadding(8, 8, 8, 8);
            view.setGravity(Gravity.CENTER);
            view.setTextSize(20);
            // 设置点击事件
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(MainActivity2.this, view.getText().toString(), Toast.LENGTH_SHORT).show();
                }
            });

            // 设置彩色背景
            GradientDrawable normalDrawable = new GradientDrawable();
            normalDrawable.setShape(GradientDrawable.RECTANGLE);
            int a = 255;
            int r = 50 + random.nextInt(150);
            int g = 50 + random.nextInt(150);
            int b = 50 + random.nextInt(150);
            normalDrawable.setColor(Color.argb(a, r, g, b));

            // 设置按下的灰色背景
            GradientDrawable pressedDrawable = new GradientDrawable();
            pressedDrawable.setShape(GradientDrawable.RECTANGLE);
            pressedDrawable.setColor(Color.GRAY);

            // 背景选择器
            StateListDrawable stateDrawable = new StateListDrawable();
            stateDrawable.addState(new int[]{android.R.attr.state_pressed}, pressedDrawable);
            stateDrawable.addState(new int[]{}, normalDrawable);

            // 设置背景选择器到TextView上
            view.setBackground(stateDrawable);

            mFl.addView(view);
        }

    }
}

你可能感兴趣的:(自定义控件)