记录自定义ViewGroup基本使用

转载自:跳转看原文

前言

在我们的实际应用中, 经常需要用到自定义控件,比如自定义圆形头像,自定义计步器等等。但有时我们不仅需要自定义控件,举个例子,FloatingActionButton 大家都很常用,所以大家也很经常会有一种需求,点击某个 FloatingActionButton 弹出更多 FloatingActionButton ,这个需求的一般思路是写 n 个 button 然后再一个个的去设置动画效果。但这实在是太麻烦了,所以网上有个 FloatingActionButtonMenu 这个开源库,这就是利用到了自定义布局 「ViewGroup」,现在就让我给他家介绍下,如何自定义布局 「layout」。
在这里插入图片描述

难点

相比于自定义 View ,自定义 ViewGroup 的难点在于,子控件位置的确定和布局大小的确定。不像 单个 View 子要花粉好模式,测量好宽度就搞定了,ViewGroup 的长宽根据子 View 的数量和单个的大小变化而变化。这就是最大的坎,所以该如何确定 ViewGroup 的大小呢?

步骤

这里 我为大家设计一个 类似 LinearLayout 线性布局的 ViewGroup 作为范例。

首先,如果是一个 LinearLayout 那么当设置 wrap_content 时,他就会以子空间中最宽的那个为它的宽度。同时在高度方面会是所有子控件高度的总和。所以我们先写两个方法,分别用于测量 ViewGroup 的宽度和高度。

	// 获取最大的子元素的宽度
    private fun getMaxWidth(): Int {
        val count = childCount
        var maxWidth = 0
        for (i in 0 until count) {
            val currentWidth = getChildAt(i).measuredWidth
            if (currentWidth > maxWidth) {
                maxWidth = currentWidth
            }
        }
        return maxWidth
    }

    // 获取所有子元素的高度之和
    private fun getTotalHeight(): Int {
        val count = childCount
        var totalHeight = 0
        for (i in 0 until count) {
            totalHeight += getChildAt(i).measuredHeight
        }
        return totalHeight
    }

对于 ViewGroup 而言我们可以粗略的分为两种模式:固定长宽模式(match_parent),自适应模式(wrap_content),根据这两种模式,就可以对 ViewGroup 的绘制进行划分。这里关于 measureChildren 这个方法,他是用于将所有的子 View 进行测量,这会触发每个子 View 的 onMeasure 函数,但是大家要注意要与 measureChild 区分,measureChild 是对单个 view 进行测量

	/**
     * 对ViewGroup进行
        android:layout_width="wrap_content|match_parent"
        android:layout_height="wrap_content|match_parent"
        的区分,并且通过测量子view,测量整个ViewGroup的宽高
     */
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        // 对子元素进行测量,必不可少
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val width = MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val height = MeasureSpec.getSize(heightMeasureSpec)
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            val groupWidth = getMaxWidth()
            val groupHeight = getTotalHeight()
            setMeasuredDimension(groupWidth, groupHeight)
        } else if (widthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(getMaxWidth(), height)
        } else if (heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(width, getTotalHeight())
        }
    }

重写 onLayout
整完上面这些东西,我们的布局大小七十九已经出来了,然我们在活动的布局文件里面加上它,并添加上几个子 View 然后运行一下,先看看效果:

<com.lan8.employee.widget.group.MyLinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:background="@color/color_F62D4C">

                <LinearLayout
                    android:layout_width="80pt"
                    android:layout_height="80pt"
                    android:background="@color/color_000000"
                    android:orientation="vertical" />

                <LinearLayout
                    android:layout_width="50pt"
                    android:layout_height="50pt"
                    android:background="@color/color_000000"
                    android:orientation="vertical" />

                <LinearLayout
                    android:layout_width="200pt"
                    android:layout_height="30pt"
                    android:background="@color/color_000000"
                    android:orientation="vertical" />

                <LinearLayout
                    android:layout_width="100pt"
                    android:layout_height="30pt"
                    android:background="@color/color_000000"
                    android:orientation="vertical" />

            </com.lan8.employee.widget.group.MyLinearLayout>

运行效果如下:
在这里插入图片描述

我们看见布局出来了,大小好像也没啥问题,但是子 View 呢??! 这么没看见子 View 在看看代码,系统之前然我们重写的 onLayout() 还是空着的呀!!也就是说,子 View 的大小和位置根本就还没有进行过设定!让我们来重写下 onLayout() 方法。

	/**
     * 对子元素进行排列摆放,不摆放怎么可能显示出来呢
     */
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        val count = childCount
        var currentHeight = 0
        for (i in 0 until count) {
            val view = getChildAt(i)
            val width = view.measuredWidth
            val height = view.measuredHeight
            view.layout(l, currentHeight, l + width, currentHeight + height)
            currentHeight += height
        }
    }

再运行一下看看:
在这里插入图片描述

仅供学习记录

源代码

class MyLinearLayout : ViewGroup {

    constructor(context: Context) : super(context) {
        init(null, 0)
    }

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
        init(attrs, 0)
    }

    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(
        context,
        attrs,
        defStyle
    ) {
        init(attrs, defStyle)
    }

    private fun init(attrs: AttributeSet?, defStyle: Int) {

    }

    /**
     * 对ViewGroup进行
        android:layout_width="wrap_content|match_parent"
        android:layout_height="wrap_content|match_parent"
        的区分,并且通过测量子view,测量整个ViewGroup的宽高
     */
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        // 对子元素进行测量,必不可少
        measureChildren(widthMeasureSpec, heightMeasureSpec)
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val width = MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val height = MeasureSpec.getSize(heightMeasureSpec)
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            val groupWidth = getMaxWidth()
            val groupHeight = getTotalHeight()
            setMeasuredDimension(groupWidth, groupHeight)
        } else if (widthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(getMaxWidth(), height)
        } else if (heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(width, getTotalHeight())
        }
    }

    /**
     * 对子元素进行排列摆放,不摆放怎么可能显示出来呢
     */
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        val count = childCount
        var currentHeight = 0
        for (i in 0 until count) {
            val view = getChildAt(i)
            val width = view.measuredWidth
            val height = view.measuredHeight
            view.layout(l, currentHeight, l + width, currentHeight + height)
            currentHeight += height
        }
    }

    // 获取最大的子元素的宽度
    private fun getMaxWidth(): Int {
        val count = childCount
        var maxWidth = 0
        for (i in 0 until count) {
            val currentWidth = getChildAt(i).measuredWidth
            if (currentWidth > maxWidth) {
                maxWidth = currentWidth
            }
        }
        return maxWidth
    }

    // 获取所有子元素的高度之和
    private fun getTotalHeight(): Int {
        val count = childCount
        var totalHeight = 0
        for (i in 0 until count) {
            totalHeight += getChildAt(i).measuredHeight
        }
        return totalHeight
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值