继承View需要走的流程是:

            1.构造实例化, public ChildView(Context context, @Nullable AttributeSet attrs)

            2.测量自身的高和宽onMeasure-->setMeasuredDimension(宽,高)

            3.onDraw绘制,需要X轴,Y轴

 

继承ViewGroup需要走的流程是:

            1.构造实例化, public ChildView(Context context, @Nullable AttributeSet attrs)

            2.onMeasure测量子控件的高和宽,子控件.measure(宽,高),而自己的高和宽交给父控件去测量,因为我是父控件的子控件

            3.onLayout给子控件排版指定位置

 

布局文件,指定自定义类:

<!-- 继承View 与 继承ViewGroup的初步理解 -->
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:myswitch="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="400dp"
    android:layout_height="500dp">

    <!-- 爷爷类,爷爷类有一个孩子(爸爸类) -->
    <custom.view.upgrade.view_viewgroup_theory.GrandpaViewGroup
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:background="#3300cc"
        android:layout_centerInParent="true"
        > <!--
            虽然android:layout_centerInParent="true"属性可以去居中
            但这是Android RelativeLayout 对爷爷类进行了居中的排版固定位置处理
         -->

        <!-- 爸爸类,爸爸类有一个孩子(孩子类) -->
        <custom.view.upgrade.view_viewgroup_theory.FatherViewGroup
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:background="#00ccff">

            <!-- 孩子类目前不包含孩子 -->
            <custom.view.upgrade.view_viewgroup_theory.ChildView
                android:layout_width="100dp"
                android:layout_height="100dp"
                android:background="#cc3300"/>

        </custom.view.upgrade.view_viewgroup_theory.FatherViewGroup>

    </custom.view.upgrade.view_viewgroup_theory.GrandpaViewGroup>
    

</RelativeLayout>

 

爷爷类,GrandpaViewGroup:

package custom.view.upgrade.view_viewgroup_theory;

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

/**
 * 爷爷类,爷爷类有自己的孩子(爸爸类) ----> 爸爸类有自己的孩子(孩子类)
 */
public class GrandpaViewGroup extends ViewGroup {

    private static final String TAG = GrandpaViewGroup.class.getSimpleName();

    /**
     * 此两个参数的构造方法,用于在xml布局指定初始化,并传入xml中的所有属性
     * @param context
     * @param attrs
     */
    public GrandpaViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // 爸爸类
    private View fatherViewGroup;

    /**
     * 当xml布局完成加载后,调用此方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // 获取子控件(爸爸类)
        fatherViewGroup = getChildAt(0);
    }

    /**
     * 由于当前是ViewGroup所以测量子控件的宽和高
     * ,如果当前是View就是此类自己的高和宽
     * @param widthMeasureSpec
     * @param heightMeasureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 测量子控件(爸爸类)的宽和高
        // 宽和高就是 爸爸类在xml布局中设置的值
        fatherViewGroup.measure(fatherViewGroup.getLayoutParams().width, fatherViewGroup.getLayoutParams().height);

        // 想获取测量后子控件(爸爸类)的高和宽,是无法获取到的,因为子控件(爸爸类)是ViewGroup,拿到测量后的高和宽需要 View-->setMeasuredDimension()
        // 测试下:子控件(爸爸类)的高和宽
        Log.d(TAG, "fatherViewGroup.getMeasuredWidth():" + fatherViewGroup.getMeasuredWidth() +
                         " fatherViewGroup.getMeasuredHeight():" + fatherViewGroup.getMeasuredHeight());
    }

    /**
     * 排版 指定位置,只给子控件指定位置的
     * @param changed 当发生改变
     * @param l 左边线距离左边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
     * @param t 上边线距离上边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
     * @param r 右边线距离左边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
     * @param b 底边线距离上边距离,注意:值是由父控件Layout后,处理了逻辑后传递过来的
     *
     * 父控件必须排版了layout此类的位置,此onLayout方法才会执行,否则不执行
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 测试下:获取自身当前GrandpaViewGroup测量后的宽和高
        int measuredWidth = getMeasuredWidth();
        int measuredHeight = getMeasuredHeight();
        Log.d(TAG, "measuredWidth:" + measuredWidth + " measuredHeight:" + measuredHeight);

        // 给子控件(爸爸类)指定位置
        // Y轴与X轴移动计算一样,X计算:移动X轴方向,得到自身(GrandpaViewGroup爷爷类)宽度的一半 减 子控件(爸爸类)的一半就居中了
        int fatherL = (measuredWidth / 2) - (fatherViewGroup.getLayoutParams().width / 2);
        int fatherT = (measuredHeight / 2) - (fatherViewGroup.getLayoutParams().height / 2);

        // L位置增加了,R位置也需要增加
        int fatherR = fatherViewGroup.getLayoutParams().width + fatherL;
        int fatherB = fatherViewGroup.getLayoutParams().height + fatherT;
        fatherViewGroup.layout(fatherL, fatherT, fatherR, fatherB);
    }

    /**
     * 为什么继承了ViewGroup就不需要onDraw绘制了,因为绘制都是在View中处理
     * 继承了ViewGroup需要做好测量-->排版固定位置就好了,绘制是交给View去处理
     */
}

 

爸爸类,FatherViewGroup:

package custom.view.upgrade.view_viewgroup_theory;

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

/**
 * 爸爸类,爸爸类有自己的孩子
 */
public class FatherViewGroup extends ViewGroup {

    private static final String TAG = FatherViewGroup.class.getSimpleName();

    /**
     * 此两个参数的构造方法,用于在xml布局指定初始化,并传入xml中的所有属性
     * @param context
     * @param attrs
     */
    public FatherViewGroup(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    // 子控件(孩子类)
    private View childView;

    /**
     * 当xml布局完成加载后,调用此方法
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        childView = getChildAt(0);
    }

    private int w;
    private int h;

    /**
     * 测量子控件(孩子类)的高和宽
     * @param widthMeasureSpec 可以转变为模式,也可以转变为父类给当前自己传递过来的宽
     * @param heightMeasureSpec 可以转变为模式,也可以转变为父类给当前自己传递过来的高
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // 可以获取模式
        MeasureSpec.getMode(widthMeasureSpec);
        MeasureSpec.getMode(heightMeasureSpec);

        /**
         * 可以获取父类(爷爷类)在测量方法--->
         * fatherViewGroup.measure(fatherViewGroup.getLayoutParams()
         * .width, fatherViewGroup.getLayoutParams().height);
         * 传递过来的高和宽
         */
        w = MeasureSpec.getSize(widthMeasureSpec);
        h = MeasureSpec.getSize(heightMeasureSpec);
        Log.d(TAG, " w:" + w + " h:" + h);

        // 开始测量子控件(孩子类)
        // 宽高都是孩子类在xml布局设置的宽高
        childView.measure(childView.getLayoutParams().width, childView.getLayoutParams().height);

        // 测试下:子控件(孩子类)的高和宽
        /**
         * 注意:为什么在这里又可以获取到子控件的宽和高呢?
         *      因为子控件是View 并且这个View在测量方法中执行了 setMeasuredDimension(w, h);
         */
        Log.d(TAG, "childView.getMeasuredWidth():" + childView.getMeasuredWidth() +
                " childView.getMeasuredHeight():" + childView.getMeasuredHeight());
    }

    /**
     * 排版 指定位置,只给子控件指定位置的
     * @param changed 当发生改变
     * @param l 左边线距离左边距离
     * @param t 上边线距离上边距离
     * @param r 右边线距离左边距离
     * @param b 底边线距离上边距离
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // 测试下:获取自身当前FatherViewGroup测量后的宽和高
        // 注意:在这里无法获取,还不知道是什么原因!!!,
        // 爷爷类可以获取到是因为爷爷类的父类控件是系统的RelativeLayout
        int measuredWidth = getMeasuredWidth();
        int measuredHeight = getMeasuredHeight();
        Log.d(TAG, "爸爸类 >>> measuredWidth:" + measuredWidth + " measuredHeight:" + measuredHeight);

        // 给子控件(爸爸类)指定位置
        // Y轴与X轴移动计算一样,X计算:移动X轴方向,得到自身(FatherViewGroup爸爸类)宽度的一半 减 子控件(孩子类)的一本就居中了
        int childL = (w / 2) - (childView.getMeasuredWidth() / 2);
        int childT = (h / 2) - (childView.getMeasuredHeight() / 2);

        // L位置增加了,R位置也需要增加
        int childR = childView.getMeasuredWidth() + childL;
        int childB = childView.getMeasuredHeight() + childT;

        childView.layout(childL, childT, childR, childB);
    }

    /**
     * 为什么继承了ViewGroup就不需要onDraw绘制了,因为绘制都是在View中处理
     * 继承了ViewGroup需要做好测量-->排版固定位置就好了,绘制是交给View去处理
     */
}

 

孩子类,ChildView:

package custom.view.upgrade.view_viewgroup_theory;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;

/**
 * 孩子类,孩子类暂时还没有自己的孩子,所有是继承View
 */
public class ChildView extends View {

    private static final String CONTEXT = "child";

    // 创建画笔把文字画到画布中去
    private Paint mPaint;

    private Rect rect;

    /**
     * 此两个参数的构造方法,用于在xml布局指定初始化,并传入xml中的所有属性
     * @param context
     * @param attrs
     */
    public ChildView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);

        mPaint =  new Paint();
        // 画笔防锯齿
        mPaint.setAntiAlias(true);
        // 画笔白色
        mPaint.setColor(Color.WHITE);
        mPaint.setTextSize(40);

        rect = new Rect();
    }

    /**
     * 此高宽是父控件(爸爸类)传递过来的高和宽
     */
    private int w;
    private int h;

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        w = MeasureSpec.getSize(widthMeasureSpec);
        h = MeasureSpec.getSize(heightMeasureSpec);
        setMeasuredDimension(w, h);
    }

    /**
     * 绘制的方法
     * @param canvas 画布
     */
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        // 拿到自身宽度的一半 减 文字宽度的一半

        mPaint.getTextBounds(CONTEXT, 0, CONTEXT.length(), rect);

        int x = (w / 2) - (rect.width() / 2);
        int y = (h / 2) - (rect.height() / 2) + rect.height();
        canvas.drawText(CONTEXT, x, y , mPaint);
    }
}

 

效果图: