Android中使用自定義ViewGroup的總結(jié)
分類
自定義Layout可以分為兩種情況。
- 自定義ViewGroup,創(chuàng)造出一些不同于LinearLayout,RelativeLayout等之類的ViewGroup。比如:API 14以后增加的GridLayout、design support library中的CoordinatorLayout等等。
- 自定義一些已經(jīng)有的Layout然后加一些特殊的功能。比如:TableLayout以及percent support library中的PercentFrameLayout等等。
流程
自定義View的流程是:onMeasure()->onLayout()->onDraw()。自定義ViewGroup的時(shí)候一般是不要去實(shí)現(xiàn)onDraw的,當(dāng)然也可能有特殊的需求,比如:CoordinatorLayout。
所以onMeasure和onLayout基本能做大部分我們接觸的ViewGroup。但是僅僅的知道在onMeasure中測(cè)量ViewGroup的大小以及在onLayout中計(jì)算Child View的位置還是不夠。
比如:怎么可以給ViewGroup的Child View設(shè)置屬性?
一個(gè)例子。
寫一個(gè)自定義的ViewGroup,增加一個(gè)屬性控制Child View的大小(長(zhǎng)寬)占ViewGroup的比例。
假設(shè)是一個(gè)LinearLayout,那么就先定義一個(gè)CustomLinearLayout。
public class CustomLinearLayout extends LinearLayout { public CustomLinearLayout(Context context) { super(context); } public CustomLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } }
其它拋開不說,我們是需要增加屬性用來控制Child View的大小。那么就先在value/attr.xml中定義那個(gè)屬性(使用CustomLinearLayout_Layout來與CustomLinearLayout區(qū)分下,當(dāng)然這個(gè)名字是隨意的)。
<declare-styleable name="CustomLinearLayout_Layout"> <!-- 定義比例 --> <attr name="inner_percent" format="float"/> </declare-styleable>
ViewGroup調(diào)用addView()的時(shí)候最終都會(huì)調(diào)用到這個(gè)方法。
public void addView(View child, int index, LayoutParams params)
這個(gè)params代表的就是View的配置,ViewGroup.LayoutParams中就包含了width、height,LinearLayout.LayoutParams增加了weight屬性等等。那么我們就應(yīng)該實(shí)現(xiàn)一個(gè)LayoutParams。那么現(xiàn)在就是這樣了。
public class CustomLinearLayout extends LinearLayout { public CustomLinearLayout(Context context) { super(context); } public CustomLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); } public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) public CustomLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } public static class LayoutParams extends LinearLayout.LayoutParams { private float innerPercent; private static final int DEFAULT_WIDTH = WRAP_CONTENT; private static final int DEFAULT_HEIGHT = WRAP_CONTENT; public LayoutParams() { super(DEFAULT_WIDTH, DEFAULT_HEIGHT); innerPercent = -1.0f; } public LayoutParams(float innerPercent) { super(DEFAULT_WIDTH, DEFAULT_HEIGHT); this.innerPercent = innerPercent; } public LayoutParams(ViewGroup.LayoutParams p) { super(p); } @TargetApi(Build.VERSION_CODES.KITKAT) public LayoutParams(LinearLayout.LayoutParams source) { super(source); } @TargetApi(Build.VERSION_CODES.KITKAT) public LayoutParams(LayoutParams source) { super(source); this.innerPercent = source.innerPercent; } public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); init(c, attrs); } private void init(Context context, AttributeSet attrs) { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CustomLinearLayout_Layout); innerPercent = a.getFloat(R.styleable.CustomLinearLayout_Layout_inner_percent, -1.0f); a.recycle(); } } }
現(xiàn)在就可以在xml使用我們的屬性了。
<?xml version="1.0" encoding="utf-8"?> <com.egos.samples.custom_layout.CustomLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="200dp" android:layout_height="200dp" android:id="@+id/test_layout" android:background="#ffff0000" android:gravity="center" android:orientation="vertical"> <ImageView android:text="Egos" android:layout_width="match_parent" android:layout_height="match_parent" android:onClick="add" android:background="#ff00ff00" app:inner_percent="0.8"/> </com.egos.samples.custom_layout.CustomLinearLayout>
只是然并軟,并沒有作用。
那么到底是怎么去控制Child View的大小呢?當(dāng)然是在onMeasure控制的。addView會(huì)執(zhí)行下面的代碼。
requestLayout(); invalidate(true);
這樣的話就會(huì)重新的走一遍onMeasure(),onLayout()了。實(shí)現(xiàn)onMeasure()的方法以后直接去處理Child View的大小,因?yàn)槲依^承的是LinearLayout,所以其實(shí)是會(huì)處理到measureChildBeforeLayout()。最終是在measureChildBeforeLayout的時(shí)候來處理Child View的大小。
@Override protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { // 在xml強(qiáng)制寫成match_parent,然后在這里強(qiáng)制設(shè)置成 if (child != null && child.getLayoutParams() instanceof LayoutParams && ((LayoutParams) child.getLayoutParams()).innerPercent != -1.0f) { parentWidthMeasureSpec = MeasureSpec.makeMeasureSpec((int) (MeasureSpec.getSize(parentWidthMeasureSpec) * ((LayoutParams) child.getLayoutParams()).innerPercent), MeasureSpec.getMode(parentWidthMeasureSpec)); parentHeightMeasureSpec = MeasureSpec.makeMeasureSpec((int) (MeasureSpec.getSize(parentHeightMeasureSpec) * ((LayoutParams) child.getLayoutParams()).innerPercent), MeasureSpec.getMode(parentHeightMeasureSpec)); super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); } else { super.measureChildWithMargins(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed); } }
這樣就可以實(shí)現(xiàn)最開始的需求了。
其實(shí)還有一些細(xì)節(jié)是需要處理的,下面的代碼就是。
/** * 當(dāng)checkLayoutParams返回false的時(shí)候就會(huì)執(zhí)行到這里的generateLayoutParams */ @Override protected LinearLayout.LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) { return super.generateLayoutParams(lp); } /** * 當(dāng)addView的時(shí)候沒有設(shè)置LayoutParams的話就會(huì)默認(rèn)執(zhí)行這里的generateDefaultLayoutParams */ @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(); } /** * 寫在xml中屬性的時(shí)候就會(huì)執(zhí)行這里的generateLayoutParams */ @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); }
總結(jié)一下
- 自定義View和ViewGroup需要多多注意的都是onMeasure、onLayout、onDraw。
- 把ViewGroup自身的屬性和Child View的屬性區(qū)分開。
- 可以多多的參考support包中的代碼,調(diào)試也非常的方便。
做Android開發(fā),自身需要自定義View的地方確實(shí)是比較的多,只是大部分都會(huì)有相應(yīng)的開源庫。但是我們還是應(yīng)該需要熟練的知道該如何自定義一個(gè)ViewGroup。
自己是一個(gè)比較健忘的人,所以寫的詳細(xì)點(diǎn)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android開發(fā)之DatePicker和TimePicker實(shí)現(xiàn)選擇日期時(shí)間功能示例
這篇文章主要介紹了Android開發(fā)之DatePicker和TimePicker實(shí)現(xiàn)選擇日期時(shí)間功能,結(jié)合實(shí)例形式分析了Android DatePicker和TimePicker組件的功能、常用函數(shù)、布局及日期時(shí)間選擇相關(guān)操作技巧,需要的朋友可以參考下2019-03-03詳解Android開發(fā)中ContentObserver類的使用
這篇文章主要介紹了詳解Android開發(fā)中ContentObserver類的使用,ContentObserver內(nèi)容觀察者主要用來監(jiān)聽uri的改變請(qǐng)情況,需要的朋友可以參考下2016-04-04Android動(dòng)態(tài)顯示具體到秒的相聚時(shí)間
這篇文章主要為大家詳細(xì)介紹了Android動(dòng)態(tài)顯示具體到秒的相聚時(shí)間,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-05-05解決django 多個(gè)APP時(shí) static文件的問題
這篇文章主要介紹了解決django 多個(gè)APP時(shí) static文件的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03Android簡(jiǎn)單實(shí)現(xiàn)啟動(dòng)畫面的方法
這篇文章主要介紹了Android簡(jiǎn)單實(shí)現(xiàn)啟動(dòng)畫面的方法,結(jié)合實(shí)例形式分析了啟動(dòng)畫面核心代碼及相關(guān)函數(shù),具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07Android實(shí)現(xiàn)簡(jiǎn)單的分頁效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)單的分頁效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11Android監(jiān)聽輸入法彈窗和關(guān)閉的實(shí)現(xiàn)方法
用過ios的都知道ios上輸入法關(guān)閉的同時(shí)會(huì)自動(dòng)關(guān)閉輸入框,那么在android上如何實(shí)現(xiàn)監(jiān)聽輸入法彈出和關(guān)閉呢?接下來通過本文給大家分享一種可靠的實(shí)現(xiàn)方式2016-11-11詳談Matrix中preTranslate()和postTranslate()的理解
這篇文章主要為大家詳細(xì)介紹了Matrix中preTranslate()和postTranslate()的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11Android系統(tǒng)添加自定義鼠標(biāo)樣式通過按鍵切換實(shí)例詳解
在本篇文章里小編給大家整理的是關(guān)于Android系統(tǒng)添加自定義鼠標(biāo)樣式通過按鍵切換實(shí)例詳解內(nèi)容,有需要的朋友們可以學(xué)習(xí)下。2019-11-11