Android view的测绘练习-流式布局-FlowLayou
2020-09-20 本文已影响0人
进击的包籽
- Android View的测量 最好先了解一下
- Github源码地址
- 码云地址
-
这次要实现的功能先看一下
FlowLayout
1.流式布局分析
1.每一行的宽高
-
换行条件就是,已使用的宽度 + 当前子view的宽度 + 子view之间的间隔 + FlowLayout的 paddingLeft + paddingRight,如果这个值大于FlowLayout的宽度,那就需要另起一行,再addView
image.png -
每一行需要的高度就是所有子view的高度最大值
-
当换行时,高度的值就是已使用的高度 + 这一行需要的高度 ,高度用List<Integer>保存,后面onLayout布置view的位置
-
子view用List<List<View>>来保存,就是二维数组,用于后面onLayout使用
2.子view测量
- 每个子view拿到自己的LayoutParams,加上FlowLayout的MeasureSpec
- 通过getChildMeasureSpec 方法获取到子view的MeasureSpec
- 子view再调用 childView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
- 最后childView.measuredWidth就是子view测量过的实际宽高
3.FlowLayout的大小(onMeasure)
image.png- 这里黑色代表Activity,最外层的ViewGroup
- 红色代表FlowLayout,如果他的测量模式是EXACTLY,精确模式,那他的宽度就是自己的MeasureSpec.getSize()方法的大小,可能是match_parent,也可能是写的具体dp,子view大小不关心;如果不是,那就要受到子view大小影响,就要先测量子View加起来需要多少空间,再把需要的空间大小赋值。前面相当于给你两百平米的房子,里面怎么分小房间,都不会超过两百平,而后面一种是小房间加起来,最后你需要多大,再给多大的空间。
- 蓝色代表子View ,子View的宽高影响自己的排列,如果宽度达到了FlowLayout的最大值,就需要换行了。
4.布局位置(onLayout)
- 布局位置起始点在FlowLayout左上角,但要算上paddingTop 和 paddingLeft
- 利用在onMeasure时计算的子view大小,循环将子view放到指定的位置
2.代码
1.FlowLayout的onMeasure
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
clearMeasureParams()
//父布局的宽高
var viewGroupWidth = MeasureSpec.getSize(widthMeasureSpec)
var viewGroupHeight = MeasureSpec.getSize(heightMeasureSpec)
//子View需要父控件的大小
var viewGroupNeedWidth = 0
var viewGroupNeedHeight = 0
//每一行的子View
var lineViews: MutableList<View> = ArrayList()
//每一行已用的宽度
var lineUseWidth = 0
//每一行已用的高度
var lineUseHeight = 0
//遍历所有子view
for (i in 0 until childCount) {
val childView = getChildAt(i)
//子view可见
if (childView.visibility == View.VISIBLE) {
//获取子View的
val layoutParams = childView.layoutParams
//子view的measureSpec,传入父布局的measureSpec,还有父布局设置的内边距,子view的大小(大于0就是确定的大小,-1是match_parent,-2是wrap_content)
val childWidthMeasureSpec = getChildMeasureSpec(
widthMeasureSpec,
paddingLeft + paddingRight,
layoutParams.width
)
val childHeightMeasureSpec = getChildMeasureSpec(
heightMeasureSpec,
paddingTop + paddingBottom,
layoutParams.height
)
//子view调用了measure才能得出确切的宽高
childView.measure(childWidthMeasureSpec, childHeightMeasureSpec)
//换行
if (lineUseWidth + childView.measuredWidth + paddingLeft + paddingRight > viewGroupWidth) {
//保存每一行view
allViewList.add(lineViews)
lineHeightList.add(lineUseHeight)
viewGroupNeedWidth = Math.max(viewGroupNeedWidth, lineUseWidth)
viewGroupNeedHeight += lineUseHeight + verticalSpace
lineViews = ArrayList()
lineUseWidth = 0;
lineUseHeight = 0;
}
//每一行保存子view
lineViews.add(childView)
//每一行宽度
lineUseWidth += childView.measuredWidth + horizontalSpace
//每一行高度
lineUseHeight = Math.max(lineUseHeight, childView.measuredHeight)
//最后一行特殊处理
if (i == childCount - 1) {
allViewList.add(lineViews)
lineHeightList.add(lineUseHeight)
viewGroupNeedWidth = Math.max(viewGroupNeedWidth, lineUseWidth)
viewGroupNeedHeight += lineUseHeight + verticalSpace
}
}
}
//再测量ViewGroup
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
//真正需要的宽高,
//如果ViewGroup的测量模式是 MeasureSpec.EXACTLY ,就用自己确定的大小,否则用子view测量大小
//需要加水padding,才是真正需要的大小
val realWidth =
if (widthMode == MeasureSpec.EXACTLY)
viewGroupWidth
else
viewGroupNeedWidth + paddingLeft + paddingRight
val realHeight =
if (heightMode == MeasureSpec.EXACTLY)
viewGroupHeight
else
viewGroupNeedHeight + paddingTop + paddingBottom
setMeasuredDimension(realWidth, realHeight)
}
2.FlowLayout的onLayout
override fun onLayout(
changed: Boolean,
l: Int,
t: Int,
r: Int,
b: Int
) {
val lineCount = allViewList.size
//子view布局的位置从ViewGroup内边距的左上角开始
var curLeft = paddingLeft
var curTop = paddingTop
for (i in 0 until lineCount) {
//把每一行子view拿出来排
val lineViews = allViewList.get(i)
for (childView in lineViews) {
val left = curLeft
val top = curTop
val right = left + childView.measuredWidth
val bottom = top + childView.measuredHeight
childView.layout(left, top, right, bottom)
curLeft = right + horizontalSpace
}
//换行
val lineHeight = lineHeightList.get(i)
curTop += lineHeight + verticalSpace
curLeft = paddingLeft
}
}