view系列-ViewGroup入门

547 阅读3分钟

前言

继承系统的viewGroup来实现从而达到自己想要的效果。 也可以参考
自定义View基础 - 最易懂的自定义View原理系列
Android自定义View基础:ViewRoot、DecorView & Window的简介
Android自定义View绘制前的准备:DecorView创建 & 显示
自定义View:Measure过程说明之MeasureSpec类详细讲解
Android自定义View:源码解析通过getWidth() 与 getMeasuredWidth()获取宽高的区别
Android:你知道该如何正确获取View坐标位置的方法吗?
Android:为什么view.post()能保证获取到view的宽高?
Android:手把手带你清晰梳理自定义View的工作全流程
Android 自定义View实战系列 :时间轴 用到recycleview的高阶知识
Android自定义View:你需要一个简单好用、含历史搜索记录的搜索框吗?
Android:手把手教你写一个完整的自定义View Path类的最全面详解 - 自定义View应用系列
Canvas类的最全面详解 - 自定义View应用系列

效果

实现思路

自定义viewGroup控件核心实现方法:onMeasure()、onLayout().

测量流程 onMeasure

    1. 测量自身大小 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    1. 为每个子View计算测量的限制信息 Mode / Size MeasureSpec.getMode(widthMeasureSpec);
    1. 把上一步确定的限制信息,传递给每一个子View,然后子View开始measure
      int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
      int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
      child.measure(childWidthSpec, childHeightSpec);
    1. 获取子View测量完成后的尺寸
    1. ViewGroup根据自身的情况,计算自己的尺寸 涉及到具体场景具体逻辑处理
    1. 保存自身的尺寸 setMeasuredDimension(width,height);

布局流程 onLayout

    1. 遍历子View for
    1. 确定自己的规则
    1. 子View的测量尺寸
    1. left,top,right,bottom
    1. child.layout

Demo1

package com.essence.wanapp;

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

/**
 * 继承系统viewGroup
 */
public class MyViewGroup extends ViewGroup {
    //写死偏移量100
    private int OFFSET = 100;

    public MyViewGroup(Context context) {
        super(context);
    }

    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //测量子布局 里面源码有必要关注
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        int width = 0;
        int height = 0;
        for (int i = 0; i < getChildCount(); i++) {
            //最大宽度
            width = Math.max(width, i * OFFSET + getChildAt(i).getMeasuredWidth());
            //高度
            height = height + getChildAt(i).getMeasuredHeight();
        }
        //设置当前viewgroup宽高
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int childCount = getChildCount();
        int top = 0;
        int left = 0;
        int bottom = 0;
        int right = 0;
        for (int i = 0; i < childCount; i++) {
            View childAt = getChildAt(i);
            left = OFFSET * i;
            right = left + childAt.getMeasuredWidth();
            bottom = top + childAt.getMeasuredHeight();
            //子view摆放布局
            childAt.layout(left, top, right, bottom);
            top = top + childAt.getMeasuredHeight();
        }
    }


}

Demo2 写法差不多,主要是需要考虑当前viewGroup本身的系统提供的宽高跟测量模式

package com.zero.myviewgroupdemo01;

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

public class MyViewGroup extends ViewGroup {

    private static final int OFFSET = 100;//表示缩进的尺寸

    public MyViewGroup(Context context) {
        super(context);
    }

    public MyViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //1. 测量自身
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 2. 为每个子View计算测量的限制信息 Mode / Size
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        //3. 把上一步确定的限制信息,传递给每一个子View,然后子View开始measure
        //自己的尺寸
        int childCount = getChildCount();//子view的个数
        for (int i = 0; i < childCount; i++) {
            View child = getChildAt(i);
            ViewGroup.LayoutParams lp = child.getLayoutParams();
            int childWidthSpec = getChildMeasureSpec(widthMeasureSpec, 0, lp.width);
            int childHeightSpec = getChildMeasureSpec(heightMeasureSpec, 0, lp.height);
            child.measure(childWidthSpec, childHeightSpec);
        }

        int width = 0;
        int height = 0;
        //4. 获取子View测量完成后的尺寸
        //5. ViewGroup根据自身的情况,计算自己的尺寸
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                width = widthSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    int widthAddOffset = i * OFFSET + child.getMeasuredWidth();
                    width = Math.max(width, widthAddOffset);//取最大的宽度
                }
                break;
            default:
                break;
        }

        switch (heightMode){
            case MeasureSpec.EXACTLY:
                height =heightSize;
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                for (int i = 0; i < childCount; i++) {
                    View child = getChildAt(i);
                    height += child.getMeasuredHeight();
                }
                break;
            default:
                break;
        }
        //6. 保存自身的尺寸
        setMeasuredDimension(width,height);

    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

//        1. 遍历子View for
//        2. 确定自己的规则
//        3. 子View的测量尺寸
//        4. left,top,right,bottom
//        6. child.layout
        int left = 0;
        int top = 0;
        int right = 0;
        int bottom = 0;

        int childCount = getChildCount();
        for(int i = 0; i < childCount; i++){
            View child = getChildAt(i);
            left  = i * OFFSET;
            right = left + child.getMeasuredWidth();
            bottom = top + child.getMeasuredHeight();
            child.layout(left,top,right,bottom);
            top += child.getMeasuredHeight();//不考虑padding margin gravity xxx

        }
    }
}