自定义viewGroup的重点是如何测量子view,并根据自己的规则去计算子view的位置和尺寸
本文通过在TagView的实现中来探究自定义viewgroup的实现细节
一、最终的效果
二、实现步骤
1、创建TagView
继承ViewGroup
class TagView(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs) {
// 创建list保存子view的坐标属性
val childrenBounds = mutableListOf<Rect>()
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
//遍历子view,测量 计算并保存子view的属性
for ((index, child) in children.withIndex()) {
if (index >= childrenBounds.size) {
childrenBounds.add(Rect())
}
childrenBounds[index].set(l, t, r, b)
}
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//循环 把属性传递给子view
for ((index, child) in children.withIndex()) {
var rect=childrenBounds[index]
child.layout(rect.left, rect.top, rect.right, rect.bottom)
}
}
}
2、测量子view
我们需要用到一个函数
* @param child The child to measure
* @param parentWidthMeasureSpec The width requirements for this view
* @param widthUsed Extra space that has been used up by the parent
* horizontally (possibly by other children of the parent)
* @param parentHeightMeasureSpec The height requirements for this view
* @param heightUsed Extra space that has been used up by the parent
* vertically (possibly by other children of the parent)
*/
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed)
使用这个函数需要重写generateLayoutParams
函数,不然会报错
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}
如效果图,child的x在不换行的情况下是递增的,换行时清零。而y则是在换行时递增
所以需要新建两个变量lineWidthUsed
和heightUsed
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
var lineWidthUsed = 0 //当前行 已经使用的宽度 即下一个child开始绘制的x位置(不换行的情况下)
var heightUsed = 0 //已经使用的高度 即下一个child开始绘制的y位置(不换行的情况下)
//遍历子view,测量 计算并保存子view的属性
for ((index, child) in children.withIndex()) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)
if (index >= childrenBounds.size) {
childrenBounds.add(Rect())
}
childrenBounds[index].set(
lineWidthUsed,
heightUsed,
lineWidthUsed + child.measuredWidth,
heightUsed + child.measuredHeight
)
lineWidthUsed += child.measuredWidth
}
}
上面的代码,并没有换行,也就没有递增heightUsed
3、换行
换行的条件:当前行使用的宽度lineWidthUsed
+child的宽度child.measuredWidth
大于viewGroup的宽度MeasureSpec.getSize(widthMeasureSpec)
heightUsed该递增多少: 上一行的view中的最高高度
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
var lineWidthUsed = 0 //当前行 已经使用的宽度 即下一个child开始绘制的x位置(不换行的情况下)
var heightUsed = 0 //已经使用的高度 即下一个child开始绘制的y位置(不换行的情况下)
var lineMaxHeight = 0//当前行的最高高度
//遍历子view,测量 计算并保存子view的属性
for ((index, child) in children.withIndex()) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)
//判断换行
if (MeasureSpec.getMode(widthMeasureSpec)!=MeasureSpec.UNSPECIFIED&&lineWidthUsed+child.measuredWidth>MeasureSpec.getSize(widthMeasureSpec)){
lineWidthUsed=0//重置 当前行占用
heightUsed+=lineMaxHeight// 递增高度占用
lineMaxHeight=0//重置 当前行的最高高度
}
lineMaxHeight= max(lineMaxHeight,child.measuredHeight)
if (index >= childrenBounds.size) {
childrenBounds.add(Rect())
}
childrenBounds[index].set(
lineWidthUsed,
heightUsed,
lineWidthUsed + child.measuredWidth,
heightUsed + child.measuredHeight
)
lineWidthUsed += child.measuredWidth
}
}
4、计算viewGroup的尺寸
widthUsed
计算出历史的最宽的宽度
var widthUsed=0//最宽的宽度
...
widthUsed= max(widthUsed,lineWidthUsed)
...
val selfWidth = widthUsed
val selfHeight = heightUsed + lineMaxHeight
setMeasuredDimension(selfWidth,selfHeight)
完整代码
TagView
class TagView(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs) {
// 创建list保存子view的坐标属性
val childrenBounds = mutableListOf<Rect>()
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
var lineWidthUsed = 0 //当前行 已经使用的宽度 即下一个child开始绘制的x位置(不换行的情况下)
var heightUsed = 0 //已经使用的高度 即下一个child开始绘制的y位置(不换行的情况下)
var lineMaxHeight = 0//当前行的最高高度
var widthUsed=0//最宽的宽度
//遍历子view,测量 计算并保存子view的属性
for ((index, child) in children.withIndex()) {
measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0)
//判断换行
if (MeasureSpec.getMode(widthMeasureSpec)!=MeasureSpec.UNSPECIFIED&&lineWidthUsed+child.measuredWidth>MeasureSpec.getSize(widthMeasureSpec)){
lineWidthUsed=0//重置 当前行占用
heightUsed+=lineMaxHeight// 递增高度占用
lineMaxHeight=0//重置 当前行的最高高度
}
lineMaxHeight= max(lineMaxHeight,child.measuredHeight)
if (index >= childrenBounds.size) {
childrenBounds.add(Rect())
}
childrenBounds[index].set(
lineWidthUsed,
heightUsed,
lineWidthUsed + child.measuredWidth,
heightUsed + child.measuredHeight
)
lineWidthUsed += child.measuredWidth
widthUsed= max(widthUsed,lineWidthUsed)
}
val selfWidth = widthUsed
val selfHeight = heightUsed + lineMaxHeight
setMeasuredDimension(selfWidth,selfHeight)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
//循环 把属性传递给子view
for ((index, child) in children.withIndex()) {
var rect = childrenBounds[index]
child.layout(rect.left, rect.top, rect.right, rect.bottom)
}
}
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context, attrs)
}
}
ColoredTextView
private val COLORS = intArrayOf(
Color.parseColor("#E91E63"),
Color.parseColor("#673AB7"),
Color.parseColor("#3F51B5"),
Color.parseColor("#2196F3"),
Color.parseColor("#009688"),
Color.parseColor("#FF9800"),
Color.parseColor("#FF5722"),
Color.parseColor("#795548")
)
private val TEXT_SIZES = intArrayOf(16, 18, 20)
private val CORNER_RADIUS = 4.dp
private val X_PADDING = 16.dp.toInt()
private val Y_PADDING = 8.dp.toInt()
class ColoredTextView(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) {
private var paint = Paint(Paint.ANTI_ALIAS_FLAG)
private val random = Random()
init {
setTextColor(Color.WHITE)
textSize = TEXT_SIZES[random.nextInt(3)].toFloat()
paint.color = COLORS[random.nextInt(COLORS.size)]
setPadding(X_PADDING, Y_PADDING, X_PADDING, Y_PADDING)
}
override fun onDraw(canvas: Canvas) {
canvas.drawRoundRect(0f, 0f, width.toFloat(), height.toFloat(), CORNER_RADIUS, CORNER_RADIUS, paint)
super.onDraw(canvas)
}
}
关于measureChildWithMargins
是可以根据自己的需求自定义计算规则的
网友评论