Kotlin 自定义 标签viewgroup
android 对于kotlin语言做了强调转移过后,kotlin逐渐取代java,成为Android开发语言中极为重要的语言之一。涉及到kotlin语法的相关知识我就不多说了,今天就项目需求,自定义一个viewgroup作标签视图来使用进项目中去。
1、开写继承constructor
一般在java语言中,constructor直接在继承viewgroup后会报错,然后根据自定义快捷键,默认为alt+enter【博主是用的eclipse 的keymap所以采用的 ctrl+/】就提示出来让你选择了。
点击图示选项.png
点击上图所示,其就会进入选择项:
选择条目.png
选择1、2、3行进行复写,然后就写其他自定义逻辑就完了。然而到了kotlin它的constructor很特别,可以根据语法如下书写:
class Test constructor (private val context:Context) : ViewGroup(context){}
如此这般如何复写三个constructor呢,实际上也很简单:
实现复写constructor.png
2、核心两方法思路与实现:
2.1、onMesure()
根据子控件来计算父控件的大小:
/**
* 计算子控件大小进行自动换行处理
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
val sizeWidth = MeasureSpec.getSize(widthMeasureSpec)
val sizeHeight = MeasureSpec.getSize(heightMeasureSpec)
val modeWidth = MeasureSpec.getMode(widthMeasureSpec)
val modeHeight = MeasureSpec.getMode(heightMeasureSpec)
//初始化父控件大小
var resultWidth = 0
var resultHeight = 0
// 初始化行控件大小
var itemWidth = 0
var itemHeight = 0
for (i in 0 until childCount) { // 遍历 所有的子元素
val child = getChildAt(i)
val layoutParams = child.layoutParams as MarginLayoutParams
measureChild(child, widthMeasureSpec, heightMeasureSpec) // 先测量
//计算所有的子控件宽高
val childWidth = child.measuredWidth
val childHeight = child.measuredHeight
// 通过margin计算所有子控件实际加上margin值的大小
val realWidth = childWidth + layoutParams.leftMargin + layoutParams.rightMargin
val realHeight = childHeight + layoutParams.topMargin + layoutParams.bottomMargin
if (sizeWidth < (itemWidth + realWidth)) {//换行
resultWidth = Math.max(realWidth, itemWidth)
resultHeight += realHeight
itemHeight = realHeight
itemWidth = realWidth
} else { // 添加
itemWidth += realWidth
itemHeight = Math.max(realHeight, itemHeight)
}
// 最后一行不换行
if (i == childCount - 1) {
resultWidth = Math.max(realWidth, itemWidth)
resultHeight += itemHeight
}
// 通过判断本自定义控件width||height 的属性是否为warp_content来进行给此view赋值,如果为march_parent或者其他属性,则使用其他属性定义的宽高值
// 如果仅为wrap_content则使用计算后的宽高给父控件赋值
setMeasuredDimension(if (modeWidth == View.MeasureSpec.EXACTLY) sizeWidth else resultWidth,
if (modeHeight == View.MeasureSpec.EXACTLY) sizeHeight else resultHeight)
}
}
通过以上方法我们控制了子view的显示,同时让我们现在的viewgroup的宽高在程序中可以进行控制处理,不会让视图错乱。
2.2、onLayout()
/**
* 控制子view所在的位置
*/
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
// 初始化子控件位置
var pointWidth = 0
var pointHeight = 0
(0 until childCount)
.asSequence() //序列化
.map { getChildAt(it) }//开始遍历子控件
.filter { it.visibility != View.GONE } // 过滤view为gone的控件
.forEach {
// 获取子控件的测量宽高
val childHeight = it.measuredHeight
val childWidth = it.measuredWidth
// 使用margin的params作为定位器
val layoutParams = it.layoutParams as MarginLayoutParams
// 判断是否换行。
if (pointWidth + childWidth + layoutParams.leftMargin + layoutParams.rightMargin > width) {
pointHeight += childHeight + layoutParams.topMargin + layoutParams.bottomMargin
pointWidth = 0
}
// 计算控件绘图定位
val top = layoutParams.topMargin + pointHeight
val bottom = layoutParams.bottomMargin + pointHeight + childHeight
val left = layoutParams.leftMargin + pointWidth
val right = layoutParams.rightMargin + pointWidth + childWidth
it.layout(left, top, right, bottom)
// 通过view的tag来显示view的实际变化
val tag = it.tag as CateGroyBean
if (tag.isChoose) it.setBackgroundResource(R.drawable.shape_selected)
else it.setBackgroundResource(R.drawable.shape_no_select)
//记录最终view的位置
pointWidth += layoutParams.leftMargin + childWidth + layoutParams.rightMargin
}
}
通过onLayout方法记住了子view的位置,直接底层绘图处理定位每个子元素的位置,并可让子view通过自己设定的方式进行显示。
3、控制子view的点击与显示
在使用angular过后明白了一点,数据绑定耐前端开发人员最核心最核心的思想,于是我们这里可以借鉴angular的数据绑定思想来控制我们的view的高亮显示:
赋值多种操作方式.png
通过数据绑定方式来控制点击视图变化.png
这里结合前面的onLayout方法,将数据的bean作为一个tag赋值给对应的子view上,于是每个子view拥有了此数据的属性,我们可以根据控制每个子view的点击状态改变绑定的数据,从而控制了整个视图的变化。
4、屏幕适配
在这里我自定义了几种属性:
几种自定义属性.png
由于本身根据子控件进行测量显示,子控件只需要控制textview的textsize就可以实现不同屏幕的适配了,这里我封装了一个textview屏幕适配的类:DimenUtil。
DimenUtil部分代码.pngDimenUtil 根据屏幕宽度的百分比来设定本textview的字体大小,textview字体可以看作是正方形模块,只要限定住了百分比就可以控制了它的适配,它也采取了单例的模式进行使用,无需额外的操作,使用也非常简单。
说明:推荐使用默认配置达到最好的适配效果
5、最终效果
普通选定效果.gif单选效果.gif
多选效果.gif