地址
筆記51 | Android自定義View(二)
http://www.jianshu.com/p/c84693096e41
自定義ViewGroup
自定義View的過程很簡單,就那幾步,可自定義ViewGroup可就沒那麼簡單啦~,因爲它不僅要管好自己的,還要兼顧它的子View。我們都知道ViewGroup是個View容器,它裝納child View並且負責把child View放入指定的位置。我們假象一下,如果是讓你負責設計ViewGroup,你會怎麼去設計呢?
首先,我們得知道各個子View的大小吧,只有先知道子View的大小,我們才知道當前的ViewGroup該設置爲多大去容納它們。
根據子View的大小,以及我們的ViewGroup要實現的功能,決定出ViewGroup的大小
ViewGroup和子View的大小算出來了之後,接下來就是去擺放了吧,具體怎麼去擺放呢?這得根據你定製的需求去擺放了,比如,你想讓子View按照垂直順序一個挨着一個放,或者是按照先後順序一個疊一個去放,這是你自己決定的。
已經知道怎麼去擺放還不行啊,決定了怎麼擺放就是相當於把已有的空間”分割”成大大小小的空間,每個空間對應一個子View,我們接下來就是把子View對號入座了,把它們放進它們該放的地方去。
現在就完成了ViewGroup的設計了,我們來個具體的案例:將子View按從上到下垂直順序一個挨着一個擺放,即模仿實現LinearLayout的垂直佈局。
首先重寫onMeasure,實現測量子View大小以及設定ViewGroup的大小:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//將所有的子View進行測量,這會觸發每個子View的onMeasure函數
//注意要與measureChild區分,measureChild是對單個view進行測量
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
if (childCount == 0) {//如果沒有子View,當前ViewGroup沒有存在的意義,不用佔用空間
setMeasuredDimension(0, 0);
} else {
//如果寬高都是包裹內容
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
//我們將高度設置爲所有子View的高度相加,寬度設爲子View中最大的寬度
int height = getTotleHeight();
int width = getMaxChildWidth();
setMeasuredDimension(width, height);
} else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹內容
//寬度設置爲ViewGroup自己的測量寬度,高度設置爲所有子View的高度總和
setMeasuredDimension(widthSize, getTotleHeight());
} else if (widthMode == MeasureSpec.AT_MOST) {//如果只有寬度是包裹內容
//寬度設置爲子View中寬度最大的值,高度設置爲ViewGroup自己的測量值
setMeasuredDimension(getMaxChildWidth(), heightSize);
}
}
}
/***
* 獲取子View中寬度最大的值
*/
private int getMaxChildWidth() {
int childCount = getChildCount();
int maxWidth = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView.getMeasuredWidth() > maxWidth)
maxWidth = childView.getMeasuredWidth();
}
return maxWidth;
}
/***
* 將所有子View的高度相加
**/
private int getTotleHeight() {
int childCount = getChildCount();
int height = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
height += childView.getMeasuredHeight();
}
return height;
}
代碼中的註釋我已經寫得很詳細,不再對每一行代碼進行講解。上面的onMeasure將子View測量好了,以及把自己的尺寸也設置好了,接下來我們去擺放子View吧~
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
//記錄當前的高度位置
int curHeight = t;
//將子View逐個擺放
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int height = child.getMeasuredHeight();
int width = child.getMeasuredWidth();
//擺放子View,參數分別是子View矩形區域的左、上、右、下邊
child.layout(l, curHeight, l + width, curHeight + height);
curHeight += height;
}
}
我們測試一下,將我們自定義的ViewGroup裏面放3個Button ,將這3個Button的寬度設置不一樣,把我們的ViewGroup的寬高都設置爲包裹內容wrap_content
,爲了看的效果明顯,我們給ViewGroup加個背景:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.evan.view.MainActivity"
tools:ignore="MergeRootFrame" >
<com.evan.view.myView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/holo_blue_light" >
<Button
android:layout_width="100dp"
android:layout_height="wrap_content"
android:text="btn" />
<Button
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="btn" />
<Button
android:layout_width="50dp"
android:layout_height="wrap_content"
android:text="btn" />
<Button
android:layout_width="150dp"
android:layout_height="wrap_content"
android:text="btn" />
<Button
android:layout_width="250dp"
android:layout_height="wrap_content"
android:text="btn" />
</com.evan.view.myView>
</FrameLayout>
看看最後的效果吧~
是不是很激動我們自己也可以實現LinearLayout的效果啦~~~
最後附上MyViewGroup的完整源碼:
package com.evan.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
public class myView extends ViewGroup{
private int defalutSize;
public myView(Context context, AttributeSet attrs) {
super(context, attrs);
//第二個參數就是我們在styles.xml文件中的<declare-styleable>標籤
//即屬性集合的標籤,在R文件中名稱爲R.styleable+name
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
//第一個參數爲屬性集合裏面的屬性,R文件名稱:R.styleable+屬性集合名稱+下劃線+屬性名稱
//第二個參數爲,如果沒有設置這個屬性,則設置的默認的值
defalutSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100);
//最後記得將TypedArray對象回收
a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
measureChildren(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int childCount = getChildCount();
if (childCount == 0) {//如果沒有子View,當前ViewGroup沒有存在的意義,不用佔用空間
setMeasuredDimension(0, 0);
}else{
//如果寬高都是包裹內容
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
int height = getTotleHeight();
int width = getMaxChildWidth();
setMeasuredDimension(width, height);
}else if (heightMode == MeasureSpec.AT_MOST) {
//寬度設置爲ViewGroup自己的測量寬度,高度設置爲所有子View的高度總和
setMeasuredDimension(widthSize, getTotleHeight());
}else if (widthMode == MeasureSpec.AT_MOST) {
//寬度設置爲子View中寬度最大的值,高度設置爲ViewGroup自己的測量值
setMeasuredDimension(getMaxChildWidth(), heightSize);
}
}
}
/*
* (non-Javadoc)
* 獲取子View中寬度的最大值
*/
private int getMaxChildWidth(){
int childCount = getChildCount();
int maxWidth = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
if (childView.getMeasuredWidth() > maxWidth) {
maxWidth = childView.getMeasuredWidth()+5;
}
}
return maxWidth;
}
/*
* (non-Javadoc)
* 將所有子View的高度相加
*
*/
private int getTotleHeight(){
int childCount = getChildCount();
int height = 0;
for (int i = 0; i < childCount; i++) {
View childView = getChildAt(i);
height+=(childView.getMeasuredHeight());
}
return height;
}
/*
* (non-Javadoc)
* 擺放
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
//記錄當前的高度位置
int curHeight = t;
//將子View逐個擺放
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int height = child.getMeasuredHeight();
int width = child.getMeasuredWidth();
//擺放子View,參數分別是子View矩形區域的左、上、右、下邊
child.layout(l, curHeight, l+width, curHeight+height);
curHeight += height;
}
}
}
好啦~自定義View的學習到此結束,是不是發現自定義View如此簡單呢?