美文网首页
Android自定义View(19) 《自定义ViewGroup

Android自定义View(19) 《自定义ViewGroup

作者: 非典型程序猿 | 来源:发表于2021-10-06 17:52 被阅读0次

    概述

    在日常的开发中我们有时候会需要按需求自己定制一个ViewGroup,今天就用一个简单的例子说一下自定义ViewGroup的大致流程

    运行效果

    customLayout.png

    这也就是大家常说的流式布局,这种布局一般在搜索功能或历史记录的展现中常用,今天就用这个例子来记录自定义ViewGroup的步骤

    整体思路

    我们知道,在自定义View中绘制大体分为3步

    • 1.onMeasure()函数 测量自身的大小
    • 2.onLayout() 定位自身的位置
    • 3.onDraw() 绘制自身的内容
      而在自定义ViewGroup中,它更重要的是展示多个子控件,自身的一些界面上的展示其实是次要的,所以我们在自定义ViewGroup中重点关心一下两个函数
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }
    

    这个onMeasure主要是负责在绘制前的一次测量,这次测量会给后面的布局作为参考,要注意,只是参考,最终的控件大小和位置是需要重新确认的

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    }
    

    onLayout函数主要是负责具体的子控件布局,通过子控件调用layout来放置在具体的位置上,最终的显示大小也是这里确认的,而不是在onMeasure()中确认的

    MeasureSpec类用法

    • 1.MeasureSpec.UNSPECIFIED 父控件不对子控件增加任何约束
    • 2.MeasureSpec.EXACTLY 父控件会约束子控件,子控件的大小在父控件内部
    • 3.MeasureSpec.AT_MOST 子控件最多达到多少指定大小
      我们需要在onMeasure()中测量出父控件的大小,同时也要测量出子控件的大小,这里测量的代码是这样的
      override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
            var measureWidth = MeasureSpec.getSize(widthMeasureSpec)
            var measureHeight = MeasureSpec.getSize(heightMeasureSpec)
            var measureWidthMode = MeasureSpec.getMode(widthMeasureSpec)
            var measureHeightMode = MeasureSpec.getMode(heightMeasureSpec)
    
            Log.d(tag,"onMeasure measureWidth +$measureWidth +\n " +
                    "measureHeight +$measureHeight +\n" +
                    "measureWidthMode +$measureWidthMode +\n" +
                    "measureHeightMode +$measureHeightMode +\n")
    
            var w = 0
            var h = 0
            if (measureWidthMode==MeasureSpec.EXACTLY){
                w=measureWidth
            }
            if (measureHeightMode==MeasureSpec.EXACTLY){
                h=measureHeight
            }
            for (index in 0 until childCount){
                var child = getChildAt(index)
                measureChild(child,widthMeasureSpec,heightMeasureSpec)
            }
            setMeasuredDimension(w,h)
        }
    

    在onLayout()函数中布局子控件,由于时间关系,这个例子我没有处理当子控件高度不一致的时候的显示,因为这涉及到更复杂的位置处理,这里我们只按统一高度处理作为示例,其他情况都可以举一反三啦

        override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
            Log.d(tag,"onLayout ")
            var w = 0
            var h = 0
            for (index in 0 until childCount){
                var child = getChildAt(index)
                var top = 0
                var left = 0
                var right = 0
                var bottom = 0
                // 获取margin
                var lp = child.layoutParams as MarginLayoutParams
                var childWith = child.measuredWidth+lp.leftMargin+lp.rightMargin
                var childHeight = child.measuredHeight+lp.topMargin+lp.bottomMargin
                if (w+childWith<=measuredWidth){
                    left = w+lp.leftMargin
                    top = h+lp.topMargin
                    right = w+childWith-lp.rightMargin
                    bottom = h+childHeight-lp.bottomMargin
                    w+=childWith
                }else{
                    w=0
                    h+=childHeight
                    left = w+lp.leftMargin
                    top = h+lp.topMargin
                    right = w+childWith-lp.rightMargin
                    bottom = h+childHeight-lp.bottomMargin
                    w+=childWith
                }
                child.layout(left,top,right,bottom)
            }
    
        }
    
    

    CustomLayout完整源码

    package com.tx.txcustomview.view
    
    import android.content.Context
    import android.graphics.Canvas
    import android.util.AttributeSet
    import android.util.Log
    import android.view.ViewGroup
    
    /**
     * create by xu.tian
     * @date 2021/10/6
     */
    class CustomLayout(context: Context?, attrs: AttributeSet?) : ViewGroup(context, attrs) {
        private val tag = "CustomLayout"
        override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec)
            var measureWidth = MeasureSpec.getSize(widthMeasureSpec)
            var measureHeight = MeasureSpec.getSize(heightMeasureSpec)
            var measureWidthMode = MeasureSpec.getMode(widthMeasureSpec)
            var measureHeightMode = MeasureSpec.getMode(heightMeasureSpec)
    
            Log.d(tag,"onMeasure measureWidth +$measureWidth +\n " +
                    "measureHeight +$measureHeight +\n" +
                    "measureWidthMode +$measureWidthMode +\n" +
                    "measureHeightMode +$measureHeightMode +\n")
    
            var w = 0
            var h = 0
            if (measureWidthMode==MeasureSpec.EXACTLY){
                w=measureWidth
            }
            if (measureHeightMode==MeasureSpec.EXACTLY){
                h=measureHeight
            }
            for (index in 0 until childCount){
                var child = getChildAt(index)
                measureChild(child,widthMeasureSpec,heightMeasureSpec)
            }
            setMeasuredDimension(w,h)
        }
    
        override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
            super.onSizeChanged(w, h, oldw, oldh)
            Log.d(tag,"w $w h $h oldw $oldw oldh $oldh ")
        }
    
    
        override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
            Log.d(tag,"onLayout ")
            var w = 0
            var h = 0
            for (index in 0 until childCount){
                var child = getChildAt(index)
                var top = 0
                var left = 0
                var right = 0
                var bottom = 0
                // 获取margin
                var lp = child.layoutParams as MarginLayoutParams
                var childWith = child.measuredWidth+lp.leftMargin+lp.rightMargin
                var childHeight = child.measuredHeight+lp.topMargin+lp.bottomMargin
                if (w+childWith<=measuredWidth){
                    left = w+lp.leftMargin
                    top = h+lp.topMargin
                    right = w+childWith-lp.rightMargin
                    bottom = h+childHeight-lp.bottomMargin
                    w+=childWith
                }else{
                    w=0
                    h+=childHeight
                    left = w+lp.leftMargin
                    top = h+lp.topMargin
                    right = w+childWith-lp.rightMargin
                    bottom = h+childHeight-lp.bottomMargin
                    w+=childWith
                }
                child.layout(left,top,right,bottom)
            }
    
        }
        override fun onDraw(canvas: Canvas?) {
            super.onDraw(canvas)
            Log.d(tag,"onDraw")
        }
    
        /**
         * 获取margin必须重写以下3个方法
         */
        override fun generateLayoutParams(p: LayoutParams?): LayoutParams {
            return MarginLayoutParams(p)
        }
    
        override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
            return MarginLayoutParams(context,attrs)
        }
    
        override fun generateDefaultLayoutParams(): LayoutParams {
            return MarginLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT)
        }
    
    }
    

    布局文件

    <?xml version="1.0" encoding="utf-8"?>
    <com.tx.txcustomview.view.CustomLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white">
        <TextView
            style="@style/text_style"
            android:text="杨幂" />
    
        <TextView
            style="@style/text_style"
            android:text="薛之谦" />
    
        <TextView
            style="@style/text_style"
            android:text="迪丽热巴" />
    
        <TextView
            style="@style/text_style"
            android:text="哈尼克孜" />
    
        <TextView
            style="@style/text_style"
            android:text="毛不易" />
    
        <TextView
            style="@style/text_style"
            android:text="易烊千玺" />
    
        <TextView
            style="@style/text_style"
            android:text="林俊杰" />
    
        <TextView
            style="@style/text_style"
            android:text="杨超越" />
    
        <TextView
            style="@style/text_style"
            android:text="杨洋" />
    
        <TextView
            style="@style/text_style"
            android:text="周杰伦" />
    
        <TextView
            style="@style/text_style"
            android:text="王俊凯" />
    
        <TextView
            style="@style/text_style"
            android:text="杨幂" />
    
        <TextView
            style="@style/text_style"
            android:text="薛之谦" />
    
        <TextView
            style="@style/text_style"
            android:text="迪丽热巴" />
    
        <TextView
            style="@style/text_style"
            android:text="哈尼克孜" />
    
        <TextView
            style="@style/text_style"
            android:text="毛不易" />
    
        <TextView
            style="@style/text_style"
            android:text="易烊千玺" />
    
        <TextView
            style="@style/text_style"
            android:text="林俊杰" />
    
        <TextView
            style="@style/text_style"
            android:text="杨超越" />
    
        <TextView
            style="@style/text_style"
            android:text="杨洋" />
    
        <TextView
            style="@style/text_style"
            android:text="周杰伦" />
    
        <TextView
            style="@style/text_style"
            android:text="王俊凯" />
    </com.tx.txcustomview.view.CustomLayout>
    

    Style

    <style name="text_style">
            <item name="android:layout_width">wrap_content</item>
            <item name="android:layout_height">wrap_content</item>
            <item name="android:textSize">20sp</item>
            <item name="android:textColor">@color/white</item>
            <item name="android:layout_margin">5dp</item>
            <item name="android:gravity">center</item>
            <item name="android:paddingTop">5dp</item>
            <item name="android:paddingBottom">5dp</item>
            <item name="android:paddingLeft">10dp</item>
            <item name="android:paddingRight">10dp</item>
            <item name="android:background">@drawable/text_shape_bg</item>
        </style>
    

    背景文件 text_shape_bg.xml

    <?xml version="1.0" encoding="utf-8"?>
    <shape xmlns:android="http://schemas.android.com/apk/res/android">
        <corners android:radius="8dp"/>
        <stroke android:color="#777173"/>
        <solid android:color="#BDB9B9"/>
    </shape>
    

    总结

    需要注意的是,如果需要获取子控件的margin,那么就要复写以下三个函数

     /**
         * 获取margin必须重写以下3个方法
         */
        override fun generateLayoutParams(p: LayoutParams?): LayoutParams {
            return MarginLayoutParams(p)
        }
    
        override fun generateLayoutParams(attrs: AttributeSet?): LayoutParams {
            return MarginLayoutParams(context,attrs)
        }
    
        override fun generateDefaultLayoutParams(): LayoutParams {
            return MarginLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT)
        }
    

    具体的使用在上面的源码里也有体现了,今天就先写到这,Respect ~

    相关文章

      网友评论

          本文标题:Android自定义View(19) 《自定义ViewGroup

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