繼承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); } }
效果圖:

