View的職責,根據測量模式和ViewGroup給出的建議的寬和高,計算出自己的寬和高;同時還有個更重要的職責是:在ViewGroup爲其指定的區域內繪製自己的形態。
ViewGroup和LayoutParams之間的關係?
大 家可以回憶一下,當在LinearLayout中寫childView的時候,可以寫layout_gravity,layout_weight屬性;在 RelativeLayout中的childView有layout_centerInParent屬性,卻沒有 layout_gravity,layout_weight,這是爲什麼呢?這是因爲每個ViewGroup需要指定一個LayoutParams,用於 確定支持childView支持哪些屬性,比如LinearLayout指定LinearLayout.LayoutParams等。如果大家去看 LinearLayout的源碼,會發現其內部定義了LinearLayout.LayoutParams,在此類中,你可以發現weight和 gravity的身影。
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中;此種模式比較少見。
注:上面的每一行都有一個一般,意思上述不是絕對的,對於childView的mode的設置還會和ViewGroup的測量mode有一定的關係;當然了,這是第一篇自定義ViewGroup,而且絕大部分情況都是上面的規則,所以爲了通俗易懂,暫不深入討論其他內容。
從API角度進行淺析
上面敘述了ViewGroup和View的職責,下面從API角度進行淺析。
View的根據ViewGroup傳人的測量值和模式,對自己寬高進行確定(onMeasure中完成),然後在onDraw中完成對自己的繪製。
ViewGroup需要給View傳入view的測量值和模式(onMeasure中完成),而且對於此ViewGroup的父佈局,自己也需要在onMeasure中完成對自己寬和高的確定。此外,需要在onLayout中完成對其childView的位置的指定。
下面將以一個具體的Demo(自定義一個線性佈局)爲例,進行詳細講解:
1.對於自定義一個橫向的線性佈局,我們需要我們的ViewGroup支持margin,所以我們可以直接使用系統的MarginLayoutParams
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
3.具體代碼
package com.example.linearlayoutdemo.view;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
public class MyLinearlayout extends ViewGroup {
private int length;
@SuppressLint("NewApi")
public MyLinearlayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyLinearlayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public MyLinearlayout(Context context) {
super(context);
}
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
return new MarginLayoutParams(getContext(), attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec); //計算所有childView的寬和高
int count = getChildCount(); //獲取子元素的個數
int width=0;
int height=0;
//標記子元素的寬和高
int cWidth=0;
int cHeight=0;
//用來標記每個元素的寬和高
int []countWidth=new int[count];
int []countHeight=new int[count];
MarginLayoutParams cParams=null;
for(int i=0;i<count;i++){
View childView = getChildAt(i);
cWidth=childView.getMeasuredWidth();
cHeight=childView.getMeasuredHeight();
cParams=(MarginLayoutParams) childView.getLayoutParams();
countWidth[i]=cWidth+cParams.leftMargin+cParams.rightMargin;
countHeight[i]=cHeight+cParams.topMargin+cParams.bottomMargin;
}
width=sortMax(countWidth);
height=sortMax(countHeight);
setMeasuredDimension((widthMode==MeasureSpec.EXACTLY)?widthSize:width,
(heightMode==MeasureSpec.EXACTLY)?heightSize:height);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
int cWidth=0;
int cHeight=0;
int lMargin=0;
int tMargin=0;
MarginLayoutParams cParams=null;
for(int i=0;i<count;i++){
View childView = getChildAt(i);
cWidth=childView.getMeasuredWidth();
cHeight=childView.getMeasuredHeight();
cParams=(MarginLayoutParams) childView.getLayoutParams();
lMargin=cWidth*i+cParams.leftMargin*(i+1);
tMargin=cParams.topMargin;
childView.layout(lMargin, tMargin, lMargin+cWidth, tMargin+cHeight);
}
}
/**
* 獲取最大值
* @param params
* @return
*/
public int sortMax(int params[]){
length = params.length;
int temp;
for(int i=0;i<length-1;i++){
for(int j=0;j<length-1-i;j++){
if(params[j]>params[j+1]){
temp=params[j];
params[j]=params[j+1];
params[j+1]=temp;
}
}
}
return params[length-1];
}
}