使用ScrollView滚动事件打造动画框架ScrollAnimationSherlock


1

一个用来打造上述引导界面动画效果的Scroll框架, 集成进https://github.com/Jerey-Jobs/KeepGank中,作为首次启动的欢迎界面。

工程源码:https://github.com/Jerey-Jobs/ScrollAnimationSherlock

目前支持:

  • 透明度动画与平移动画(四种方向),支持混合调用
  • 背景色渐变设置
  • SherlockLinearLayout与SherlockRelativeLayout提供动画界面的线性布局与相对布局支持
  • SherlockAnimationCallBack提供自定义扩展

如何使用

如何使用

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

  allprojects {
    repositories {
      ...
      maven { url 'https://jitpack.io' }
    }
  }

module's build.gradle (模块的build.gradle)

  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会被默认设置为全屏,因此

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

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 https://github.com/Jerey-Jobs/ScrollAnimationSherlock


本文作者:Anderson/Jerey_Jobs

博客地址 : http://jerey.cn/

简书地址 : Anderson大码渣

github地址 : https://github.com/Jerey-Jobs

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容