• android高级UI之UI绘制流程(测量布局)


    在上一次https://www.cnblogs.com/webor2006/p/11839103.html中对于UI的整体绘制流程有了一个全面的了解,对于我们自定义ViewGroup时都知道会涉及到onMeasure()和onLayout()这两个很重要的流程,所以这一次则集中对这两块的细节给吃透了,还是参考大神的这两篇博客来学习:

    https://www.jianshu.com/p/4e3e25092015

    https://www.jianshu.com/p/82f23acbc1c2

    onMeasure测量流程:

    首先还是从上一次的总体绘制流程中来一点点分析测量流程,先来回顾一下整体绘制进入View的流程图:

    所以咱们从ViewRootImpl.performTraversals()方法里面找到开始测量的位置:

    而它里面则会调用到View.measure()方法:

    而mView其实就是DecorView,在进行进一步分析之前,先来对传进来的两个参数进行了解:childWidthMeasureSpec、childHeightMeasureSpec:

    关于这块的东东我在之前https://www.cnblogs.com/webor2006/p/7525979.html的UI学习中也已经详细剖析过,其实这个整型里面包含了布局的规格信息了,一个整型是有32位,而用前二位表示一个mode,而剩下的30位表示具体的值,咱们点击看一下getRootMeasureSpec():

    其中MeasureSpec的作用是在Measure流程中,系统将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec(规格),然后在onMeasure中根据这个MeasureSpec来确定view的测量宽高。关于这个细节可以看一下博主对于MeasureSpec核心代码的说明:

       public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT; 
         /**
          * UNSPECIFIED 模式:
          * 父View不对子View有任何限制,子View需要多大就多大
          */ 
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    
        /**
          * EXACTYLY 模式:
          * 父View已经测量出子View所需要的精确大小,这时候View的最终大小
          * 就是SpecSize所指定的值。对应于match_parent和精确数值这两种模式
          */ 
        public static final int EXACTLY     = 1 << MODE_SHIFT;
    
        /**
          * AT_MOST 模式:
          * 子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值,
          * 即对应wrap_content这种模式
          */ 
        public static final int AT_MOST     = 2 << MODE_SHIFT;
    
        //将size和mode打包成一个32位的int型数值
        //高2位表示SpecMode,测量模式,低30位表示SpecSize,某种测量模式下的规格大小
        public static int makeMeasureSpec(int size, int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }
    
        //将32位的MeasureSpec解包,返回SpecMode,测量模式
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
    
        //将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格大小
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
        //...
    }
    
    测量模式
        EXACTLY :父容器已经测量出所需要的精确大小,这也是childview的最终大小
                ------match_parent,精确值是爸爸的
    
        ATMOST : child view最终的大小不能超过父容器的给的
                ------wrap_content 精确值不超过爸爸
    
        UNSPECIFIED: 不确定,源码内部使用
                -------一般在ScrollView,ListView 

    好,对于getMode()和getSize()实现的细节其实也比较好理解,用博主的图来说明一下:

    getMode解析用了measureSpec & MODE_MASK(解析只要前2位):

    getSize解析用了measureSpec & ~MODE_MASK(不要前两位):

    好,对于MeasureSpec的细节已经清楚了,接下来回到主流程继续:

    而回到咱们的DecorView,我们都知道它是一个FrameLayout,所以此时就转到它里面的onMeasure大概来瞅一下,这里贴一下博主所分析过的:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //获取当前布局内的子View数量
    int count = getChildCount();
    
    //判断当前布局的宽高是否是match_parent模式或者指定一个精确的大小,如果是则置measureMatchParent为false.
    final boolean measureMatchParentChildren =
            MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
            MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();
    
    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;
    
    //遍历所有类型不为GONE的子View
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //对每一个子View进行测量
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //寻找子View中宽高的最大者,因为如果FrameLayout是wrap_content属性
            //那么它的大小取决于子View中的最大者
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            //如果FrameLayout是wrap_content模式,那么往mMatchParentChildren中添加
            //宽或者高为match_parent的子View,因为该子View的最终测量大小会受到FrameLayout的最终测量大小影响
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT ||
                        lp.height == LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }
    
    // Account for padding too
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();
    
    // Check against our minimum height and width
    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
    
    // Check against our foreground's minimum height and width
    final Drawable drawable = getForeground();
    if (drawable != null) {
        maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());
        maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());
    }
    
    //保存测量结果
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
    
    //子View中设置为match_parent的个数
    count = mMatchParentChildren.size();
    //只有FrameLayout的模式为wrap_content的时候才会执行下列语句
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            final View child = mMatchParentChildren.get(i);
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    
            //对FrameLayout的宽度规格设置,因为这会影响子View的测量
            final int childWidthMeasureSpec;
    
            /**
              * 如果子View的宽度是match_parent属性,那么对当前FrameLayout的MeasureSpec修改:
              * 把widthMeasureSpec的宽度规格修改为:总宽度 - padding - margin,这样做的意思是:
              * 对于子Viw来说,如果要match_parent,那么它可以覆盖的范围是FrameLayout的测量宽度
              * 减去padding和margin后剩下的空间。
              *
              * 以下两点的结论,可以查看getChildMeasureSpec()方法:
              *
              * 如果子View的宽度是一个确定的值,比如50dp,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
              * SpecSize为子View的宽度,即50dp,SpecMode为EXACTLY模式
              * 
              * 如果子View的宽度是wrap_content属性,那么FrameLayout的widthMeasureSpec的宽度规格修改为:
              * SpecSize为子View的宽度减去padding减去margin,SpecMode为AT_MOST模式
              */
            if (lp.width == LayoutParams.MATCH_PARENT) {
                final int width = Math.max(0, getMeasuredWidth()
                        - getPaddingLeftWithForeground() - getPaddingRightWithForeground()
                        - lp.leftMargin - lp.rightMargin);
                childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
                        width, MeasureSpec.EXACTLY);
            } else {
                childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
                        getPaddingLeftWithForeground() + getPaddingRightWithForeground() +
                        lp.leftMargin + lp.rightMargin,
                        lp.width);
            }
            //同理对高度进行相同的处理,这里省略...
    
            //对于这部分的子View需要重新进行measure过程
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }

    具体细节就不细细揣摩了,但是要明白它其实就是遍历了子View然后进行子View的测量,而不像View.onMeasure默认的实现只测量了自身,最后引用博主对于这块测量的一个总结:

    onLayout布局流程:

    好,接下来则到了布局测量阶段了,还是找入口处:

    其中跟进去看主流程:

    而此时会调用View.layout()方法,跟进去:

    而对于setFrame在进行初始化的时候会对比上一次是否一致,若一致则不会在此进行,若是一致,则会使我们旧的信息直接失效invalidate(sizeChanged);

    而这个onLayout()则是一个空实现:

    所以对于DecorView来说就得看它具体onLayout是如何摆放元素的了:

    然后它里面具体的实现则是遍历它所有的子View,一个个进行子View的摆放:

        void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
            final int count = getChildCount();
    
            final int parentLeft = getPaddingLeftWithForeground();
            final int parentRight = right - left - getPaddingRightWithForeground();
    
            final int parentTop = getPaddingTopWithForeground();
            final int parentBottom = bottom - top - getPaddingBottomWithForeground();
    
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                if (child.getVisibility() != GONE) {
                    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    
                    final int width = child.getMeasuredWidth();
                    final int height = child.getMeasuredHeight();
    
                    int childLeft;
                    int childTop;
    
                    int gravity = lp.gravity;
                    if (gravity == -1) {
                        gravity = DEFAULT_CHILD_GRAVITY;
                    }
    
                    final int layoutDirection = getLayoutDirection();
                    final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
                    final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
    
                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                        case Gravity.CENTER_HORIZONTAL:
                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                            lp.leftMargin - lp.rightMargin;
                            break;
                        case Gravity.RIGHT:
                            if (!forceLeftGravity) {
                                childLeft = parentRight - width - lp.rightMargin;
                                break;
                            }
                        case Gravity.LEFT:
                        default:
                            childLeft = parentLeft + lp.leftMargin;
                    }
    
                    switch (verticalGravity) {
                        case Gravity.TOP:
                            childTop = parentTop + lp.topMargin;
                            break;
                        case Gravity.CENTER_VERTICAL:
                            childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                            lp.topMargin - lp.bottomMargin;
                            break;
                        case Gravity.BOTTOM:
                            childTop = parentBottom - height - lp.bottomMargin;
                            break;
                        default:
                            childTop = parentTop + lp.topMargin;
                    }
    
                    child.layout(childLeft, childTop, childLeft + width, childTop + height);
                }
            }
        }

    至此,整个跟咱们自定义相关的绘制流程的细节就分析完了,接下来则咱们以一个自定义的小案例来进行实践一下。

    实践:

    onMeasure处理:

    这个案例的最终样子长这样:

    也就是大家很熟悉的瀑布流式布局控件的效果,这个在实际显示各种标签时用得比较普遍,当然这个DEMO只是为了巩固咱们的绘制基础,不可能做成商业那么完善,重点是体会在自定控件时对于onMeasure和onLayout对于自定义控件的意义。下面则开始一点点来实现这个效果:

    首先新建一个自定义View,然后声明在xml中,里面整一些卡片子元素,比较简单,直接将代码贴出来了:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <com.android.flowlayout.MyFlowLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
    
            <TextView
                style="@style/text_flag_01"
                android:text="hello" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hello,hi" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="你是我的"
                android:textSize="18sp" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hello,man" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="helloview" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="view" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="我是你的"
                android:textSize="20sp" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="he" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hello" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="textview" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="view" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hello,view" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hello,mt" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel2" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel2" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel2" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel2" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="hel2" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="kwkkskksks" />
    
            <TextView
                style="@style/text_flag_01"
                android:text="大ddddddddddddddddd" />
    
        </com.android.flowlayout.MyFlowLayout>
    
    
    </LinearLayout>

    其中用到一个样式和一个背景资源:

        <style name="text_flag_01">
            <item name="android:layout_width">wrap_content</item>
            <item name="android:layout_height">wrap_content</item>
            <item name="android:layout_margin">4dp</item>
            <item name="android:background">@drawable/flag_01</item>
            <item name="android:textColor">#ffffff</item>
        </style>

    flag_01.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android" >
    
        <solid android:color="#7690A5" >
        </solid>
    
        <corners android:radius="5dp"/>
        <padding
            android:bottom="2dp"
            android:left="10dp"
            android:right="10dp"
            android:top="2dp" />
    
    </shape>

    好,此时运行肯定一片空白,这个在之前关于这块的UI学习中也演示过了,因为需要咱们自己来测量及摆放布局:

    好,接下来开始聚集MyFlowLayout的实现,对于绘制流程来说,首先会调用onMeasure()方法,默认它只会测量自身,不会测量它里面的子View的,所以接下来需要重写一个onMeasure方法,来具体实现咱们的测量逻辑:

     

    那接下来怎么来决定当前控件的宽高呢?很明显需要根据Mode来了,因为我们在布局中对于控件的尺寸可以声明为:

    所以:

    接下来则开启判断:

    好,针对else的情况接下来则要开启子元素的遍历了:

    所以先定义累加宽高的变量:

    好,下面则开启具体的遍历计算,当然在遍历时要对具体的每个子View也需要进行一次测量的:

    那这样做完之后是不是就可以得到每个子View的宽高信息了?

    其实这里需要考虑一个细节,就是每个子View都可以设置margin信息的,所以对于子View的宽高是需要额外加上这个margin才行的,所以下面咱们来获取每个子View的margin,如何获取呢?

    但是它只有宽高信息:

    那怎么办?此时需要MarginLayoutParams进行转一下:

    它是LayoutParams的子类,能不能直接这样写呢?

    咱们运行一下就知道行不行了:

    抛异常了。。那要怎么整呢?其实这里需要重写一个方法,貌似平常没怎么用了:

    好,接下来咱们就可以正常的获取子View的margin信息了,所以咱们就可以来累加了:

    但是此时还是有一个问题,当遇到换行的话其整个控件的高度和宽度肯定也会发生变化,所以接下来则需要将换行的逻辑给处理了才行:

    那核心就是如何来换行了,下面来做一下:

    貌似这个totalCalWidth和totalCalHeight这俩变量的名称取得不够见名之义,应该代表是当前的行信息,所以改一下名称:

    貌似整个View的宽高也计算完了,接下来则可以走onLayout布局流程了,但是!!!目前还缺布局的元素,哪些元素该摆放在哪,是摆到第一行,还是第二行?这些信息都没有,很明显这些应该在测量时就应该产出的,所以接下来咱们再来定义两个产生的变量,之后在onLayout时只需要根据产生的这俩变量进行布局既可,如下:

    我们在每次换行处理时进行这俩变量的数据更新,如下:

    好,一切就绪,接下来则直接进行布局摆放既可。

    onLayout处理:

    接下来运行试一下,发现界面一片空白,啥也木有显示。。debug发现原来是由于onMeasure执行了两遍,咱们打个日志确认一下:

    确实是执行了2遍。。很明显布局需要的那俩变量数据就会有重复了,所以解决办法就是在每一次onMeasure()触发时清除一下,如下:

    再运行一下:

    那为啥onMeasure会调用两次?这里还是从绘制流程来找原因:

    在这个方法里面有一个真正开始绘的代码,其中可以找到答案:

    貌似目前的效果完美了,其实这里面显示不全,最后一行没有显示出来:

    最后一行的元素是它,很明显它木有出现在目前运行的效果图中,那是为啥呢?很明显是程序有bug,这里直接来修复一下:

     

    好,最后再来运行一下:

    ok,完美了!!其实还是不完美的,咱们来修改一下这个view的宽高信息:

    此时再运行又出现一片空白了。。咱们最后来解决一下它,其实还是代码写得有bug的原因,咱们简单分析一下:

    很明显,此时会进入这个条件,但是很明显我们只确定了当前控件的宽高,但是对于子元素完全没有主动测量嘛,所以。。咱们得把else中的代码挪过来才行,修改如下:

    最后整个View的代码如下:

    package com.android.flowlayout;
    
    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MyFlowLayout extends ViewGroup {
    
        /* 代表每一行的高度 */
        List<Integer> lstLineHegiht = new ArrayList<>();
        /* 每一行里面显示具体的子控件 */
        List<List<View>> lstLineView = new ArrayList<>();
    
        public MyFlowLayout(Context context) {
            this(context, null);
        }
    
        public MyFlowLayout(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public MyFlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            lstLineView.clear();
            lstLineHegiht.clear();
            //首先需要测量当前控件自身的宽高,根据MODE来
            int measureWidth = 0;
            int measureHeight = 0;
    
            //算出宽高的mode+size
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            //以下两个变量是根据子控件的不断测量之后进行累加的
            int currentLineTotalCalWidth = 0;
            int currentLineTotalCalHeight = 0;
    
            if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
                //说明当前控件设置了精确的值了,则算出来的size则就是整个当前控件的大小了
                measureWidth = widthSize;
                measureHeight = heightSize;
    
                //此时则还需要测量子View的宽高
                int childCount = getChildCount();
                List<View> viewList = new ArrayList<>();
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    //先让子控件测量自己
                    measureChild(child, widthMeasureSpec, heightMeasureSpec);//第二、三个参数是传的父的measureSpec
    
                    //获取当前子View的margin信息
                    MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
    
                    //获取当前子View的实际宽高
                    final int childWidth = child.getMeasuredWidth() + layoutParams.rightMargin + layoutParams.leftMargin;
                    final int childHegiht = child.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
    
                    //处理换行
                    if (childWidth + currentLineTotalCalWidth > widthSize) {
                        //1、首先将当前的行高信息累加到当前控件的宽高信息上
    
                        lstLineHegiht.add(currentLineTotalCalHeight);
                        lstLineView.add(viewList);
    
                        //2、更新总行的宽高信息,换行了嘛,所以当前行得从头开始算
                        currentLineTotalCalWidth = childWidth;
                        currentLineTotalCalHeight = childHegiht;
    
                        viewList = new ArrayList<>();
                        viewList.add(child);
    
                    } else {
                        //在当前行可以放得下当前控件,则直接累加既可
                        currentLineTotalCalWidth += childWidth;
                        currentLineTotalCalHeight = Math.max(currentLineTotalCalHeight, childHegiht);
    
                        viewList.add(child);
                    }
    
                    //6.如果正好是最后一行需要换行
                    if (i == childCount - 1) {
                        //记录当前行的最大宽度,高度累加
    
                        //将当前行的viewList添加至总的mViewsList,将行高添加至总的行高List
                        lstLineView.add(viewList);
                        lstLineHegiht.add(currentLineTotalCalHeight);
    
                    }
                }
            } else {
                int childCount = getChildCount();
                List<View> viewList = new ArrayList<>();
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    //先让子控件测量自己
                    measureChild(child, widthMeasureSpec, heightMeasureSpec);//第二、三个参数是传的父的measureSpec
    
                    //获取当前子View的margin信息
                    MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
    
                    //获取当前子View的实际宽高
                    final int childWidth = child.getMeasuredWidth() + layoutParams.rightMargin + layoutParams.leftMargin;
                    final int childHegiht = child.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
    
                    //处理换行
                    if (childWidth + currentLineTotalCalWidth > widthSize) {
                        //1、首先将当前的行高信息累加到当前控件的宽高信息上
                        measureWidth = Math.max(measureWidth, currentLineTotalCalWidth);
                        measureHeight += currentLineTotalCalHeight;
    
                        lstLineHegiht.add(currentLineTotalCalHeight);
                        lstLineView.add(viewList);
    
                        //2、更新总行的宽高信息,换行了嘛,所以当前行得从头开始算
                        currentLineTotalCalWidth = childWidth;
                        currentLineTotalCalHeight = childHegiht;
    
                        viewList = new ArrayList<>();
                        viewList.add(child);
    
                    } else {
                        //在当前行可以放得下当前控件,则直接累加既可
                        currentLineTotalCalWidth += childWidth;
                        currentLineTotalCalHeight = Math.max(currentLineTotalCalHeight, childHegiht);
    
                        viewList.add(child);
                    }
    
                    //6.如果正好是最后一行需要换行
                    if (i == childCount - 1) {
                        //记录当前行的最大宽度,高度累加
                        measureWidth = Math.max(measureWidth, currentLineTotalCalWidth);
                        measureHeight += currentLineTotalCalHeight;
    
    
                        //将当前行的viewList添加至总的mViewsList,将行高添加至总的行高List
                        lstLineView.add(viewList);
                        lstLineHegiht.add(currentLineTotalCalHeight);
    
                    }
                }
    
            }
    
    
            setMeasuredDimension(measureWidth, measureHeight);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int curLeft = 0;
            int curTop = 0;
    
            int left, top, right, bottom;
    
            for (int i = 0; i < lstLineView.size(); i++) {
                List<View> lineviews = lstLineView.get(i);
    
                for (int j = 0; j < lineviews.size(); j++) {//显示一行中的元素
                    View view = lineviews.get(j);
    
                    //也得考虑每一个子元素的margin
                    MarginLayoutParams layoutParams = (MarginLayoutParams) view.getLayoutParams();
    
                    left = curLeft + layoutParams.leftMargin;
                    top = curTop + layoutParams.topMargin;
                    right = left + view.getMeasuredWidth();
                    bottom = top + view.getMeasuredHeight();
                    view.layout(left, top, right, bottom);
    
                    curLeft += view.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
    
    
                }
    
                curLeft = 0;//当一行布局完了,当前的left=0,curTop则需要往下跑,以便输出下一行元素
                curTop += lstLineHegiht.get(i);
    
            }
    
            lstLineView.clear();
            lstLineHegiht.clear();
        }
    
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new MarginLayoutParams(getContext(), attrs);
        }
    }
  • 相关阅读:
    mysql 卸载 linux
    mybatis教程
    Python操作Redis的5种数据类型
    python+selenium 浏览器无界面模式运行
    关闭Sublime Text 3的自动更新
    ui自动化-则神第一天02-学习练习一个网址写脚本
    ui自动化-则神第一天01-html基础和元素定位之面试问题
    ui自动化-则神第一天01
    字典的学习
    安全测试的测试整理
  • 原文地址:https://www.cnblogs.com/webor2006/p/12167825.html
Copyright © 2020-2023  润新知