整理自:
http://blog.csdn.net/huachao1001/article/details/51577291
最近一個項目里面要用到自定義View的技術(shù),看了很多博客症杏,準(zhǔn)備在這里整理一下
首先,我把實現(xiàn)自己想要的View效果的方法分為3類:
-
自定義View
有多種實現(xiàn)方式:
Ⅰ篙梢、繼承現(xiàn)有控件,對其控件的功能進(jìn)行拓展美旧。
Ⅱ渤滞、將現(xiàn)有控件進(jìn)行組合,實現(xiàn)功能更加強(qiáng)大控件陈症。
Ⅲ蔼水、重寫View實現(xiàn)全新的控件
-
自定義ViewGroup
這個實際上就是我們自定義一個布局,我們在實際使用的時候依然要在這個自定義ViewGroup中加子元素(比如TextView录肯、ImageView或者其他)
上一篇把自定義View講了趴腋,這一篇講自定義ViewGroup:
首先先明確設(shè)計ViewGroup的要點:
- 首先,我們得知道各個子View的大小吧,只有先知道子View的大小优炬,我們才知道當(dāng)前的ViewGroup該設(shè)置為多大去容納它們颁井。
- 根據(jù)子View的大小,以及我們的ViewGroup要實現(xiàn)的功能蠢护,決定出ViewGroup的大小
- ViewGroup和子View的大小算出來了之后雅宾,接下來就是去擺放了吧,具體怎么去擺放呢葵硕?這得根據(jù)你定制的需求去擺放了眉抬,比如,你想讓子View按照垂直順序一個挨著一個放懈凹,或者是按照先后順序一個疊一個去放蜀变,這是你自己決定的。
- 已經(jīng)知道怎么去擺放還不行啊介评,決定了怎么擺放就是相當(dāng)于把已有的空間”分割”成大大小小的空間库北,每個空間對應(yīng)一個子View,我們接下來就是把子View對號入座了们陆,把它們放進(jìn)它們該放的地方去寒瓦。
我們來個具體的案例:將子View按從上到下垂直順序一個挨著一個擺放,即模仿實現(xiàn)LinearLayout的垂直布局坪仇。
一杂腰、重寫onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//將所有的子View進(jìn)行測量,這會觸發(fā)每個子View的onMeasure函數(shù)
//注意要與measureChild區(qū)分烟很,measureChild是對單個view進(jìn)行測量
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,當(dāng)前ViewGroup沒有存在的意義颈墅,不用占用空間
setMeasuredDimension(0, 0);
} else {
//如果寬高都是包裹內(nèi)容,即wrap_content
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
//我們將高度設(shè)置為所有子View的高度相加蜡镶,寬度設(shè)為子View中最大的寬度
int height = getTotleHeight();
int width = getMaxChildWidth();
setMeasuredDimension(width, height);
} else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹內(nèi)容
//寬度設(shè)置為ViewGroup自己的測量寬度雾袱,高度設(shè)置為所有子View的高度總和
setMeasuredDimension(widthSize, getTotleHeight());
} else if (widthMode == MeasureSpec.AT_MOST) {//如果只有寬度是包裹內(nèi)容
//寬度設(shè)置為子View中寬度最大的值,高度設(shè)置為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;
}
解釋基本注釋說的就很清楚了
二官还、拜訪子View芹橡,使用onLayout
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
//記錄當(dāng)前的高度位置
int curHeight = t;
//將子View逐個擺放
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int height = child.getMeasuredHeight();
int width = child.getMeasuredWidth();
//擺放子View,參數(shù)分別是子View矩形區(qū)域的左望伦、上林说、右、下邊
child.layout(l, curHeight, l + width, curHeight + height);
curHeight += height;
}
}
上面child.layout(l, curHeight, l + width, curHeight + height);
代碼就是設(shè)置這個子View的左邊上邊右邊下邊的位置分別在哪里屯伞。
然后因為我們是想要實現(xiàn)一個在一個下面的效果腿箩,所以我們設(shè)置curHeight += height;
我們測試一下,將我們自定義的ViewGroup里面放3個Button ,將這3個Button的寬度設(shè)置不一樣劣摇,把我們的ViewGroup的寬高都設(shè)置為包裹內(nèi)容wrap_content
珠移,為了看的效果明顯,我們給ViewGroup加個背景:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.hc.studyview.MyViewGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ff9900">
<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" />
</com.hc.studyview.MyViewGroup>
</LinearLayout>
注意看上面的MyViewGroup里面包的三個Button子元素就是我們在寫MyViewGroup遇到的int childCount = getChildCount();
這些東西!>濉暇韧!我們就是把這些Child按照我們呢自己想要的方式來排序~~
看看最后的效果吧~
最后附上MyViewGroup的完整源碼:
public class MyViewGroup extends ViewGroup {
public MyViewGroup(Context context) {
super(context);
}
public MyViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
}
/***
* 獲取子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;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//將所有的子View進(jìn)行測量,這會觸發(fā)每個子View的onMeasure函數(shù)
//注意要與measureChild區(qū)分浓瞪,measureChild是對單個view進(jìn)行測量
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,當(dāng)前ViewGroup沒有存在的意義懈玻,不用占用空間
setMeasuredDimension(0, 0);
} else {
//如果寬高都是包裹內(nèi)容
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
//我們將高度設(shè)置為所有子View的高度相加,寬度設(shè)為子View中最大的寬度
int height = getTotleHeight();
int width = getMaxChildWidth();
setMeasuredDimension(width, height);
} else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹內(nèi)容
//寬度設(shè)置為ViewGroup自己的測量寬度乾颁,高度設(shè)置為所有子View的高度總和
setMeasuredDimension(widthSize, getTotleHeight());
} else if (widthMode == MeasureSpec.AT_MOST) {//如果只有寬度是包裹內(nèi)容
//寬度設(shè)置為子View中寬度最大的值涂乌,高度設(shè)置為ViewGroup自己的測量值
setMeasuredDimension(getMaxChildWidth(), heightSize);
}
}
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
int count = getChildCount();
//記錄當(dāng)前的高度位置
int curHeight = t;
for (int i = 0; i < count; i++) {
View child = getChildAt(i);
int height = child.getMeasuredHeight();
int width = child.getMeasuredWidth();
child.layout(l, curHeight, l + width, curHeight + height);
curHeight += height;
}
}
}