Android 自定义UI 实战 02 流式布局

举报
半身风雪 发表于 2022/06/24 12:02:37 2022/06/24
【摘要】 Android 自定义UI 实战 02 流式布局-- 自定义ViewGroup第二章 自定义ViewGroup 流式布局@TOC 前言使用纯代码 加 注释的方式,可以更快的理解源码如果你喜欢,请点个赞,后期会不断的深入讲解 1、自定义控件,并在XML 使用自定一个Layout 继承 ViewGrouppublic class FlowLayout extends ViewGroup { ...

Android 自定义UI 实战 02 流式布局-- 自定义ViewGroup

第二章 自定义ViewGroup 流式布局


@TOC


前言

使用纯代码 加 注释的方式,可以更快的理解源码
如果你喜欢,请点个赞,后期会不断的深入讲解


1、自定义控件,并在XML 使用

自定一个Layout 继承 ViewGroup

public class FlowLayout extends ViewGroup {

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

    public FlowLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);


    }


    @Override
    protected void onLayout(boolean b, int i, int i1, int i2, int i3) {

    }
}

在Layout 中使用组件,我这里简单的写了一些数据测试

<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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">

    <com.traveleasy.leaningui02.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:id="@+id/mFlowLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="10dp"
        tools:ignore="MissingClass">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="55dp"
            android:text="Hello hi ..." />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="你是谁呀" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:text="人在他在,塔亡人亡" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="100dp"
            android:text="一行一个元素生活不止眼前的苟且,还有诗和远方大法师的打分sad发送到发送到发的发送到发送到第三方阿萨德佛挡杀佛但是" />

        <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="match_parent"
            android:text="11一行一个元素生活不止眼前的苟且,还有诗和远方大法师的打分sad发送到发送到发的发送到发送到第三方阿萨德佛挡杀佛但是" />


        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="你是谁呀" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="85dp"
            android:layout_gravity="bottom"
            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="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="你是谁呀" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:text="人在他在,塔亡人亡" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="75dp"
            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="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="60dp"
            android:layout_gravity="bottom"
            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="85dp"
            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="100dp"
            android:layout_gravity="bottom"
            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="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:layout_gravity="bottom"
            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="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="wrap_content"
            android:layout_gravity="bottom"
            android:text="人在他在,塔亡人亡" />

        <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="发电房" />

        <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="wrap_content"
            android:layout_gravity="bottom"
            android:text="人在他在,塔亡人亡" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="65dp"
            android:text="生活不止眼前的苟且,还有诗和远方" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="100dp"
            android:text="发电房" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="小麻小儿郎呀" />


        <Button
            android:layout_width="wrap_content"
            android:layout_height="75dp"
            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="wrap_content"
            android:layout_gravity="bottom"
            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="80dp"
            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="wrap_content"
            android:layout_gravity="bottom"
            android:text="人在他在,塔亡人亡" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="300dp"
            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="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="65dp"
            android:layout_gravity="bottom"
            android:text="人在他在,塔亡人亡" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="生活不止眼前的苟且,还有诗和远方1" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="250dp"
            android:text="发电房" />

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="这是结束" />
    </com.traveleasy.leaningui02.FlowLayout>


</ScrollView>

2、初始化存储

初始化三个数组,用于存储行数据,所有的行数据,行高

//    每一行的view
    private List<View> mLineView;
//    所有的行,一行一行的存储
    private List<List<View>> mViews;
//    每一行的高度
    private List<Integer> mHeights;
    private void init() {
        mLineView = new ArrayList<>();
        mViews = new ArrayList<>();
        mHeights = new ArrayList<>();
    }

3、测量

重新 onMeasure() 方法,测量view 的属性

源码的解释,都在代码中,如下:

       @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        获取限制的值
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightNode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

//        记录当前行的宽度和高度
        int lineWidth = 0;      //宽度是当前行子view 宽度的和
        int lineHeight = 0;     //高度是当前行所有子view 中高度的最大值

//        整个流式布局的宽度和高度
        int flowLayoutWidth = 0;  // 所有行中宽度的最大值
        int flowLayoutHeight = 0; // 所有行的高度的累加

        init();

//        获取到当前的所有child 数量
        int childCount = this.getChildCount();

//        先测量子View,再根据子View 尺寸, 计算自己的
        for (int i = 0; i < childCount; i++) {
            View child = this.getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);

//            获取到当前子View 的测量高度 和 宽度
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

//            已经放入的子 view 的宽度 + 准备放入的子 view 的宽度 大于 总宽度,就换行
            if (lineWidth + childWidth > widthSize) {
                mViews.add(mLineViews);
//                创建 新的一行
                mLineViews = new ArrayList<>();
//                所有行中,最宽的一行,作为流式布局的宽
                flowLayoutWidth = Math.max(flowLayoutWidth, lineWidth);
//                流式布局的高度,为所有行的高度相加
                flowLayoutHeight += lineHeight;
                mHeights.add(lineHeight);
                lineHeight = 0;
                lineWidth = 0;
            }
//            当前行添加子view
            mLineViews.add(child);
//            已有行宽,添加当前 子 view 的宽
            lineWidth += childWidth;

//            获取行中最高的子 View
            lineHeight = Math.max(lineHeight, childHeight);
        }

//        保存尺寸给后面使用
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : flowLayoutWidth,
                heightNode == MeasureSpec.EXACTLY ? heightSize : flowLayoutHeight);
    }

4、布局处理

  @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int currX = 0;
        int currY = 0;
        int lineCount = mViews.size();
//        处理每一行
        for (int i = 0; i < lineCount; i++) {
            List<View> lineViews = mViews.get(i);
            int lineHeight = mHeights.get(i);
            int size = lineViews.size();
//            处理每一行中的view
            for (int j = 0; j < size; j++) {
                View child = lineViews.get(j);
//                子view 的左上右下
                int left = currX;
                int top = currY;
                int right = left + child.getMeasuredWidth();
                int bottom = top + child.getMeasuredHeight();
//                布局子view
                child.layout(left, top, right, bottom);
                currX += child.getMeasuredWidth();
            }

            currY += lineHeight;
            currX = 0;
        }
    }

5、初步效果图

在这里插入图片描述

6、添加 padding margin 约束

到这就完了吗?当然没有,如图所示,没有约束啊,设置的样式无效。我们想要的效果如下图:

在这里插入图片描述

修改完之后的代码如下:

public class FlowLayout extends ViewGroup {

    //    每一行的view
    private List<View> mLineViews;
    //    所有的行,一行一行的存储
    private List<List<View>> mViews;
    //    每一行的高度
    private List<Integer> mHeights;

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

    public FlowLayout(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init();
    }

    private void init() {
        mLineViews = new ArrayList<>();
        mViews = new ArrayList<>();
        mHeights = new ArrayList<>();
    }


    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MarginLayoutParams(getContext(), attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        获取限制的值
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightNode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int paddingLeftAndRight = getPaddingLeft() + getPaddingRight();
        int paddingTopAndBottom = getPaddingTop() + getPaddingTop();

//        解决子 view match_parent 无效的问题
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);

//        记录当前行的宽度和高度
        int lineWidth = 0;      //宽度是当前行子view 宽度的和
        int lineHeight = 0;     //高度是当前行所有子view 中高度的最大值

//        整个流式布局的宽度和高度
        int flowLayoutWidth = 0;  // 所有行中宽度的最大值
        int flowLayoutHeight = 0; // 所有行的高度的累加

        init();

//        获取到当前的所有child 数量
        int childCount = this.getChildCount();

//        先测量子View,再根据子View 尺寸, 计算自己的
        for (int i = 0; i < childCount; i++) {
            View child = this.getChildAt(i);
//            measureChild(child, widthMeasureSpec, heightMeasureSpec);
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);

            MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            int childMarginLeftAndRight = lp.leftMargin + lp.rightMargin;
            int childMarginTopAndBottom = lp.topMargin + lp.bottomMargin;

//            获取到当前子View 的测量高度 和 宽度
            int childWidth = child.getMeasuredWidth();
            int childHeight = child.getMeasuredHeight();

//            已经放入的子 view 的宽度 + 准备放入的子 view 的宽度 大于 总宽度,就换行
            if (lineWidth + childWidth + childMarginLeftAndRight + paddingLeftAndRight > widthSize) {
                mViews.add(mLineViews);
//                创建 新的一行
                mLineViews = new ArrayList<>();
//                所有行中,最宽的一行,作为流式布局的宽
                flowLayoutWidth = Math.max(flowLayoutWidth, lineWidth);
//                流式布局的高度,为所有行的高度相加
                flowLayoutHeight += lineHeight;
                mHeights.add(lineHeight);
                lineHeight = 0;
                lineWidth = 0;
            }
//            当前行添加子view
            mLineViews.add(child);
//            已有行宽,添加当前 子 view 的宽
            lineWidth += childWidth + childMarginLeftAndRight;

//            获取行中最高的子 View
            lineHeight = Math.max(lineHeight, childHeight + childMarginTopAndBottom);

//            处理最后一行的显示
            if (i == childCount - 1){
                flowLayoutHeight += lineHeight;
                flowLayoutWidth = Math.max(flowLayoutWidth, lineWidth);
                mHeights.add(lineHeight);
                mViews.add(mLineViews);
            }
        }

        flowLayoutWidth += paddingLeftAndRight;
        flowLayoutHeight += paddingTopAndBottom;

//        保存尺寸给后面使用
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : flowLayoutWidth,
                heightNode == MeasureSpec.EXACTLY ? heightSize : flowLayoutHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int currX = getPaddingLeft();
        int currY = getPaddingTop();
        int lineCount = mViews.size();
//        处理每一行
        for (int i = 0; i < lineCount; i++) {
            List<View> lineViews = mViews.get(i);
            int lineHeight = mHeights.get(i);
            int size = lineViews.size();
//            处理每一行中的view
            for (int j = 0; j < size; j++) {
                View child = lineViews.get(j);
                MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

//                子view 的左上右下
                int left = currX + lp.leftMargin;
                int top = currY + lp.topMargin;
                int right = left + child.getMeasuredWidth();
                int bottom = top + child.getMeasuredHeight();
//                布局子view
                child.layout(left, top, right, bottom);
                currX += right + lp.rightMargin;
            }

            currY += lineHeight;
            currX = getPaddingLeft();
        }
    }
}

总结

流式布局 – 自定义ViewGroup
1.自定义属性,以及xml中使用
2.测量 — 先测量子View,再根据子View尺寸,计算自己的,保存尺寸给后面用
3.布局 onLayout — 根据自己的规则确定child 的位置
4.绘制 – onDraw(正常不会调用) 重写dispatchDraw – 一般不会用
5.交互

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。