使用ScrollView滾動事件打造動畫框架ScrollAnimationSherlock


先看效果。java

ScrollAnimationSherlock是一個用來打造上述引導界面動畫效果的Scroll框架,集成進github.com/Jerey-Jobs/…中,做爲首次啓動的歡迎界面。android

工程源碼:github.com/Jerey-Jobs/…git

目前支持:github

  • 透明度動畫與平移動畫(四種方向),支持混合調用
  • 背景色漸變設置
  • SherlockLinearLayout與SherlockRelativeLayout提供動畫界面的線性佈局與相對佈局支持
  • SherlockAnimationCallBack提供自定義擴展

如何使用app

如何使用框架

project's build.gradle (工程下的 build.gradle)maven

allprojects {
    repositories {
      ...
      maven { url 'https://jitpack.io' }
    }
  }複製代碼

module's build.gradle (模塊的build.gradle)ide

dependencies {
          compile 'com.github.Jerey-Jobs:ScrollAnimationSherlock:1.0'
  }複製代碼

項目中:

頂層佈局:cn.jerey.animationlib.SherlockScrollView,內嵌一個SherlockLinearLayout佈局

<cn.jerey.animationlib.SherlockScrollView android:layout_width="match_parent" android:layout_height="match_parent">
        <cn.jerey.animationlib.SherlockLinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="vertical">

            <include layout="@layout/splash_layout"></include>

        </cn.jerey.animationlib.SherlockLinearLayout>
    </cn.jerey.animationlib.SherlockScrollView>複製代碼

SherlockLinearLayout的第一個子View會被默認設置爲全屏,所以gradle

 複製代碼

splash_layout中完成第一個界面的搭建

這是上圖的splash_layout

接下來就是使用SherlockLinearLayoutSherlockRelativeLayout進行其餘界面搭建。demo中使用的是SherlockRelativeLayout其中放置了四個子View,並設置了相應動畫。

<cn.jerey.animationlib.SherlockRelativeLayout android:layout_width="match_parent" android:layout_height="300dp">


     <ImageView android:id="@+id/moon" android:layout_width="100dp" android:layout_height="100dp" android:layout_marginLeft="30dp" android:layout_marginTop="30dp" android:background="@drawable/moon" app:animation_alpha="true" app:animation_translation="left"/>

     <ImageView android:id="@+id/astronaut" android:layout_width="50dp" android:layout_height="50dp" android:layout_marginLeft="120dp" android:layout_marginTop="28dp" android:background="@drawable/astronaut" app:animation_alpha="true" app:animation_translation="right|bottom"/>

     <ImageView android:id="@+id/imageView" android:layout_width="200dp" android:layout_height="180dp" android:layout_alignParentRight="true" android:layout_marginTop="100dp" android:background="@drawable/planet_earth_1" app:animation_alpha="true" app:animation_translation="right|bottom"/>

     <ImageView android:layout_width="100dp" android:layout_height="100dp" android:layout_alignParentBottom="true" android:layout_marginLeft="30dp" android:background="@drawable/rocket_1" app:animation_alpha="true"/>

 </cn.jerey.animationlib.SherlockRelativeLayout>複製代碼

原理

總的原理一句話:賦予每一個須要進行動畫變換的View動畫屬性,並根據位置改變屬性。

如何作到

咱們須要作的事情有

  1. 如何肯定某個View須要進行動畫變換
  2. 肯定後如何賦予動畫屬性
  3. 如何分發動畫變換事件,讓子View進行變換

肯定是否須要動畫變換

ViewGroup的addView方法,該方法是添加子View的時候調用的。

public void addView(View child, int index, ViewGroup.LayoutParams params)複製代碼

咱們須要在這邊進行子View的判斷,如何判斷呢,咱們能夠參照support包的設計,添加app屬性,

咱們去定義幾個屬性

<attr name="animation_alpha" format="boolean" />//是否支持透明度動畫;
       <attr name="animation_scaleX" format="boolean" />//是否支持X軸縮放動畫;
       <attr name="animation_scaleY" format="boolean" />//是否支持Y軸縮放動畫;
       <attr name="bgColorStart" format="color" />//背景漸變顏色的開始顏色值;
       <attr name="bgColorEnd" format="color" />//背景漸變顏色的結束顏色值,與bgColorStart成對出現;
       <attr name="animation_translation">//移動動畫,是一個枚舉類型,支持上下左右四種值。
           <flag name="left" value="0x01" />
           <flag name="top" value="0x02" />
           <flag name="right" value="0x04" />
           <flag name="bottom" value="0x08" />
       </attr>複製代碼

在addView時候,經過layoutParams參數來判斷,那麼這裏的LayoutParams是咱們自定義的,繼承於系統的LayoutParams,
不過在其構造方法時追加參數解析。

/** * 不能將此LayoutParams抽象出來, 其繼承的是本身內部類的Params */
public class RelativeLayoutParams extends LayoutParams {
    //是否支持透明度;
    public boolean mAlphaSupport;
    //是否支持X Y軸縮放;
    public boolean mScaleXSupport;
    public boolean mScaleYSupport;

    //顏色變化的起始值;
    public int mBgColorStart;
    public int mBgColorEnd;
    //移動值;
    public int mTranslationValue;

    public RelativeLayoutParams(Context c, AttributeSet attrs) {
        super(c, attrs);
        TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.MyFrameLayout);
        mAlphaSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_animation_alpha, false);
        mBgColorStart = typedArray.getColor(R.styleable.MyFrameLayout_bgColorStart, -1);
        mBgColorEnd = typedArray.getColor(R.styleable.MyFrameLayout_bgColorEnd, -1);
        mScaleXSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_animation_scaleX, false);
        mScaleYSupport = typedArray.getBoolean(R.styleable.MyFrameLayout_animation_scaleY, false);
        mTranslationValue = typedArray.getInt(R.styleable.MyFrameLayout_animation_translation, -1);
        typedArray.recycle();
    }

    /** * 判斷當前params是否包含自定義屬性; * * @return */
    public boolean isHaveMyProperty() {
        if (mAlphaSupport || mScaleXSupport || mScaleYSupport || (mBgColorStart != -1 && mBgColorEnd != -1) || mTranslationValue != -1) {
            return true;
        }
        return false;
    }
}複製代碼

這樣咱們在addView時可以拿到這個params,而且裏面已經解析了是否支持動畫了。

@Override
public void addView(View child, int index, ViewGroup.LayoutParams params) {
    RelativeLayoutParams myLayoutParams = (RelativeLayoutParams) params;
    if (myLayoutParams.isHaveMyProperty())複製代碼

賦予動畫變換屬性

咱們的View是不大可能本身動的,並且咱們也無法去改view的代碼。這些view都是系統的view,
這樣咱們只能說讓view有一個父類,去操做它了。或者說。給View「僞增長」一個方法,使其接收到咱們的移動事件後,可以進行動畫變換。

如何增長呢?

咱們在解析view的屬性時,即addView時,在其外面包裹一層父View,我稱之爲 Frame。 使用FrameView去包裹它,
固然須要注意的是,爲了讓view可以直接完整的進行動畫顯示。咱們須要設置各個父類的ClipChildren屬性爲false。

所以封裝了SherlockFrame,其繼承於FrameLayout,並實現咱們的位移callback接口的,注意只有實現了咱們的位移回調接口的。咱們分發事件時纔會分發。
一樣,這個接口提供了自定義擴展,能夠本身編寫實現這個接口的自定義view,一樣會接收到位移分發。

public class SherlockFrame extends FrameLayout implements SherlockAnimationCallBack{
    //從哪一個方向開始動畫;
    private static final int TRANSLATION_LEFT = 0x01;
    private static final int TRANSLATION_TOP = 0x02;
    private static final int TRANSLATION_RIGHT = 0x04;
    private static final int TRANSLATION_BOTTOM = 0x08;

    //是否支持透明度;
    private boolean mAlphaSupport;
    //顏色變化的起始值;
    private int mBgColorStart;
    private int mBgColorEnd;

    //是否支持X Y軸縮放;
    private boolean mScaleXSupport;
    private boolean mScaleYSupport;
    //移動值;
    private int mTranslationValue;
    //當前View寬高;
    private int mHeight, mWidth;
}複製代碼

SherlockFrame的這些屬性,會在addview的時候進行賦值。

SherlockAnimationCallBack回調excuteanimation方法時,SherlockFrame就根據自身的屬性狀況進行動畫變換。

事件分發

做爲scrollView,滾動事件的分發確定是在onScrollChanged了。在這裏面進行滾動事件分發

parseViewGroup方法有點講究了,這是一個遞歸遍歷子view,看其是否實現了SherlockAnimationCallBack接口,若沒有則去判斷是不是ViewGroup,如果的話則繼續遞歸遍歷其子view。

如果實現了SherlockAnimationCallBack接口的view。咱們要根據其距離頂部的高度來計算動畫應該執行百分之多少。咱們能夠經過view的getTop方法,這個方法是獲得view距離其父view的頂部的距離。
所以咱們還要進行遞歸傳遞。

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    super.onScrollChanged(l, t, oldl, oldt);
    parseViewGroup(mLinearLayout, l, t, oldl, oldt, true, 0);
}

/** * @param linearLayout * @param l * @param t * @param oldl * @param oldt * @param isRootLinearLayout 是不是頂層佈局 * @param getTop 距離頂部高度 */
private void parseViewGroup(ViewGroup linearLayout, int l, int t, int oldl, int oldt, boolean isRootLinearLayout, int getTop) {
    int scrollViewHeight = getHeight();
    Log.w(TAG, "linearLayout.getChildCount()" + linearLayout.getChildCount());
    for (int i = 0; i < linearLayout.getChildCount(); i++) {
        //若是子控件不是MyFrameLayout則循環下一個子控件;
        View child = linearLayout.getChildAt(i);

        // 若不是動畫控件,則進入判斷是不是ViewGroup,是的話遞歸其子view.不是的話則判斷下一個
        if (!(child instanceof SherlockAnimationCallBack)) {
            if (child instanceof ViewGroup) {
                Log.d(TAG, "parseViewGroup: 該View不是FrameLayout,是ViewGroup: " + child
                        .getClass().getName());
                parseViewGroup((ViewGroup) child, l, t, oldl, oldt, false,
                        child.getTop() + getTop);
            }
            continue;
        }
        //如下爲執行動畫邏輯;
        SherlockAnimationCallBack myCallBack = (SherlockAnimationCallBack) child;
        //獲取子View高度;
        int childHeight = child.getHeight();
        //子控件到父控件的距離;
        int childTop = child.getTop();
        if (!isRootLinearLayout) {
            childTop += getTop;
        }
        //滾動過程當中,子View距離父控件頂部距離;
        int childAbsluteTop = childTop - t;
        //進入了屏幕
        if (childAbsluteTop <= scrollViewHeight) {
            //當前子控件顯示出來的高度;
            int childShowHeight = scrollViewHeight - childAbsluteTop - 100 ;
            float moveRadio = childShowHeight / (float) childHeight;//這裏必定要轉化成float類型;
            //執行動畫;
            myCallBack.excuteanimation(getMiddleValue(moveRadio, 0, 1));
        } else {
            //沒在屏幕內,恢復數據;
            myCallBack.resetViewanimation();
        }
    }
}複製代碼

動畫執行

有了事件的分發了。咱們只須要在excuteanimation的回調中實現咱們的動畫便可了。默認的SherlockFrame已經實現了一些了。若要強大的自定義動畫效果,實現這個接口便可。

public interface SherlockAnimationCallBack {
    /** * 執行自定義動畫方法; */
    void excuteanimation(float moveRadio);

    /** * 恢復初始狀態; */
    void resetViewanimation();
}複製代碼

優化

有了上面的幾步,咱們的動畫已經能正常跑起來了,不過因爲從最下面一開始就執行動畫,有點感受過快了。所以在分發位置移動事件的時候,在計算當前子控件顯示出來的高度時減小了100, 這樣動畫就能延遲,看起來更加天然

int childShowHeight = scrollViewHeight - childAbsluteTop - 100 ;複製代碼

總結

還有不少不完善的地方,你們多多指教。

歡迎star github.com/Jerey-Jobs/…


本文做者:Anderson/Jerey_Jobs

博客地址 : jerey.cn/

簡書地址 : Anderson大碼渣

github地址 : github.com/Jerey-Jobs

相關文章
相關標籤/搜索