Android kotlin的ViewGroup的三种布局总结篇(流式布局添加大小不同的View)

**

ViewGroup的三种布局总结篇

**
一、三大布局测量步骤

  1. 父容器尺寸确定,子控件尺寸不确定
    需要根据父容器的尺寸确定子控件的尺寸
    #onMeasure中获取父容器的尺寸,,获取限制measurespec
    #计算子控件的尺寸
    #侯建MeasureSpec对象,用于测量子空间时限制子控件
    #使用measure方法测量子控件
    #onLayout中调用视图的额layout方法布局子控件

  2. 父容器尺寸不确定,子控件尺寸确定
    需要根据子控件的尺寸确定父容器的尺寸
    #onMeasure中先测量父容器,获取限制measurespec
    #测量子控件,获取尺寸
    #按照规则计算父容器的宽高
    #设置父容器的宽高尺寸
    #onLayour中对子控件进行布局

  3. 父容器尺寸不确定,子控件尺寸不确定
    需要先子控件的尺寸,然后再确定父容器的尺寸
    #onMeasure中先测量父容器,获取限制measurespec
    #自定义View中通过onMeasure测量子View的尺寸
    #按照规则计算父容器的宽高
    #设置父容器的宽高尺寸
    #onLayout中对子控件进行布局

二、测量子控件调用方法的区分
*

measureChild()
child.measure()
measureChildren()

// 参数int widthMeasureSpec, int heightMeasureSpec
child.measure()
这被称为找出一个视图应该有多大。 父母在width和height参数中提供约束信息。
源码(部分代码):

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        boolean optical = isLayoutModeOptical(this);
        if (optical != isLayoutModeOptical(mParent)) {
            Insets insets = getOpticalInsets();
            int oWidth  = insets.left + insets.right;
            int oHeight = insets.top  + insets.bottom;
            widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);
            heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
        }
}

// 参数View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec
measureChild()
要求所有的的孩子自我衡量,并考虑既要考虑该视图的MeasureSpec要求,又要考虑其填充。
繁重的工作在getChildMeasureSpec中完成。
源码:

protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

// 参数int widthMeasureSpec, int heightMeasureSpec
要求所有孩子去衡量自己,并既要考虑该视图的MeasureSpec要求,又要考虑其填充。跳过处于GONE状态的孩子 ,繁重的工作在getChildMeasureSpec中完成。
measureChildren()
源码:

 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

可以看出关系是
measureChildren -> measureChild -> measure
measureChildren 调用meansreChild,measreChild调用meansre

做一个大小不确定的流式布局demo
结果:
在这里插入图片描述
在这里插入图片描述

kotlin代码
自定义ViewGroup

package com.example.myviewgroupreview

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.ViewGroup

class MyViewGroup4:ViewGroup {
    // 记录所有的view,是一个二维数组,每一个元素是一行views
    lateinit var totalViews:MutableList<MutableList<View>>
    // 记录每行的高度
    lateinit var totalLineHeight:MutableList<Int>
    // 间距
    private val space = 30
    constructor(context: Context, attrs: AttributeSet):super(context, attrs){}

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        // 初始化
        totalViews = mutableListOf<MutableList<View>>()
        totalLineHeight = mutableListOf<Int>()
        
        // 获得ViewGroup的建议宽度
        val parentWidth = MeasureSpec.getSize(widthMeasureSpec)
        // 记录当前行的数据高度和宽度
        var currentWidth = space
        var currentHeight = 0
        // 保存当前行的View
        var lineViews = mutableListOf<View>()
        // 记录总的宽高
        var resultHeight = space
        var resultWidth = 0

        for (i in 0 until childCount){
            val child = getChildAt(i)
            val childPar = child.layoutParams
            val childWidthSpec = getChildMeasureSpec(widthMeasureSpec,2*space,childPar.width)
            val childHeightSpec = getChildMeasureSpec(heightMeasureSpec,2*space,childPar.height)
            child.measure(childWidthSpec,childHeightSpec)
            // 判断是否换行
            if (currentWidth + child.measuredWidth + space <= parentWidth){
                // 换行
                // 更新数据
                currentWidth +=child.measuredWidth+space
                currentHeight = Math.max(currentHeight,child.measuredHeight)
                // 保存数据
                lineViews.add(child)

            }else{
                // 不换行
                // 保存旧数据
                totalViews.add(lineViews)
                totalLineHeight.add(currentHeight)
                resultWidth = Math.max(resultWidth,currentWidth)
                resultHeight +=currentHeight+space

                // 清空旧数据 重置
                lineViews = mutableListOf()

                // 赋值新数据
                lineViews.add(child)
                currentWidth = child.measuredWidth+space
                currentHeight = child.measuredHeight

            }
            // 如果是最后一个,需要将最后一行的lineView添加到totalViews
            if (i == childCount-1){
                totalViews.add(lineViews)
                totalLineHeight.add(currentHeight)
                resultWidth = Math.max(resultWidth,currentWidth)+space
                resultHeight +=currentHeight+space

            }
        }
        // 设置ViewGroup的大小
        setMeasuredDimension(resultWidth,resultHeight)
    }
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        var left = space
        var top = space
        var right = 0
        var bottom = 0
        val row = totalViews.size

        for (i in 0 until row){
            val count = totalViews[i].size
            for (j in 0 until  count){
                val child = totalViews[i][j]
                right = left+child.measuredWidth
                bottom = top+child.measuredHeight
                child.layout(left,top,right,bottom)
                left +=child.measuredWidth+space
            }
            top +=totalLineHeight[i]+space
            left = space
        }
    }

}

自定义View

package com.example.myviewgroupreview

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View

class MyView:View {
    private var cx = 0f
    private var cy = 0f
    private var radius = 0f
    private val paint = Paint().apply {
        color = Color.CYAN
        style = Paint.Style.FILL
    }
    constructor(context: Context, attrs: AttributeSet):super(context, attrs){

    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        val suggestWidthMode = MeasureSpec.getMode(widthMeasureSpec)
        val suggestWidthSize = MeasureSpec.getSize(widthMeasureSpec)
        val suggestHeightMode = MeasureSpec.getMode(heightMeasureSpec)
        val suggestHeightSize = MeasureSpec.getSize(heightMeasureSpec)

        var resultWidth = 0
        var resultHeight = 0

        when(suggestWidthMode){
            MeasureSpec.AT_MOST-> resultWidth = 100
            MeasureSpec.EXACTLY-> resultWidth = suggestWidthSize
        }
        when(suggestHeightMode){
            MeasureSpec.AT_MOST-> resultHeight = 100
            MeasureSpec.EXACTLY-> resultHeight = suggestHeightSize
        }

        setMeasuredDimension(resultWidth,resultHeight)
    }
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        cx = measuredWidth/2f
        cy = measuredHeight/2f
        radius = Math.min(measuredWidth,measuredHeight)/2f
    }
    override fun onDraw(canvas: Canvas?) {
        canvas?.drawCircle(cx, cy, radius, paint)
    }
}

xml的代码

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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.example.myviewgroupreview.MyViewGroup4
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:background="@color/black"
      >
      <com.example.myviewgroupreview.MyView
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:background="@color/design_default_color_primary"
          />
      <com.example.myviewgroupreview.MyView
          android:layout_width="100dp"
          android:layout_height="200dp"
          android:background="@color/design_default_color_primary"
          />
      <View
          android:layout_width="150dp"
          android:layout_height="40dp"
          android:background="@color/purple_200"/>
      <View
          android:layout_width="100dp"
          android:layout_height="80dp"
          android:background="@color/purple_200"/>
      <View
          android:layout_width="300dp"
          android:layout_height="50dp"
          android:background="@color/purple_200"/>
      <View
          android:layout_width="80dp"
          android:layout_height="80dp"
          android:background="@color/purple_200"/>
      <View
          android:layout_width="150dp"
          android:layout_height="40dp"
          android:background="@color/purple_200"/>
      <View
          android:layout_width="100dp"
          android:layout_height="80dp"
          android:background="@color/purple_200"/>
      <View
          android:layout_width="300dp"
          android:layout_height="50dp"
          android:background="@color/purple_200"/>
      <View
          android:layout_width="80dp"
          android:layout_height="80dp"
          android:background="@color/purple_200"/>
  </com.example.myviewgroupreview.MyViewGroup4>

</FrameLayout>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值