自定義ViewGroup

首先介紹一下ViewGroup是什麼?
ViewGroup相當於一個放置View的容器,並且我們在寫佈局xml的時候,會告訴容器(凡是以layout爲開頭的屬性,都是爲用於告訴容器 的),我們的寬度(layout_width)、高度(layout_height)、對齊方式(layout_gravity)等;當然還有 margin等;於是乎,ViewGroup的職能爲:給childView計算出建議的寬和高和測量模式 ;決定childView的位置;爲什麼只是建議的寬和高,而不是直接確定呢,別忘了childView寬和高可以設置爲wrap_content,這樣 只有childView才能計算出自己的寬和高。

View的職責是啥?

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);
}
我們重寫父類的該方法,即爲我們的ViewGroup指定了其LayoutParams爲MarginLayoutParams
2.measureChildren(widthMeasureSpecheightMeasureSpec); 
該方法主要根據傳入的三種測量模式以及傳入的尺寸的要求,並考慮padding和margin的值,計算出來的一個測量值,所以我們使用這一行代碼可以滿足我們的大部分需求,但是一些特殊情況還是要按需進行計算。

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];
}
 
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章