美文网首页Android
Android自定义ViewGroup

Android自定义ViewGroup

作者: Timmy_zzh | 来源:发表于2021-02-25 21:41 被阅读0次
  • ViewGroup是一个容器,内部可以包裹多个子View
    • 自定义ViewGroup的时候也需要处理三个方法onMeasure,onLayout,onDraw方法

1. ViewGroup的onMeasure

  • 因为ViewGroup是一个容器,onMeasure方法会更加复杂一些。因为ViewGroup在测量自己的宽高之前,需要先确定其内部子View所占大小,然后才能确定自己的大小。

比如如下一段代码:

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:orientation="vertical">

        <TextView
            android:layout_width="300dp"
            android:layout_height="wrap_content"
            android:background="#f00"
            android:padding="10dp"
            android:text="Text1" />

        <TextView
            android:layout_width="200dp"
            android:layout_height="wrap_content"
            android:background="#0f0"
            android:padding="10dp"
            android:text="Text2" />

        <TextView
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:background="#00f"
            android:padding="10dp"
            android:text="Text3" />

    </LinearLayout>
  • 效果:
1.LinearLayout的宽度.png
  • LinearLayout的宽高都是wrap_content表示由子控件的大小决定,而3个子控件的宽度分别是300,200,100,最终LinearLayout的宽度显示为300dp
    • 所以自定义一个ViewGroup的时候,需要在onMeasure方法中综合考虑子View的宽度
自定义流式布局 FlowLayout

效果如下:

2.流式布局FlowLayout.png

在实现FlowLayout中,FlowLayout中的每一行上的item个数不固定,当每行的item累计宽度超过可用总宽度,则需要重启一行排放item。所以需要在onMeasure方法中主动的分行计算出FlowLayout的最终高度。

    /**
     * 测量:先遍历计算子View的大小,然后最终计算得出ViewGroup的大小
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取到父类传递过来的宽高和测量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //子View个数
        int childCount = getChildCount();
        //当前行计算的宽度
        int lineWidth = 0;
        //当前行最高的高度
        int lineMaxHeight = 0;
        //累加的高度
        int totalHeight = 0;

        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            //todo 先测量子View,再拿到子View的宽高\
            measureChild(childView,widthMeasureSpec,heightMeasureSpec);
          
            MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
            int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

            //判断一行子view累加宽度 是否超过 父容器规定的宽度
            if (lineWidth + childWidth > widthSize) {
                //超过最大宽度,另外起一行
                lineWidth = childWidth;
                //累加后的totalHeight 等于上面所有行的总高度,不包括当前行的高度
                totalHeight += lineMaxHeight;
                lineMaxHeight = childHeight;
            } else {
                //未超过最大宽度,宽度累加,高度选最大值
                lineWidth += childWidth;
                lineMaxHeight = Math.max(lineMaxHeight, childHeight);
            }

            //最后一个子view特殊处理,需要加上当前行的高度
            if (i == childCount - 1) {
                totalHeight += lineMaxHeight;
            }
        }

        //设置最终的宽高
        heightSize = heightMode == MeasureSpec.EXACTLY ? heightSize : totalHeight;
        setMeasuredDimension(widthSize, heightSize);
    }
  • 解析
    • 在自定义ViewGroup的onMeasure方法中,调用measureChild方法递归测量子View
    • 通过叠加每一行的高度,计算出最终FlowLayout的最终高度totalHeight

2.onLayout

  • 自定义ViewGroup时,复写onMeasure方法只是计算出ViewGroup的最终现实宽高,但是并没有规定其中包裹的子view显示在何处位置。要定义ViewGroup内部子View的显示规则,需要复写并实现onLayout方法

ViewGroup中的onLayout方法声明如下:

    @Override
    protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
  • VIewGroup的onLayout方法是一个抽象方法,自定义ViewGroup时必须主动实现如何排布子View,具体就是遍历每一个子view,调用childView.layout(l,t,r,b)方法来为每个子view设置具体的布局位置
    • 四个参数分别代表左上右下的坐标位置。
    /**
     * 摆放子view
     * 1。遍历拿到每一个childView的宽高,并计算每一行的宽高
     * 2。二次遍历,调用childView的layout方法进行子view的摆放
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        allViews.clear();
        lineMaxHeights.clear();

        //保存每一行的子view
        List<View> lineViews = new ArrayList<>();
        //每一行的宽度
        int lineTotalWidth = 0;
        //每行最高的高度
        int lineMaxHeight = 0;
        int childCount = getChildCount();

        for (int i = 0; i < childCount; i++) {
            View childView = getChildAt(i);
            // 获取到ChildView的宽高
            MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();
            int childWidth = childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            int childHeight = childView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;

            if (lineTotalWidth + childWidth > getMeasuredWidth()) {
                //超过一行宽度,将前一行的所有view添加到allViews,高度添加到集合中
                allViews.add(lineViews);
                lineMaxHeights.add(lineMaxHeight);
                //开启新一行
                lineMaxHeight = 0;
                lineTotalWidth = 0;
                lineViews = new ArrayList<>();
            }
            lineViews.add(childView);
            lineTotalWidth += childWidth;
            lineMaxHeight = Math.max(lineMaxHeight, childHeight);
        }
        //最后一行处理
        allViews.add(lineViews);
        lineMaxHeights.add(lineMaxHeight);

        /**
         * 遍历allViews,lineMaxHeights 拿到每一行的views和高度,进行子view进行摆放
         */
        int mTop = 0;
        int mLeft = 0;
        for (int i = 0; i < allViews.size(); i++) {
            List<View> oneLineViews = allViews.get(i);
            Integer oneLineMaxHeight = lineMaxHeights.get(i);

            //遍历每一行的views
            for (int j = 0; j < oneLineViews.size(); j++) {
                View childView = oneLineViews.get(j);
                MarginLayoutParams lp = (MarginLayoutParams) childView.getLayoutParams();

                int left = mLeft + lp.leftMargin;
                int top = mTop + lp.topMargin;
                int right = left + childView.getMeasuredWidth();
                int bottom = top + childView.getMeasuredHeight();

                childView.layout(left, top, right, bottom);
                mLeft += childView.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
            }
            mLeft = 0;
            mTop += oneLineMaxHeight;
        }
    }
  • xml中使用
    <com.timmy.demopractice.view.FlowLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#ff0">

                    ...
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="6dp"
            android:background="#f00"
            android:text="23452452"
            android:textSize="14sp" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="6dp"
            android:background="#f00"
            android:text="8968796"
            android:textSize="14sp" />
                    ...

    </com.timmy.demopractice.view.FlowLayout>

  • 最终效果如下:
3.自定义流式布局FlowLayout.png

相关文章

网友评论

    本文标题:Android自定义ViewGroup

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