继承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); } }
效果图: