請優先閱讀
Android 之 自定義控件 之 View
http://www.lxweimin.com/p/f49c3cd844dc
介紹
ViewGroup相當于一個放置View的容器,并且我們在寫布局xml的時候,會告訴容器(凡是以layout為開頭的屬性,都是為用于告訴容器的),我們的寬度(layout_width)、高度(layout_height)、對齊方式(layout_gravity)等;當然還有margin等;于是乎,ViewGroup的職能為:給childView計算出建議的寬和高和測量模式 ;決定childView的位置;為什么只是建議的寬和高,而不是直接確定呢,別忘了childView寬和高可以設置為wrap_content,這樣只有childView才能計算出自己的寬和高。
方法介紹
onLayout:用于確定子view在布局中的位置
onMeasure:用于計算和誰在ViewGroup的寬高
generateLayoutParams:生成的LayoutParams 如果我們需要支持margin,需要使用系統的MarginLayoutParams。
View的3種測量模式
上面提到了ViewGroup會為childView指定測量模式,下面簡單介紹下三種測量模式:
EXACTLY:表示設置了精確的值,一般當childView設置其寬、高為精確值、match_parent時,ViewGroup會將其設置為EXACTLY;
AT_MOST:表示子布局被限制在一個最大值內,一般當childView設置其寬、高為wrap_content時,ViewGroup會將其設置為AT_MOST;
UNSPECIFIED:表示子布局想要多大就多大,一般出現在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此種模式比較少見。
如何定義
1.創建自己的ViewGroup
public class MyViewGroup extends ViewGroup {
private Context context;
public MyViewGroup(Context context) {
super(context);
this.context = context;
}
//如果通過XML配置 需要重寫帶屬性的方法
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
}
}
2.重寫onMeasure方法,測量指定容器大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
/**
* 獲得此ViewGroup上級容器為其推薦的寬和高,以及計算模式
*/
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);
int height = 0;
// 遍歷每個子元素 測量子控件的寬高
for (int i = 0; i < getChildCount(); i++)
{
View child = getChildAt(i);
// 測量每一個child的寬和高
measureChild(child, widthMeasureSpec, heightMeasureSpec);
// 得到child的lp
MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// 當前子空間實際占據的寬度
int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin;
// 當前子空間實際占據的高度
int childHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin;
//總高度
height+=childHeight;
}
//確認模式
if(heightMode==MeasureSpec.AT_MOST){
setMeasuredDimension(sizeWidth,cHeight);
}else{
setMeasuredDimension(sizeWidth,sizeHeight);
}
}
因為需要獲Margin所以需要重寫該方法生成我們的LayoutParams,否則會報出 類型轉換異常
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(),attrs);
}
3.onLayout對所有的childView進行布局
/**
* 定位子控件位置 其參數 用于自身對于 外控件的 坐標
* @param changed
* @param l
* @param t
* @param r
* @param b
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int paddingBottom = getPaddingBottom();
int paddingLeft = getPaddingLeft();
int paddingRight = getPaddingRight();
int paddingTop = getPaddingTop();
int childCount = getChildCount();
int cWidth = 0;
cHeight = 0;
for (int i=0;i<childCount;i++){
View childView = getChildAt(i);
// int width = childView.getWidth(); 與 getMeasuredWidth 區別在于
// 都是獲取控件寬度 實現方式不一樣,其次 getWidth在onMeasure 中無法獲取到
int measuredWidth = childView.getMeasuredWidth();
int measuredHeight = childView.getMeasuredHeight();
MarginLayoutParams layoutParams = (MarginLayoutParams) childView.getLayoutParams();
//設置子View所處的位置
//childView.layout(int l, int t, int r, int b) ; //l:左邊 t:上邊 r:右邊 b:下邊 的位置坐標
childView.layout(0,cHeight+layoutParams.topMargin,measuredWidth-paddingRight,cHeight+measuredHeight+layoutParams.topMargin);
cHeight +=layoutParams.topMargin; //追加 magin
cHeight +=measuredHeight; //追加 之前的高度
}
}
至此 試著去寫一個 自己的 自定義ViewGroup
資料來源:http://blog.csdn.net/lmj623565791/article/details/38352503/