Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android深入淺出自定義控件(二)

Android深入淺出自定義控件(二)

編輯:關於Android編程

什麼是ViewGroup?

在Android的樹狀結構圖中,ViewGroup類衍生出我們所熟悉的LinearLayout、RelativeLayout等布局:

 

\

簡單來說,ViewGroup其實就相當於所有布局的父親,所以我們可以通過自定義ViewGroup類實現千變萬化的布局。

 

 

 

自定義ViewGroup主要分為以下幾個步驟:

1.創建自定義ViewGroup類,繼承於ViewGroup類,重寫ViewGroup的三個構造方法
2.重寫onMeasure方法,設置好ViewGroup及其子View在界面上所顯示的大小
3.添加參數
4.重寫onLayout方法,設置好子View的布局,這個方法也是最為關鍵的,它決定了你所自定義的布局的特性

 

 

 

1.創建自定義ViewGroup類,繼承於ViewGroup類,重寫ViewGroup的三個構造方法

 

public class FlowLayoutextends ViewGroup{

	public FlowLayout(Context context) {
		// TODO Auto-generated constructor stub
		this(context,null);
	}
	
	public FlowLayout(Context context, AttributeSet attrs) {
		// TODO Auto-generated constructor stub
		this(context,attrs,0);
		
	}

	public FlowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
		super(context, attrs, defStyleAttr);
		// TODO Auto-generated constructor stub
	}	

}


 

 

2.重寫onMeasure方法,設置好ViewGroup及其子View在界面上所顯示的大小

 

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	// TODO Auto-generated method stub
	measureChildren(widthMeasureSpec, heightMeasureSpec);
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}


 

對這個方法的使用見下文,此處記得添加measureChildren(widthMeasureSpec, heightMeasureSpec);表示由系統自己測量每個子View的大小

 

 

3.添加參數,比如我們想要讓子View之間能有間距,則需要手動創建一個內部參數類

 

public static class FlowLayoutParams extends ViewGroup.MarginLayoutParams{

	public FlowLayoutParams(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
	}
		
}


 

 

 

4.重寫onLayout方法,設置好子View的布局,這個方法也是最為關鍵的,它決定了你所自定義的布局的特性

 

@Override
protected void onLayout(boolean arg0, int left, int top, int right, int bottom) {
	// TODO Auto-generated method stub
	//獲得FlowLayout所測量出來的寬度
	int mViewGroupWidth = getMeasuredWidth();
	/**
	* paintX:繪制每個View時的光標起點的橫坐標
	* paintY:繪制每個View時的光標起點的縱坐標
	*/
	int paintX = left;
	int paintY = top;
	
	//用於記錄上一行的最大高度	
	int maxlineHeight = 0;
		
	int childCount = getChildCount();
	for(int i=0; i mViewGroupWidth){
			//繪制的起點的橫坐標重新移回FlowLayout的橫坐標
			paintX = left;
			//繪制的起點的縱坐標要向下移動一行的高度
			paintY = paintY + maxlineHeight + params.topMargin + params.bottomMargin;
			maxlineHeight = 0;
		}
		maxlineHeight = Math.max(childViewHeight, maxlineHeight);
		childView.layout(paintX+params.leftMargin, paintY+params.topMargin, paintX+childViewWidth+params.leftMargin, paintY+childViewHeight+params.topMargin);
		//每繪制一次,起點光標就要向右移動一次
		paintX = paintX + childViewWidth + params.leftMargin + params.rightMargin;
	}
}

解析:在onLayout方法中,我們對每個子View進行遍歷並設置好它們應該在的位置,比如是LinearLayout的話,在onLayout中系統會規定它的子View只能按著橫向或者豎向排列下去,也就是說,onLayout裡子View的排布是按著我們自己的想法來決定,到底這個自定義布局會有怎樣的特性?

此處我們demo是自定義FlowLayout,也就是每一行都向右排列下去,直到這一行不夠容納,則子View自動換行,進入下一行,依此類推,從而實現流式布局,為了實現這樣的效果,最關鍵的應該是在換行的時候,需要實現讓我們的子View能夠判斷到底換不換行,代碼思路如下:
 

1.首先需要記錄FlowLayout的寬度, 作為每一行的寬度上限:

 

int mViewGroupWidth = getMeasuredWidth();



2.每次繪制一個子View,是通過View.layout()方法來進行,而layout方法需要提供4個參數,即(繪制的起點橫坐標,繪制的起點縱坐標,子View的寬度,子View的高度),而每一個子View的繪制起點肯定不一樣,所以需要定義兩個變量來記錄:paintX,paintY:

 

 

/**
		 * paintX:繪制每個View時的光標起點的橫坐標
		 * paintY:繪制每個View時的光標起點的縱坐標
		 */
		int paintX = left;
		int paintY = top;




3.通過for循環,遍歷得到每個子View,由於要讓子View之間能夠有間距,所以還需要定義一個margin參數提供給子View:
FlowLayout.FlowLayoutParams params = (FlowLayout.FlowLayoutParams)childView.getLayoutParams();


以及獲得每個子View的寬高:
int childViewWidth = childView.getMeasuredWidth();
int childViewHeight = childView.getMeasuredHeight();




4.判斷如果再添加一個子View,需不需要換行,所以需要將這個View的寬度和當前行的寬度相加,與FlowLayout的寬度(即上限)進行對比,如果超過上限,就進行換行操作:

 

 

//繪制的起點的橫坐標重新移回FlowLayout的橫坐標
paintX = left;
//繪制的起點的縱坐標要向下移動一行的高度
paintY = paintY + maxlineHeight + params.topMargin + params.bottomMargin;
//由於換行,所以當前行變成了下一行,最大高度自然也就置為當前這個新起行的子View的高度(因為它是下一行的第一個View)
maxlineHeight = childViewHeight;


 

5.繪制當前子View,光標移動至下一個起點:

 

//每次都要計算當前行的最大高度
maxlineHeight = Math.max(childViewHeight, maxlineHeight);
childView.layout(paintX+params.leftMargin, paintY+params.topMargin, paintX+childViewWidth+params.leftMargin, paintY+childViewHeight+params.topMargin);
//每繪制一次,起點光標就要向右移動一次
paintX = paintX + childViewWidth + params.leftMargin + params.rightMargin;


 

 

至此,一個基本的FlowLayout布局定義完成,接下來就只需要在布局文件中來使用它:

 


    
    


 

 

運行:

\

 

 

可以看到達到了我們所要的效果,但這裡我們設置的FlowLayout的大小都是fill_parent,如果改為wrap_content會怎樣?
將FlowLayout的layout_height設置為wrap_content,雖然屏幕上仍然呈現的是一樣的效果,可是在預覽窗口中點擊FlowLayout,可以看到其實FlowLayout依舊是fill_parent的大小,而不是貼著子View的邊緣,如圖:

\

 

這不是我們想要的效果,正確的做法應該是:設置為wrap_content時,FlowLayout的邊界是緊緊貼著子View的邊緣的,所以我們應該修改onMeasure方法:

 

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
	// TODO Auto-generated method stub
	//measureChildren(widthMeasureSpec, heightMeasureSpec);
	super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	
	//得到FlowLayout的模式和寬高
	int widthMode = MeasureSpec.getMode(widthMeasureSpec);
	int widthSize = MeasureSpec.getSize(widthMeasureSpec);
	int heightMode = MeasureSpec.getMode(heightMeasureSpec);
	int heightSize = MeasureSpec.getSize(heightMeasureSpec);
		
	//記錄wrap_content下的最終寬度和高度
	int width = 0;
	int height = 0;
	//記錄每一行的最大寬度和最大高度
	int lineWidth = 0;
	int lineHeight = 0;
	
	int childCount = getChildCount();
	for(int i=0;i widthSize){
			width = Math.max(lineWidth, childWidth); 
			height = height + lineHeight;
			lineWidth = childWidth;
			lineHeight = childHeight;
		}else  
	            // 否則累加值lineWidth,lineHeight取最大高度  
	        {  
	                lineWidth += childWidth;  
	                lineHeight = Math.max(lineHeight, childHeight);  
	        } 
		
		//如果是最後一個子View
		if (i == childCount - 1){  
                	width = Math.max(width, lineWidth);  
                	height += lineHeight;  
            	}  
			
			
	}
	//根據模式來決定FlowLayout的大小,如果不是EXACTLY,則說明布局文件中設置的模式應該是wrap_content
	/**
	* 如果是wrap_content,則將FlowLayout寬度和高度設置為我們計算出來的最終寬高
	* 如果是fill_parent或者具體數值,則將FlowLayout寬度和高度設置為一開始getMode和getSize得到的那個寬高
	*/
	setMeasuredDimension((widthMode == MeasureSpec.EXACTLY) ? widthSize  
             : width, (heightMode == MeasureSpec.EXACTLY) ? heightSize  
             : height); 
}


 

 

再次查看預覽窗口:

\

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved