自定義屬性動畫框架

先來觀察完整的動畫效果

分析:每個child都可以設置自己的屬性動畫 根據滑動出自身的高度比例來進行開發者設置的屬性動畫(平移 漸變 縮放 透明度...)既然是動畫框架封裝 肯定是希望開發者用的更爽,代碼簡潔 意圖清晰。 

首先設定好自定義的屬性參數

<declare-styleable name="DiscrollView_LayoutParams">
        <attr name="discrollve_alpha" format="boolean"/>
        <attr name="discrollve_scaleX" format="boolean"/>
        <attr name="discrollve_scaleY" format="boolean"/>
        <attr name="discrollve_fromBgColor" format="color"/>
        <attr name="discrollve_toBgColor" format="color"/>
        <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>
    </declare-styleable>

解析爲什麼discrollve_translation 各個參數0x01 0x02 0x04 0x08
0x01 0000 0000 0000 0001 fromTop
0x02 0000 0000 0000 0010 fromBottom
0x04 0000 0000 0000 0100 fromLeft
0x08 0000 0000 0000 1000 fromRight
這樣設置的目的 因爲這個平移可以同時多個方向移動 比如我們希望他 左上方向移動 就可以這樣設置
app:discrollve_translation = "fromTop|fromLeft"
既 它們的值 0000 0000 0000 0101 將來我們判斷的時候 可以這樣判斷
比如判斷是否設置了fromTop  就拿這個值 0101 & 0001fromTop = 0001 如果得出來的值等於fromTop就說明有設置這個值

我們希望開發者用這個屬性動畫框架的時候 只需要對對應的child設置相應屬性參數 將來運行的時候就會執行相應的屬性動畫
理想開發者在佈局文件設定即可 
 

<com.twy.mywearview.MyScrollView xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:discrollve="http://schemas.android.com/apk/res-auto">
    <com.twy.mywearview.MyLinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
       <ImageView
           android:layout_width="match_parent"
           android:layout_height="600dp"
           android:src="@mipmap/tb_bg"
           android:scaleType="centerCrop"/>
        <TextView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:background="#007788"
            android:textColor="@android:color/black"
            android:textSize="25sp"
            android:padding="25dp"
            android:gravity="center"
            android:fontFamily="serif"
            android:text="帶上您的行李箱,準備shopping!"
            discrollve:discrollve_alpha="true"
            tools:ignore="MissingPrefix" />
        <ImageView
            android:layout_width="200dp"
            android:layout_height="120dp"
            android:layout_gravity="top|right"
            discrollve:discrollve_translation="fromLeft|fromBottom"
            discrollve:discrollve_alpha="true"
            android:src="@mipmap/baggage"
            tools:ignore="MissingPrefix" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:textColor="@android:color/black"
            android:textSize="25sp"
            android:padding="25dp"
            android:gravity="center"
            android:fontFamily="serif"
            android:text="準備好相機,這裏有你想象不到的驚喜!"
            discrollve:discrollve_fromBgColor="#ffff00"
            discrollve:discrollve_toBgColor="#88EE66"
            />

        <ImageView
            android:layout_width="220dp"
            android:layout_height="110dp"
            android:layout_gravity="right"
            android:src="@mipmap/camera"
            discrollve:discrollve_translation="fromRight" />

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:padding="20dp"
            android:fontFamily="serif"
            android:gravity="center"
            android:background="#D97C1F"
            android:text="這次淘寶造物節真的來了,我們都在造,你造嗎?\n
							7月22日-7月24日\n
							上海世博展覽館\n
							在現場,我們造什麼?"
            android:textSize="23sp"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_translation="fromBottom" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/sweet"
            discrollve:discrollve_scaleX="true"
            discrollve:discrollve_scaleY="true"  />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/shoes"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_scaleX="true"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromLeft|fromBottom" />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/shoes"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromRight|fromTop" />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/sweet"
            discrollve:discrollve_alpha="true"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromLeft" />
        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="20dp"
            android:layout_gravity="center"
            android:src="@mipmap/camera"
            discrollve:discrollve_scaleY="true"
            discrollve:discrollve_translation="fromLeft" />
    </com.twy.mywearview.MyLinearLayout>
</com.twy.mywearview.MyScrollView>

理想中 我們只需要這樣寫 運行起來 對應child就會自己根據用戶滑動自身可見高度比例 執行自身設置的屬性動畫
問題1:這樣的話 首先我得所對應得child對象本身 設置屬性動畫
問題2:獲取到用戶對所對應child對象設置屬性的參數
問題3:監聽用戶上下滑動
針對上述問題 我們必須自定義一個LinearLayout和FramLayout容器 
自定義FramLayout容器 是用來包裹有設置屬性動畫的控件 目的是爲了在該控件 設置對應的屬性動畫
自定義LinerLayout容器 有兩個目的 獲取到自定義屬性參數 在addView實現偷天換日 首先將child的添加到我們自定義
FramLayout容器 再將該容器添加到LinerLayout容器 
問題3我們需要自定ScrollView以便於我們監聽用戶上下滑動

先來看看自定義的MyFrameLayout 此定義的控件主要是用來包裹有設置屬性參數的控件 是用來執行屬性動畫 這樣的話
我們讓它實現一個接口 用來規範 執行動畫 和恢復原來的樣子
 

public interface DiscrollInterface {
    /**
     * 當滑動的時候調用該方法,用來控制裏面的控件執行相應的動畫
     * @param ratio 動畫執行的百分比(child view畫出來的距離百分比)
     */
    public void onDiscroll(float ratio);

    /**
     * 重置動畫--讓view所有的屬性都恢復到原來的樣子
     */
    public void onResetDiscroll();
}

將來要執行動畫 就執行child.onDiscroll(ratio)

public class MyFrameLayout extends FrameLayout implements DiscrollInterface {

    //保存自定義屬性
    //定義很多的自定義屬性
    /**
     *  <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 MyFrameLayout(Context context) {
        super(context);
    }

    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;
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    @Override
    public void onDiscroll(float ratio) {
        if(mDiscrollveAlpha){
            setAlpha(ratio);
        }
        if(mDiscrollveScaleX){
            setScaleX(ratio);
        }
        if(mDiscrollveScaleY){
            setScaleY(ratio);
        }
        if(isTranslationFrom(TRANSLATION_FROM_LEFT)){
            setTranslationX(-mWidth*(1-ratio));//mWidth--->0(0代表恢復到原來的位置)
        }
        if(isTranslationFrom(TRANSLATION_FROM_RIGHT)){
            setTranslationX(mWidth*(1-ratio));
        }
        if(isTranslationFrom(TRANSLATION_FROM_TOP)){
            setTranslationY(-mHeight*(1-ratio));////mHeight--->0(0代表恢復到原來的位置)
        }
        if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){
            setTranslationY(mHeight*(1-ratio));
        }

        //判斷從什麼顏色到什麼顏色
        if(mDiscrollveFromBgColor!=-1&&mDiscrollveToBgColor!=-1){
            setBackgroundColor((int) sArgbEvaluator.evaluate(ratio, mDiscrollveFromBgColor, mDiscrollveToBgColor));
        }

    }

    @Override
    public void onResetDiscroll() {
        if(mDiscrollveAlpha){
            setAlpha(0);
        }
        if(mDiscrollveScaleX){
            setScaleX(0);
        }
        if(mDiscrollveScaleY){
            setScaleY(0);
        }
        //平移動畫  int值:left,right,top,bottom    left|bottom
        if(isTranslationFrom(TRANSLATION_FROM_BOTTOM)){//是否包含bottom
            setTranslationY(mHeight);//height--->0(0代表恢復到原來的位置)
        }
        if(isTranslationFrom(TRANSLATION_FROM_TOP)){//是否包含bottom
            setTranslationY(-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 = fromBottom
        return (mDisCrollveTranslation & translationMask) == translationMask;
    }

}

自定義LinearLayout 主要兩個目的 爲每個子控件獲取到開發者設定的屬性參數  另外實現偷天換日 就是將設有屬性的控件用 自定義的FramLayout的控件包裹起來 再將此控件添加到Linerlayout裏面去
 

public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrientation(VERTICAL);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new MyLayoutParams(getContext(),attrs);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        MyLayoutParams p = (MyLayoutParams) params;
        if(isDiscrollvable(p)){
            //偷天換日
            MyFrameLayout mf = new MyFrameLayout(getContext());
            mf.addView(child);
            mf.setmDiscrollveAlpha(p.mDiscrollveAlpha);
            mf.setmDiscrollveFromBgColor(p.mDiscrollveFromBgColor);
            mf.setmDiscrollveToBgColor(p.mDiscrollveToBgColor);
            mf.setmDiscrollveScaleX(p.mDiscrollveScaleX);
            mf.setmDiscrollveScaleY(p.mDiscrollveScaleY);
            mf.setmDisCrollveTranslation(p.mDisCrollveTranslation);
            super.addView(mf,params);
        }else {
            super.addView(child, params);
        }
    }

    private boolean isDiscrollvable(MyLayoutParams p){
        return p.mDiscrollveAlpha ||
                p.mDiscrollveScaleX ||
                p.mDiscrollveScaleY ||
                p.mDisCrollveTranslation != -1 ||
                (p.mDiscrollveFromBgColor !=-1 && p.mDiscrollveToBgColor != -1);
    }


    public class MyLayoutParams extends LinearLayout.LayoutParams{
        public int mDiscrollveFromBgColor;//背景顏色變化開始值
        public int mDiscrollveToBgColor;//背景顏色變化結束值
        public boolean mDiscrollveAlpha;//是否需要透明度動畫
        public int mDisCrollveTranslation;//平移值
        public boolean mDiscrollveScaleX;//是否需要x軸方向縮放
        public boolean mDiscrollveScaleY;//是否需要y軸方向縮放
        public MyLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            //解析attrs得到自定義的屬性,保存
            TypedArray a = getContext().obtainStyledAttributes(attrs,R.styleable.DiscrollView_LayoutParams);
            mDiscrollveAlpha = 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();
        }
    }

}

最後自定義ScrollView 監聽用戶滑動 算出child浮現高度 和child高度比例 0~1 以便根據該值 去執行該控件的屬性動畫

public class MyScrollView extends ScrollView {
    private MyLinearLayout mContent;
    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mContent = (MyLinearLayout) getChildAt(0);
    }

    @Override
    protected 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();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        int scrollViewHeight = getHeight();
        //監聽滑動的程度---childView從下面冒出來多少距離/childView.getHeight();----0~1:動畫執行的百分比ratio
        //動畫執行的百分比ratio控制動畫執行
        for(int i = 0;i<mContent.getChildCount();i++){
            View child = mContent.getChildAt(i);
            if(!(child instanceof DiscrollInterface)){
                continue;
            }
            int childHeight = child.getHeight();
            //接口回掉,傳遞執行的百分比給MyFrameLayout
            //低耦合高內聚
            DiscrollInterface discrollInterface = (DiscrollInterface) child;
            //child離parent頂部的高度
            int childTop = child.getTop();
            //滑出去的這一截高度:t
//            child離屏幕頂部的高度
            int absoluteTop = childTop -t;
            if(absoluteTop<=scrollViewHeight){
                //child浮現的高度 = ScrollView的高度 - child離屏幕頂部的高度
                int visibleGap = scrollViewHeight - absoluteTop;
                //float ratio = child浮現的高度/child的高度
                float ratio = visibleGap / (float) childHeight;
                //確保ratio是在0~1的範圍
                discrollInterface.onDiscroll(clamp(ratio,1f,0f));
            }else {
                discrollInterface.onResetDiscroll();
            }
        }
    }

    //求三個數的中間大小的一個數。
    public static float clamp(float value, float max, float min){
        return Math.max(Math.min(value, max), min);
    }
}

ratio值的計算

以上就是該自定義屬性框架的分析

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章