**
ViewGroup的三种布局总结篇
**
一、三大布局测量步骤
-
父容器尺寸确定,子控件尺寸不确定
需要根据父容器的尺寸确定子控件的尺寸
#onMeasure中获取父容器的尺寸,,获取限制measurespec
#计算子控件的尺寸
#侯建MeasureSpec对象,用于测量子空间时限制子控件
#使用measure方法测量子控件
#onLayout中调用视图的额layout方法布局子控件 -
父容器尺寸不确定,子控件尺寸确定
需要根据子控件的尺寸确定父容器的尺寸
#onMeasure中先测量父容器,获取限制measurespec
#测量子控件,获取尺寸
#按照规则计算父容器的宽高
#设置父容器的宽高尺寸
#onLayour中对子控件进行布局 -
父容器尺寸不确定,子控件尺寸不确定
需要先子控件的尺寸,然后再确定父容器的尺寸
#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>