开源工具技巧kotlinAndroid

Kotlin 自定义 标签viewgroup

2018-03-05  本文已影响147人  stormKid

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 根据屏幕宽度的百分比来设定本textview的字体大小,textview字体可以看作是正方形模块,只要限定住了百分比就可以控制了它的适配,它也采取了单例的模式进行使用,无需额外的操作,使用也非常简单。

DimenUtil部分代码.png

说明:推荐使用默认配置达到最好的适配效果

5、最终效果

普通选定效果.gif
单选效果.gif
多选效果.gif

查看使用方式及例子请点击此处

上一篇下一篇

猜你喜欢

热点阅读