最近想加强一下自定义view方面的学习,正好也在学习Kotlin,所以就尝试着用Kotlin写一下简单的自定义view
这次的猪脚是简单的流式布局
首先创建一个kotlin类,继承自viewgroup
class TagView (context: Context): ViewGroup(context)
接着为了让子控件能获取距离父控件的margin属性,我们需要重写以下方法:
override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
return MarginLayoutParams(context,attrs)
}
接下来就是重头戏,重写onMeasure和onLayout方法。首先是onMeasure方法:
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
//建议的长宽,但还不是最终的,由模式决定
val suggestWidth = MeasureSpec.getSize(widthMeasureSpec)
val suggestHeight = MeasureSpec.getSize(heightMeasureSpec)
//测量子view的尺寸信息
measureChildren(widthMeasureSpec,heightMeasureSpec)
/**
* 主要处理 width 和 height AT_MOST 测量模式下的情况
* 在 width 方面,TagView 中的子元素要求出所有行中的宽度最大的一行,并且这个数值
* 不能大于 parent 给出的建议宽度
* */
var cWidth: Int//子控件的宽
var cHeight: Int//子控件的高
var lineWidth: Int = paddingLeft + paddingRight
var lineMaxWidth: Int = lineWidth
var lineHeight: Int = paddingTop + paddingBottom
var singleLineHeight: Int = 0;
var childParams: MarginLayoutParams
var resultWidth: Int = suggestWidth//最后的测量宽度
var resultHeight: Int = suggestHeight//最后的测量高度
for (index in 0 until childCount){
val view = getChildAt(index)
childParams = view.layoutParams as MarginLayoutParams//强转
cWidth = view.measuredWidth + childParams.leftMargin + childParams.rightMargin
cHeight = view.measuredHeight + childParams.topMargin + childParams.bottomMargin
/**如果后者不判断的话,当出现widthMode为MeasureSpec.EXACTLY,
* 而heightMode == MeasureSpec.AT_MOST时
* 会出现最后设置的高度为零的情况,导致界面不显示
* */
if (widthMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.AT_MOST){
if(lineWidth + cWidth > suggestWidth){//需要进行换行
lineWidth = cWidth + paddingLeft + paddingRight//新的一行的初始宽度
lineHeight += singleLineHeight//增加了一行的高度(只记录了满行的高度)
//换行后重置新单行高度
singleLineHeight = cHeight
}else{
lineWidth += cWidth
if(lineWidth > lineMaxWidth){
lineMaxWidth = lineWidth
}
}
if(singleLineHeight < cHeight){//目的是设置单行的高度为这一行中最高的childView
singleLineHeight = cHeight
}
if(index == childCount - 1){//当来到最后一个childview的时候,不管这行有没有满,都要加上这一行的高度
lineHeight += singleLineHeight
}
}
}
if(widthMode == MeasureSpec.AT_MOST){
Log.d("----","lineMaxWidth:"+lineMaxWidth)
resultWidth = lineMaxWidth
}
if(heightMode == MeasureSpec.AT_MOST){
Log.d("----","lineheight:"+lineHeight)
resultHeight = lineHeight
if(resultHeight > suggestHeight){
resultHeight = suggestHeight
}
}
setMeasuredDimension(resultWidth,resultHeight)
最后是onDraw方法:
var left: Int = paddingLeft
var right: Int = width - paddingRight
var top: Int = paddingTop
var bottom: Int = height - paddingBottom
var singleLineHeight: Int = 0
var lp: MarginLayoutParams
var childWidth: Int
var childHeight: Int
for (index in 0 until childCount){
var view = getChildAt(index)
lp = view.layoutParams as MarginLayoutParams
childWidth = view.measuredWidth + lp.leftMargin + lp.rightMargin
childHeight = view.measuredHeight + lp.topMargin + lp.bottomMargin
if(left + childWidth > right){//该换行了
left = paddingLeft//新起点都是这个
top += singleLineHeight
singleLineHeight = childHeight
}else{
if(singleLineHeight < childHeight){
singleLineHeight = childHeight
}
}
if(top >= bottom){
break
}
//绘制子view的位置
view.layout(left + lp.leftMargin,top + lp.topMargin,left + childWidth,top + childHeight)
left += childWidth//绘制完一个view后,left的位置显然要增加上刚刚绘制的view的宽度
}
我喜欢写注释,所以通过注释已经可以看得懂了。这里值得注意的一点是,一开始
在for循环处我写的是
for(indext in 0 .. childCount-1){
}
然后我看有提示说 .. 可以替换成until,我就换了,变成下面这样
for(indext in 0 until childCount-1){
}
后面发现界面怎么都出不来,最后发现 ..和until还是有区别的。例如 0..10表示的是[0,10],
而0 until 10 表示的是 [0,10),含头不含尾。
最后这个流式布局的界面效果就不贴了,因为很丑。。。
具体的代码在我的github上:https://github.com/ckwcc/KotlinWidget