Android系统控件获取自定义属性
我們?nèi)绻朐贗mageView,Button,TextView等系統(tǒng)控件中在XML中配置自定義屬性該如何實現(xiàn)呢?例如我們有一個scrollView,在ScrollView里面有上述的一些控件的自定義屬性,實現(xiàn)在滑動Scrollview時,里面的控件根據(jù)滑動的距離執(zhí)行各自的動畫進度。scrollivew里包含的這些控件可以是任意常用的控件,如 ImageView,Button,TextView等。我們將給這些普通的系統(tǒng)控件配置自定義屬性!看到這里是不是覺得無法實現(xiàn),因為系統(tǒng)的ImageView,Button等是無法識別我們自定義的屬性值的,系統(tǒng)的控件怎么識別我們隨便定義的屬性呢。今天我們就來解決這個問題,解決這個問題的意義在于讓系統(tǒng)控件能像我們自定義控件一樣,配置了屬性就可以執(zhí)行相應的動畫。我們先來看一下運行效果,完整項目見:https://github.com/buder-cp/CustomView/tree/master/buder_DN_view/buderdn13
首先我們看下自定義的控件的xml布局文件:
<com.test.buderdn13.AnimatorScrollView xmlns:android="http://schemas.android.com/apk/res/android"xmlns:discrollve="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"><com.test.buderdn13.AnimatorLinerLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><TextViewandroid:layout_width="match_parent"android:layout_height="200dp"android:background="#007788"android:fontFamily="serif"android:gravity="center"android:padding="25dp"android:text="帶上您的行李箱,準備shopping!"android:textColor="@android:color/black"android:textSize="25sp"discrollve:discrollve_alpha="true" /><ImageViewandroid:layout_width="200dp"android:layout_height="120dp"android:layout_gravity="top|right"android:src="@mipmap/baggage"discrollve:discrollve_alpha="true"discrollve:discrollve_translation="fromLeft|fromBottom" /><TextViewandroid:layout_width="match_parent"android:layout_height="200dp"android:fontFamily="serif"android:gravity="center"android:padding="25dp"android:text="準備好相機,這里有你想象不到的驚喜!"android:textColor="@android:color/black"android:textSize="25sp"discrollve:discrollve_fromBgColor="#ffff00"discrollve:discrollve_toBgColor="#88EE66" /><ImageViewandroid:layout_width="220dp"android:layout_height="110dp"android:layout_gravity="right"android:src="@mipmap/camera"discrollve:discrollve_translation="fromRight" /><TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:background="#D97C1F"android:fontFamily="serif"android:gravity="center"android:padding="20dp"android:text="這次淘寶造物節(jié)真的來了,我們都在造,你造嗎?\n7月22日-7月24日\n上海世博展覽館\n在現(xiàn)場,我們造什么?"android:textSize="23sp"discrollve:discrollve_alpha="true"discrollve:discrollve_translation="fromBottom" /><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_margin="20dp"android:src="@mipmap/sweet"discrollve:discrollve_scaleX="true"discrollve:discrollve_scaleY="true" /><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_margin="20dp"android:src="@mipmap/shoes"discrollve:discrollve_alpha="true"discrollve:discrollve_scaleX="true"discrollve:discrollve_scaleY="true"discrollve:discrollve_translation="fromLeft|fromBottom" /><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_margin="20dp"android:src="@mipmap/shoes"discrollve:discrollve_alpha="true"discrollve:discrollve_scaleY="true"discrollve:discrollve_translation="fromRight|fromTop" /><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_margin="20dp"android:src="@mipmap/sweet"discrollve:discrollve_alpha="true"discrollve:discrollve_scaleY="true"discrollve:discrollve_translation="fromLeft" /><ImageViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:layout_margin="20dp"android:src="@mipmap/camera"discrollve:discrollve_scaleY="true"discrollve:discrollve_translation="fromLeft" /></com.test.buderdn13.AnimatorLinerLayout></com.test.buderdn13.AnimatorScrollView>AnimatorScrollView(重寫了Scrollview) --->AnimatorLinerLayout(重寫了LinearLayout)--->包含的系統(tǒng)控件。
現(xiàn)在我們來解釋下為什么要重寫AnimatorScrollView與AnimatorLinerLayout以及如何讓系統(tǒng)控件如Imageview等來識別我們的自定義屬性。
1.1? 為什么需要自定義AnimatorScrollView
為什么要重寫AnimatorScrollView,這個是為了重寫
protected void onScrollChanged(int l, int t, int oldl, int oldt)目的是用來獲取滑動的距離t的,然后t/控件的height就可以得出一個比例,執(zhí)行每個內(nèi)部控件的動畫(透明度,平移X,平移Y等)的比例。
1.2 為什么需要AnimatorLinerLayout
這個是關鍵,為了解決系統(tǒng)控件(如Imageview)不識別我們的自定義屬性的問題。
AnimatorLinerLayout繼承于LinearLayout,我們自定義LinearLayout,無非是想改變LinearLayout的行為。那么想改變什么行為呢。我們想利用AnimatorLinerLayout來獲取AnimatorLinerLayout包含的各個系統(tǒng)控件,并且解析到為系統(tǒng)控件配置的自定義屬性值。這個我們很容易用一個for循環(huán)獲取到各個子控件及相關XML自定義屬性的值。但是我們獲取到了這些自定義控件屬性又能如何,imageview等系統(tǒng)控件又不識別,就不能執(zhí)行動畫。那我們獲取這些自定義屬性給誰用??
我們可以在imageview外再包裹一個自定義父布局ViewGroup,然后把這些獲取到的自定義屬性(動畫屬性值)賦予這個包裹的VIEWGROUP,然后讓父布局可以根據(jù)屬性值來執(zhí)行動畫,那么里面的imageview是不是也就跟著動起來了呢?這個想法應該可以實現(xiàn),整體布局都執(zhí)行動畫飛了起來,子布局自然就跟著動了起來,相當于我們的系統(tǒng)控件(如Imageview)執(zhí)行了動畫。這是一個瞞天過海的做法,關于如何在LinearLayout addView之前給每一個系統(tǒng)控件包裹VIEWGROUP的事情,就交給了我們自定義的LinearLayout:AnimatorLinerLayout。這就是我們?yōu)槭裁葱枰狝nimatorLinerLayout的原因:包裹+動畫 = 子控件動畫 = 瞞天過海
我們總結一下上面的分析:
1. 自定義LinearLayout:DisScrollviewContent,改變布局結構,用自定義VIEWGROUP包裹系統(tǒng)控件如imageview等。
2. 自定義VIEWGROUP(用于包裹)
3. 自定義Scrollview:DisScrollview,根據(jù)滑動的距離來計算動畫執(zhí)行的進度比例。
2.1AnimatorLinerLayout(自定義LinearLayout)
/*** 外層布局控件,總控內(nèi)部*/ public class AnimatorLinerLayout extends LinearLayout {public AnimatorLinerLayout(Context context) {this(context, null);}public AnimatorLinerLayout(Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public AnimatorLinerLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);setOrientation(VERTICAL);}/*** 這個函數(shù)在加載XML布局時自動調(diào)用,可以獲取到每一個系統(tǒng)控件配置的布局參數(shù),包括自定義參數(shù)。* 每加載一個系統(tǒng)控件(如Imageview),則調(diào)用一次這個函數(shù)。* @param attrs* @return*/@Overridepublic LayoutParams generateLayoutParams(AttributeSet attrs) {從attrs所有參數(shù)里提取自定義屬性值,并保持在MyLayoutParams對象里,以供“自定義包裹VIEWGROUP"使用并執(zhí)行動畫。return new AnimatorLayoutParams(getContext(), attrs);}//1.考慮到系統(tǒng)控件不識別自定義屬性,所以我門要考慮給控件包一層幀//2.這里采取有父容器組件給子容器包裹一層的方式//3.系統(tǒng)是通過夾雜i 布局文件,然后調(diào)用VIEW的addView來進行加載的//4.那么此時我門進行偷天換日/*** 這個函數(shù)是在generateLayoutParams之后執(zhí)行,在這里我們可以獲取到generateLayoutParams函數(shù)返回的MyLayoutParams里的自定義屬性值。* 然后在addview系統(tǒng)控件(如Imageview)之前,先創(chuàng)建并添加一個“自定義包裹VIEWGROUP"視圖,然后將自定義屬性賦給這個視圖,最后在把系統(tǒng)控件* ddview到"自定義包裹VIEWGROUP"里,從而實現(xiàn)了在代碼中為XML里的每一個系統(tǒng)控件外層包裹一個“自定義包裹VIEWGROUP"視圖。* @param child* @param params*/@Overridepublic void addView(View child, ViewGroup.LayoutParams params) {//獲取自定義屬性,這時考慮到自定義屬性在子控件當中,//那么系統(tǒng)控件不識別自定義屬性,怎么讓自定義屬性到這個里面來//來看源碼//根據(jù)源碼流程-->先調(diào)用generateLayoutParams組裝XML屬性參數(shù)//在調(diào)用addView進行添加,所以,自定義屬性在generateLayoutParams中進行組裝獲取//在addView當中將具體的值進行封裝AnimatorLayoutParams layoutParams = (AnimatorLayoutParams) params;AnimatorFramelayout view = new AnimatorFramelayout(child.getContext());if (!isDiscrollvable(layoutParams)) {//沒有自定義屬性的系統(tǒng)控件,我們就不需要外層包裹一個“自定義包裹VIEWGROUP"視圖。直接addview即可。super.addView(view);} else {//有自定義屬性的系統(tǒng)控件,我們需要外層包裹一個“自定義包裹VIEWGROUP"視圖。view.addView(child);view.setmDiscrollveAlpha(layoutParams.mDiscrollveAlpha);view.setmDiscrollveFromBgColor(layoutParams.mDiscrollveFromBgColor);view.setmDiscrollveToBgColor(layoutParams.mDiscrollveToBgColor);view.setmDiscrollveScaleX(layoutParams.mDiscrollveScaleX);view.setmDiscrollveScaleX(layoutParams.mDiscrollveScaleY);view.setmDisCrollveTranslation(layoutParams.mDisCrollveTranslation);super.addView(view, params);}//至此到這一步就已經(jīng)獲取到了自己的自定義屬性,可以進行操作了}private boolean isDiscrollvable(AnimatorLayoutParams layoutParams) {return layoutParams.mDiscrollveAlpha ||layoutParams.mDiscrollveScaleX ||layoutParams.mDiscrollveScaleY ||layoutParams.mDisCrollveTranslation != -1 ||(layoutParams.mDiscrollveFromBgColor != -1 &&layoutParams.mDiscrollveToBgColor != -1);}/*** 自定義LayoutParams* 獲取自定義屬性*/private class AnimatorLayoutParams extends LinearLayout.LayoutParams {private boolean mDiscrollveAlpha;private boolean mDiscrollveScaleX;private boolean mDiscrollveScaleY;private int mDisCrollveTranslation;private int mDiscrollveFromBgColor;private int mDiscrollveToBgColor;private AnimatorLayoutParams(Context c, AttributeSet attrs) {super(c, attrs);TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.DiscrollView_LayoutParams);//沒有傳屬性過來,給默認值FALSEmDiscrollveAlpha = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_alpha, false);mDiscrollveScaleX = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleX, false);mDiscrollveScaleY = a.getBoolean(R.styleable.DiscrollView_LayoutParams_discrollve_scaleY, false);mDisCrollveTranslation = a.getInt(R.styleable.DiscrollView_LayoutParams_discrollve_translation, -1);mDiscrollveFromBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_fromBgColor, -1);mDiscrollveToBgColor = a.getColor(R.styleable.DiscrollView_LayoutParams_discrollve_toBgColor, -1);a.recycle();}} }想要系統(tǒng)獲取我們自定義的屬性,關鍵就是重寫這兩個函數(shù):
generateLayoutParams:這個函數(shù)在加載XML布局時自動調(diào)用,可以獲取到每一個系統(tǒng)控件配置的布局參數(shù),包括自定義參數(shù)。每加載一個系統(tǒng)控件(如Imageview),則調(diào)用一次這個函數(shù)。
addView:這個函數(shù)是在generateLayoutParams之后執(zhí)行,在這里我們可以獲取到generateLayoutParams函數(shù)返回的MyLayoutParams里的自定義屬性值。在addview系統(tǒng)控件(如Imageview)之前,先創(chuàng)建并添加一個“自定義包裹VIEWGROUP"視圖,然后將自定義屬性賦給這個視圖,最后在把系統(tǒng)控件addview到"自定義包裹VIEWGROUP"里,從而實現(xiàn)了在代碼中為XML里的每一個系統(tǒng)控件外層包裹一個“自定義包裹VIEWGROUP"視圖。
OK,至此我們已經(jīng)實現(xiàn)了在系統(tǒng)控件外包裹一層可以識別自定義屬性的VIEWGROUP父布局,接下來我們就來看一下這個自定義VIEWGROUP是如何執(zhí)行動畫的。
2.1 自定義VIEWGROUP:? AnimatorFramelayout
public class AnimatorFramelayout extends FrameLayout implements DiscrollInterface {public AnimatorFramelayout(@NonNull Context context) {this(context, null);}public AnimatorFramelayout(@NonNull Context context, @Nullable AttributeSet attrs) {this(context, attrs, 0);}public AnimatorFramelayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}/*** <attr name="discrollve_translation">* <flag name="fromTop" value="0x01" />* <flag name="fromBottom" value="0x02" />* <flag name="fromLeft" value="0x04" />* <flag name="fromRight" value="0x08" />* </attr>* 0000000001* 0000000010* 0000000100* 0000001000* top|left* 0000000001 top* 0000000100 left 或運算 |* 0000000101* 反過來就使用& 與運算*///保存自定義屬性//定義很多的自定義屬性private static final int TRANSLATION_FROM_TOP = 0x01;private static final int TRANSLATION_FROM_BOTTOM = 0x02;private static final int TRANSLATION_FROM_LEFT = 0x04;private static final int TRANSLATION_FROM_RIGHT = 0x08;//顏色估值器private static ArgbEvaluator sArgbEvaluator = new ArgbEvaluator();/*** 自定義屬性的一些接收的變量*/private int mDiscrollveFromBgColor;//背景顏色變化開始值private int mDiscrollveToBgColor;//背景顏色變化結束值private boolean mDiscrollveAlpha;//是否需要透明度動畫private int mDisCrollveTranslation;//平移值private boolean mDiscrollveScaleX;//是否需要x軸方向縮放private boolean mDiscrollveScaleY;//是否需要y軸方向縮放private int mHeight;//本view的高度private int mWidth;//寬度public void setmDiscrollveFromBgColor(int mDiscrollveFromBgColor) {this.mDiscrollveFromBgColor = mDiscrollveFromBgColor;}public void setmDiscrollveToBgColor(int mDiscrollveToBgColor) {this.mDiscrollveToBgColor = mDiscrollveToBgColor;}public void setmDiscrollveAlpha(boolean mDiscrollveAlpha) {this.mDiscrollveAlpha = mDiscrollveAlpha;}public void setmDisCrollveTranslation(int mDisCrollveTranslation) {this.mDisCrollveTranslation = mDisCrollveTranslation;}public void setmDiscrollveScaleX(boolean mDiscrollveScaleX) {this.mDiscrollveScaleX = mDiscrollveScaleX;}public void setmDiscrollveScaleY(boolean mDiscrollveScaleY) {this.mDiscrollveScaleY = mDiscrollveScaleY;}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);mWidth = w;mHeight = h;}@Overridepublic void onDiscroll(float ratio) {//執(zhí)行動畫ratio:0~1if (mDiscrollveAlpha) {setAlpha(ratio);}if (mDiscrollveScaleX) {setScaleX(ratio);}if (mDiscrollveScaleY) {setScaleY(ratio);}//平移動畫 int值:left,right,top,bottom left|bottomif (isTranslationFrom(TRANSLATION_FROM_BOTTOM)) {//是否包含bottomsetTranslationY(mHeight * (1 - ratio));//height--->0(0代表恢復到原來的位置)}if (isTranslationFrom(TRANSLATION_FROM_TOP)) {//是否包含bottomsetTranslationY(-mHeight * (1 - ratio));//-height--->0(0代表恢復到原來的位置)}if (isTranslationFrom(TRANSLATION_FROM_LEFT)) {setTranslationX(-mWidth * (1 - ratio));//mWidth--->0(0代表恢復到本來原來的位置)}if (isTranslationFrom(TRANSLATION_FROM_RIGHT)) {setTranslationX(mWidth * (1 - ratio));//-mWidth--->0(0代表恢復到本來原來的位置)}//判斷從什么顏色到什么顏色if (mDiscrollveFromBgColor != -1 && mDiscrollveToBgColor != -1) {setBackgroundColor((int) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));}}@Overridepublic void onResetDiscroll() {if (mDiscrollveAlpha) {setAlpha(0);}if (mDiscrollveScaleX) {setScaleX(0);}if (mDiscrollveScaleY) {setScaleY(0);}//平移動畫 int值:left,right,top,bottom left|bottomif (isTranslationFrom(TRANSLATION_FROM_BOTTOM)) {//是否包含bottomsetTranslationY(mHeight);//height--->0(0代表恢復到原來的位置)}if (isTranslationFrom(TRANSLATION_FROM_TOP)) {//是否包含bottomsetTranslationY(-mHeight);//-height--->0(0代表恢復到原來的位置)}if (isTranslationFrom(TRANSLATION_FROM_LEFT)) {setTranslationX(-mWidth);//mWidth--->0(0代表恢復到本來原來的位置)}if (isTranslationFrom(TRANSLATION_FROM_RIGHT)) {setTranslationX(mWidth);//-mWidth--->0(0代表恢復到本來原來的位置)}}private boolean isTranslationFrom(int translationMask) {if (mDisCrollveTranslation == -1) {return false;}//fromLeft|fromeBottom & fromBottom = fromBottomreturn (mDisCrollveTranslation & translationMask) == translationMask;}}我們發(fā)現(xiàn)自定義控件里實現(xiàn)了接口DiscrollvableInterface并重寫了
void onDiscrollve(float ratio)??//根據(jù)比例,執(zhí)行動畫進度
void onResetDiscrollve();//逆向動畫,恢復到初始狀態(tài)。
在這兩個函數(shù)里會根據(jù)自定義屬性值與ratio來執(zhí)行 這個“自定義VIEWGROUP包裹”的動畫,從而內(nèi)部包含的系統(tǒng)控件(如Imageview)等也會跟著動起來。那這兩個函數(shù)是在什么地方調(diào)用的,以及ratio是怎么算出來的,那這個與Scrollview的滑動有關系。那我們就來看一下自定義Scrollview。
2.2自定義Scrollview
public class AnimatorScrollView extends ScrollView {private AnimatorLinerLayout mContent;public AnimatorScrollView(Context context) {super(context);}public AnimatorScrollView(Context context, AttributeSet attrs) {super(context, attrs);}public AnimatorScrollView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);}//渲染完畢之后@Overrideprotected void onFinishInflate() {super.onFinishInflate();mContent = (AnimatorLinerLayout) getChildAt(0);}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);View first = mContent.getChildAt(0);first.getLayoutParams().height = getHeight();}@Overrideprotected void onScrollChanged(int l, int t, int oldl, int oldt) {//監(jiān)聽滑動程度--CHILD從下面冒出來多少距離//需要一個百分比來執(zhí)行動畫,//百分比為 滑出高度/child實際高度=百分比//實際高度int scrollViewHeight = getHeight();//監(jiān)聽滑動的程度---childView從下面冒出來多少距離/childView.getHeight();----0~1:動畫執(zhí)行的百分比ratio//動畫執(zhí)行的百分比ratio控制動畫執(zhí)行for (int i = 0; i < mContent.getChildCount(); i++) {View child = mContent.getChildAt(i);int childHeight = child.getHeight();if (!(child instanceof DiscrollInterface)) {continue;}//接口回掉,傳遞執(zhí)行的百分比給MyFrameLayout//低耦合高內(nèi)聚DiscrollInterface discrollInterface = (DiscrollInterface) child;//child離parent頂部的高度int childTop = child.getTop();//滑出去的這一截高度:t // child離屏幕頂部的高度int absoluteTop = childTop - t;if (absoluteTop <= scrollViewHeight) {//child浮現(xiàn)的高度 = ScrollView的高度 - child離屏幕頂部的高度int visibleGap = scrollViewHeight - absoluteTop;//float ratio = child浮現(xiàn)的高度/child的高度float ratio = visibleGap / (float) childHeight;//確保ratio是在0~1的范圍discrollInterface.onDiscroll(clamp(ratio, 1f, 0f));} else {discrollInterface.onResetDiscroll();}}}/*** 求中間大小*/private float clamp(float value, float max, float min) {return Math.max(Math.min(value, max), min);} }K,所有流程就分析完了。現(xiàn)在總結一下思路:
1. 自定義LinearLayout的addview,讓添加imageview等系統(tǒng)控件前,先在外層包裹一個自定義VIEWGROUP,并賦予它自定義屬性的配置。
2. 自定義VIEWGROUP,接收滑動的ratio來執(zhí)行動畫進度
3. 自定義Scrollview,計算滑動的ratio,并調(diào)用自定義VIEWGROUP里的執(zhí)行動畫函數(shù)。
完整項目
總結
以上是生活随笔為你收集整理的Android系统控件获取自定义属性的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用Kinect制作变身钢铁侠
- 下一篇: 对比“码绘”与“手绘”的区别