自定义View之自定义ViewGroup

1,318 阅读1分钟

1、自定义ViewGroup时通常只需要关注两个方法分别是onMeasure和onLayout。完整代码如下:

package com.example.view2

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.View
import android.view.ViewGroup

/**
 * 项目名称 View2
 * 创建人
 * 创建时间 10/2/21 10:22 AM
 *
 **/
class MyFirstGroupView:ViewGroup{

    val TAG = "MyFirstGroupView"

    var offset = 100

    constructor(context: Context?) : super(context) {}
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {}


    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        // 1、首选需要根据父容器的MeasureSpec和子View的布局参数测量子View by  on 10/2/21 10:31 AM
        val childCount = getChildCount()
        for(i in (0..childCount-1)){
            val view:View = getChildAt(i)
            view?.let {
                val params = view.layoutParams
                val measureSpecWidthOfView = getChildMeasureSpec(widthMeasureSpec, 0, params.width)
                val measureSpecHeightOfView = getChildMeasureSpec(
                    heightMeasureSpec,
                    0,
                    params.height
                )
                view.measure(measureSpecWidthOfView, measureSpecHeightOfView)
            }

        }
        // 2、计算ViewGroup自己的尺寸 by  on 10/2/21 10:50 AM
        val widthMode = MeasureSpec.getMode(widthMeasureSpec)
        val widthSize = MeasureSpec.getSize(widthMeasureSpec)
        val heightMode = MeasureSpec.getMode(heightMeasureSpec)
        val heightSize = MeasureSpec.getSize(heightMeasureSpec)
        var width = 0
        var height = 0
        when(widthMode){
            MeasureSpec.EXACTLY -> {
                width = widthSize
            }
            MeasureSpec.AT_MOST -> {
                for (i in 0..childCount) {
                    width = Math.max(height, getChildAt(i).measuredWidth + i * offset)
                }
            }
            MeasureSpec.UNSPECIFIED -> {
                width = widthSize
            }
        }

        when(heightMode){
            MeasureSpec.EXACTLY -> {
                height = heightSize
            }
            MeasureSpec.AT_MOST -> {
                for (i in 0..childCount) {
                    height = height + getChildAt(i).measuredWidth
                }
            }
            MeasureSpec.UNSPECIFIED -> {
                height = heightSize
            }
        }
        // 3、保存自身的尺寸 by  on 10/2/21 10:55 AM
        setMeasuredDimension(width, height)

    }


    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        val childCount = childCount
        var heightOffset = 0
        for(i in 0..childCount-1){
            var view = getChildAt(i)
            view.layout(
                offset * i,
                heightOffset,
                view.measuredWidth + offset * i,
                view.measuredHeight + heightOffset
            )
            heightOffset = heightOffset + view.measuredHeight
        }
    }
}

测量时可分为3步:

  • 根据父容器的MeasureSpec和自身的布局参数测量子View的MeasureSpec。
  • 根据父容器的MeasureSpec(其实是SpecMode)和子View的大小确定父容器的大小。
  • 设置父容器的大小

布局时比较简单,其实就是根据预先设定的规则,计算子View的left、top、right和bottom,将其放置到正确的位置

使用

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <com.example.view2.MyFirstGroupView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="我是第一行"></TextView>


        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@mipmap/ic_launcher"/>


        <EditText
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:hint="这是一个EditText"/>

        <Button
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="这是一个button"/>

    </com.example.view2.MyFirstGroupView>

</androidx.constraintlayout.widget.ConstraintLayout>

运行结果如下:

image.png