繼承View須要走的流程是:android
1.構造實例化, public ChildView(Context context, @Nullable AttributeSet attrs)canvas
2.測量自身的高和寬onMeasure-->setMeasuredDimension(寬,高)ide
3.onDraw繪製,須要X軸,Y軸佈局
繼承ViewGroup須要走的流程是:測試
1.構造實例化, public ChildView(Context context, @Nullable AttributeSet attrs)spa
2.onMeasure測量子控件的高和寬,子控件.measure(寬,高),而本身的高和寬交給父控件去測量,由於我是父控件的子控件code
3.onLayout給子控件排版指定位置xml
佈局文件,指定自定義類:blog
<!-- 繼承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); } }
效果圖: