關於View的基本知識和如何自定義View請見html
自定義View - http://www.javashuo.com/article/p-bnjbkrpi-bo.htmljava
ViewGroup至關於一個放置View的容器,而且咱們在寫佈局xml的時候,會告訴容器(凡是以layout爲開頭的屬性,都是用於告訴容器的),咱們的寬度layout_width、高度layout_height、對齊方式layout_gravity、內邊距layout_maring等。因此ViewGroup的智能爲:給childView計算出建議的寬和高的測量模式;決定childView的位置。(由於childView寬和高能夠設置爲wrap_content,這樣只有childView才能計算出本身的寬和高,因此這裏給出的建議寬和高)android
View的職責就是根據測量模式和ViewGroup給出的建議的寬和高,計算出本身的寬和高;同事還有個更重要的職責:在ViewGroup爲其指定的區域內繪製本身的形態。canvas
每一個ViewGroup須要指定一個LayoutParams,用於肯定它的childView支持哪些屬性。不一樣的ViewGroup指定的LayoutParams是不一樣的,包含的屬性也不盡相同。ide
EXACTLY、AT_MOST、UNSPECIFIED佈局
上面敘述了ViewGroup和View的職責,下面從API角度進行淺析。this
View須要根據ViewGroup傳入的測量值和模式,對本身寬高進行肯定(onMeasure中完成),而後在onDraw中完成對本身的繪製。spa
ViewGroup須要給View傳入view的測量值和模式(onMeasure中完成),並且對於此ViewGroup的父佈局,本身也須要在onMeasure中完成對本身寬和高的肯定。此外,須要在onLayout中完成對其childView的位置的指定。.net
定義一個ViewGroup,內部能夠傳入0到4個childView,分別依次顯示在左上角、右上角、左下角、右下角。同時在中央繪製一個圓。code
/** * Created by xupeng on 16/12/26. */ public class MyCustomViewGroup extends ViewGroup { private Paint mPaint; public MyCustomViewGroup(Context context) { super(context); init(); } public MyCustomViewGroup(Context context, AttributeSet attrs) { // super(context, attrs); this(context); } private void init() { setWillNotDraw(false); mPaint = new Paint(); mPaint.setColor(Color.RED); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new MarginLayoutParams(getContext(), attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { /** * 得到此ViewGroup上級容器爲其推薦的寬和高,以及計算模式 */ int widthSize = MeasureSpec.getSize(widthMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); // 計算出全部的children的寬和高 measureChildren(widthMeasureSpec, heightMeasureSpec); /** * 記錄若是是wrap_content設置的寬和高 */ int width = 0; int height = 0; int cCount = getChildCount(); int cWidth = 0; int cHeight = 0; MarginLayoutParams cParams = null; // 用於計算左邊兩個childView的高度 int lHeight = 0; // 用於計算右邊兩個childView的高度,最終高度取二者之間大值 int rHeight = 0; // 用於計算上邊兩個childView的寬度 int tWidth = 0; // 用於計算下邊兩個childView的寬度,最重寬度取二者之間大值 int bWidth = 0; /** * 根據childView計算得出寬和高,以及設置的margin計算容器的寬和高,主要用於容器是wrap_content時 */ for (int i = 0; i < cCount; i++) { View childView = getChildAt(i); cWidth = childView.getMeasuredWidth(); cHeight = childView.getMeasuredHeight(); cParams = (MarginLayoutParams) childView.getLayoutParams(); // 上面兩個childView if (i == 0 || i == 1) { tWidth += cWidth + cParams.leftMargin + cParams.rightMargin; } // 下面兩個childView if (i == 2 || i == 3) { bWidth += cWidth + cParams.leftMargin + cParams.rightMargin; } if (i == 0 || i == 2) { lHeight += cHeight + cParams.topMargin + cParams.bottomMargin; } if (i == 1 || i == 3) { rHeight += cHeight + cParams.topMargin + cParams.bottomMargin; } } width = Math.max(tWidth, bWidth); height = Math.max(lHeight, rHeight); setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize : height); // super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int cCount = getChildCount(); int cWidth = 0; int cHeight = 0; MarginLayoutParams cParams = null; /** * 遍歷全部childView根據其寬和高,以及margin進行佈局 */ for (int i = 0; i < cCount; i++) { View childView = getChildAt(i); cWidth = childView.getMeasuredWidth(); cHeight = childView.getMeasuredHeight(); cParams = (MarginLayoutParams) childView.getLayoutParams(); int cl = 0, ct = 0, cb = 0, cr = 0; switch (i) { case 0: cl = cParams.leftMargin; ct = cParams.topMargin; break; case 1: cl = getWidth() - cWidth - cParams.rightMargin; ct = cParams.topMargin; break; case 2: cl = cParams.leftMargin; ct = getHeight() - cHeight - cParams.bottomMargin; break; case 3: cl = getWidth() - cWidth - cParams.rightMargin; ct = getHeight() - cHeight - cParams.bottomMargin; break; } cr = cl + cWidth; cb = cHeight + ct; childView.layout(cl, ct, cr, cb); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int measureWidth = getMeasuredWidth(); requestLayout(); int measureWidthAndState = getMeasuredWidthAndState(); canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, 100 , mPaint); // invalidate(); }
<?xml version="1.0" encoding="utf-8"?> <com.gnepux.sdkusage.widget.MyCustomViewGroup xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_custom_view_group" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#ffff00" tools:context="com.gnepux.sdkusage.activity.CustomViewGroupActivity"> <com.gnepux.sdkusage.widget.MyCustomView android:layout_width="50dp" android:layout_height="50dp" android:text="1" android:gravity="center" android:textColor="#ffffff" android:background="#ff0000"/> <com.gnepux.sdkusage.widget.MyCustomView android:layout_width="50dp" android:layout_height="50dp" android:text="2" android:gravity="center" android:textColor="#ffffff" android:background="#00ff00"/> <com.gnepux.sdkusage.widget.MyCustomView android:layout_width="50dp" android:layout_height="50dp" android:text="3" android:gravity="center" android:textColor="#ffffff" android:background="#0000ff"/> <com.gnepux.sdkusage.widget.MyCustomView android:layout_width="50dp" android:layout_height="50dp" android:text="4" android:gravity="center" android:textColor="#ffffff" android:background="#ff00ff"/> </com.gnepux.sdkusage.widget.MyCustomViewGroup>
View原理簡介
http://blog.csdn.net/u011733020/article/details/50849475
幫你搞定Android自定義View
http://www.jianshu.com/p/84cee705b0d3
Android手把手教您自定義ViewGroup
http://blog.csdn.net/lmj623565791/article/details/38339817/
爲何自定義ViewGroup ondraw方法不會被調用
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1014/1765.html
關於getMeasuredHeight和getHeight區別(getMeasureHeight是xml或代碼中制定的測量高度,setMeasuredHeight; getHeight是實際顯示出來的高度,經過view.layout()方即可以改變其值)