美文网首页开源工具技巧kotlinAndroid
Kotlin 自定义 标签viewgroup

Kotlin 自定义 标签viewgroup

作者: stormKid | 来源:发表于2018-03-05 18:15 被阅读147次

    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

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

    相关文章

      网友评论

      本文标题:Kotlin 自定义 标签viewgroup

      本文链接:https://www.haomeiwen.com/subject/ymmhfftx.html