使用一种独特的方式实现动画框架

使用一种独特的方式实现动画框架

在App中,为了展现的效果更佳,设计师通常都会加入一些动画的元素在里面,在现在的各类应用中,动画也变得越来越常见,今天,分享的是一种实现动画框架的思路,具体的效果图可以参考下面这个视差动画(只是例子):
这里写图片描述

从效果图看,要实现该效果,应该是不难的,正常操作都是给每一个View设置动画
如:view.setScaleX()……
但这样的话,虽然是效果实现了,但是复用性可以说为0,我们需要在每个用到的地方,都去设置他的动画属性。

下面,就来介绍一种很特别的实现方式:
我们可以在每个需要实现动画的View中加入自定义的参数,具体如下:

<ImageView
      android:layout_width="200dp"
      android:layout_height="120dp"
      android:layout_gravity="top|right"
      parallax:parallax_translation="fromLeft|fromBottom"
      parallax:parallax_alpha="true"
      android:src="@mipmap/baggage" />

在如上的代码中,我们除了看到平常使用的控件属性外,还有一些使我们自己定义的属性:

<!-- 平移 -->
parallax:parallax_translation="fromLeft|fromBottom"
<!-- 背景的透明度 -->
parallax:parallax_alpha="true"

这样一来,自定义的属性是加上了,但是,问题就来了,我们知道,只有系统控件才能识别系统的属性,自定义控件才能识别自定义的属性,这系统控件也不识别自定义属性啊?

别慌,方法还是有的,我们来看一个图:
这里写图片描述

我们可以在系统加载渲染控件的时候,偷偷地做件事情,给需要添加动画的空间外面包裹一层frameLayout,这样的话,当frameLayout执行动画的时候,其里面的子空间也就跟着一起执行动画了!

所以,我们需要自定义一个FrameLayout,让他来帮助我们完成动画。
那么问题又来了,我们在什么时机去给控件包裹这层FrameLayout呢?
答案当然是在控件的父控件去添加该View的时候,所以,我们可以在父控件的addView方法中来做处理,既然这样,我们就需要来自定义父容器,去干扰父容器添加子View的过程。在androi中,但弗雷去添加子view之前,会去解析子view的LayoutParams,该方法为generateLayoutParams(AttributeSet attrs)方法。具体的思路就是这样。
俗话说得好,自古~~~,唯有代码得人心。

自定义ViewGroup,这里以LinearLayout为例

public class ParallaxLinearLayout extends LinearLayout {

    public ParallaxLinearLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public void addView(View child, ViewGroup.LayoutParams params) {
        ParallaxLayoutParams layoutParams = (ParallaxLayoutParams) params;
        if(needParallax(layoutParams)){
            //
            ParallaxFrameLayout frameLayout = new ParallaxFrameLayout(getContext());
            frameLayout.addView(child);
            frameLayout.setParallaxAlpha(layoutParams.parallaxAlpha);
            frameLayout.setParallaxFromBgColor(layoutParams.parallaxFromBgColor);
            frameLayout.setParallaxToBgColor(layoutParams.parallaxToBgColor);
            frameLayout.setParallaxScaleX(layoutParams.parallaxScaleX);
            frameLayout.setParallaxScaleY(layoutParams.parallaxScaleY);
            frameLayout.setParallaxTranslation(layoutParams.parallaxTranslation);
            super.addView(frameLayout,params);
        }else{
            super.addView(child, params);
        }
    }

    /**
     * 在addView之前执行
     * 重写该方法  在addView之前获取自定义属性
     * @param attrs
     * @return
     */
    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new ParallaxLayoutParams(getContext(),attrs);
    }

    //判断是否含有视差效果  进而判断是否需要进行处理
    private boolean needParallax(ParallaxLayoutParams layoutParams){
        return layoutParams.parallaxAlpha ||
                layoutParams.parallaxScaleX ||
                layoutParams.parallaxScaleY ||
                (layoutParams.parallaxTranslation != -1) ||
                (layoutParams.parallaxFromBgColor != -1 && layoutParams.parallaxToBgColor != -1);
    }

    public class ParallaxLayoutParams extends LinearLayout.LayoutParams{

        /**
         *
         * @param c
         * @param attrs
         */
        private boolean parallaxAlpha = false;//是否需要设置透明度的渐变 默认设置为false(不需要)
        private int parallaxTranslation = -1;//是否需要设置平移动画 默认设置为-1(不需要)
        private int parallaxFromBgColor = -1;//是否需要设置颜色的渐变 默认设置为-1(不需要)
        private int parallaxToBgColor = -1;//是否需要设置颜色的渐变 默认设置为-1(不需要)
        private boolean parallaxScaleX = false;//是否需要设置X轴的缩放 默认设置为false(不需要)
        private boolean parallaxScaleY = false;//是否需要设置Y轴的缩放 默认设置为false(不需要)

        public ParallaxLayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
            //解析我们的自定义属性
            TypedArray parallaxArray = c.obtainStyledAttributes(attrs,R.styleable.parallax_attrs);
            parallaxAlpha = parallaxArray.getBoolean(R.styleable.parallax_attrs_parallax_alpha,parallaxAlpha);
            parallaxTranslation = parallaxArray.getInt(R.styleable.parallax_attrs_parallax_translation,parallaxTranslation);
            parallaxFromBgColor = parallaxArray.getColor(R.styleable.parallax_attrs_parallax_fromBgColor,parallaxFromBgColor);
            parallaxToBgColor = parallaxArray.getColor(R.styleable.parallax_attrs_parallax_toBgColor,parallaxToBgColor);
            parallaxScaleX = parallaxArray.getBoolean(R.styleable.parallax_attrs_parallax_scaleX,parallaxScaleX);
            parallaxScaleY = parallaxArray.getBoolean(R.styleable.parallax_attrs_parallax_scaleY,parallaxScaleY);
            parallaxArray.recycle();
        }
    }
}

自定义的frameLayout

public class ParallaxFrameLayout extends FrameLayout implements ParallaxInterface{

    private boolean parallaxAlpha;
    private int parallaxTranslation;
    private int parallaxFromBgColor;
    private int parallaxToBgColor;
    private boolean parallaxScaleX;
    private boolean parallaxScaleY;

    private int height;
    private int width;

    private int fromLeft = 0x01;
    private int fromTop = 0x02;
    private int fromRight = 0x04;
    private int fromBottom = 0x08;

    //颜色估值器
    private ArgbEvaluator mArgbEvaluator = new ArgbEvaluator();

    public ParallaxFrameLayout(@NonNull Context context) {
        super(context);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        this.width = w;
        this.height = h;
    }

    public void setParallaxAlpha(boolean parallaxAlpha) {
        this.parallaxAlpha = parallaxAlpha;
    }

    public void setParallaxTranslation(int parallaxTranslation) {
        this.parallaxTranslation = parallaxTranslation;
    }

    public void setParallaxFromBgColor(int parallaxFromBgColor) {
        this.parallaxFromBgColor = parallaxFromBgColor;
    }

    public void setParallaxToBgColor(int parallaxToBgColor) {
        this.parallaxToBgColor = parallaxToBgColor;
    }

    public void setParallaxScaleX(boolean parallaxScaleX) {
        this.parallaxScaleX = parallaxScaleX;
    }

    public void setParallaxScaleY(boolean parallaxScaleY) {
        this.parallaxScaleY = parallaxScaleY;
    }

    private boolean getTranslationFrom(int value){
        if(value == -1){
            return false;
        }
        return (parallaxTranslation & value) == value;
    }

    /**
     *
     * @param ratio
     */
    @Override
    public void onShowParallax(float ratio) {
        //alpha动画
        if(parallaxAlpha){
            setAlpha(ratio);
        }
        //背景的颜色渐变
        if(parallaxFromBgColor != -1 && parallaxToBgColor != -1){
            setBackgroundColor((Integer) mArgbEvaluator.evaluate(ratio,parallaxFromBgColor,parallaxToBgColor));
        }
        //设置X轴缩放
        if(parallaxScaleX){
            setScaleX(ratio);
        }
        //设置Y轴缩放
        if(parallaxScaleY){
            setScaleY(ratio);
        }
        //设置平移动画
        if(getTranslationFrom(fromTop)){
            setTranslationY(-height*(1-ratio));
        }
        if(getTranslationFrom(fromBottom)){
            setTranslationY(height*(1-ratio));
        }
        if(getTranslationFrom(fromLeft)){
            setTranslationX(-width*(1-ratio));
        }
        if(getTranslationFrom(fromRight)){
            setTranslationX(width*(1-ratio));
        }
    }

    @Override
    public void onRestoreParallax() {
        //alpha动画
        if(parallaxAlpha){
            setAlpha(0);
        }
        //背景的颜色渐变
        if(parallaxFromBgColor != -1 && parallaxToBgColor != -1){
            setBackgroundColor((Integer) mArgbEvaluator.evaluate(0,parallaxFromBgColor,parallaxToBgColor));
        }
        //设置X轴缩放
        if(parallaxScaleX){
            setScaleX(0);
        }
        //设置Y轴缩放
        if(parallaxScaleY){
            setScaleY(0);
        }
        //设置平移动画
        if(getTranslationFrom(fromTop)){
            setTranslationY(-height);
        }
        if(getTranslationFrom(fromBottom)){
            setTranslationY(height);
        }
        if(getTranslationFrom(fromLeft)){
            setTranslationX(-width);
        }
        if(getTranslationFrom(fromRight)){
            setTranslationX(width);
        }
    }
}

定义一个接口,用来回调当前的状态(Ratio)

public interface ParallaxInterface {

    //执行视差动画
    public void onShowParallax(float ratio);
    //状态复原
    public void onRestoreParallax();
}

最后,在我们的例子中,由于需要一个上下滑动,根据滑动来执行相应的动画,所以我们再来自定义一个ScrollView:

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

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

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //为了页面美观  将第一个子View的高设置为ScrollView的高度(覆盖全屏)
        View firstView = mContent.getChildAt(0);
        firstView.getLayoutParams().height = getHeight();
        Log.i("justh","viewHeight--->"+firstView.getLayoutParams().height+"scrollViewHeight------->"+getHeight());
        invalidate();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        //计算ratio = 当前控件显示高度/控件高度
        int scrollViewHeight = getHeight();
        for(int i = 0;i<mContent.getChildCount();i++){
            View child = mContent.getChildAt(i);
            int childHeight = child.getHeight();
            if(!(child instanceof ParallaxInterface)){
                continue;
            }
            ParallaxInterface parallaxInterface = (ParallaxInterface) child;
            //获取child距离父容器顶部的距离
            int childTop = child.getTop();
            //t为先屏幕外滑动了多少
            int absoluteTop = childTop - t;
            if(absoluteTop <= scrollViewHeight){
                //获取到child的可见距离
                int visibleGap = scrollViewHeight - absoluteTop;
                //计算除child的可见距离的百分比
                float ratio = visibleGap/(float) childHeight;
                //根据百分比执行动画
                parallaxInterface.onShowParallax(clamp(ratio,1f,0f));
            }else{
                //恢复动画
                parallaxInterface.onRestoreParallax();
            }
        }
    }

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

还有布局:

<?xml version="1.0" encoding="utf-8"?>
<com.example.dell.parallaxanimation.ParallaxScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:parallax="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.dell.parallaxanimation.MainActivity">


    <com.example.dell.parallaxanimation.ParallaxLinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="700dp"
            android:scaleType="centerCrop"
            android:src="@mipmap/tb_bg" />

        <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!"
            parallax:parallax_alpha="true"
            />

        <ImageView
            android:layout_width="200dp"
            android:layout_height="120dp"
            android:layout_gravity="top|right"
            parallax:parallax_translation="fromLeft|fromBottom"
            parallax:parallax_alpha="true"
            android:src="@mipmap/baggage" />

        <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="准备好相机,这里有你想象不到的惊喜!"
            parallax:parallax_fromBgColor="#ffff00"
            parallax:parallax_toBgColor="#88EE66"
            />

        <ImageView
            android:layout_width="220dp"
            android:layout_height="110dp"
            android:layout_gravity="right"
            android:src="@mipmap/camera"
            parallax:parallax_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="这次淘宝造物节真的来了,我们都在造,你造吗?\n7月22日-7月24日\n上海世博展览馆\n在现场,我们造什么?"
            android:textSize="23sp"
            parallax:parallax_alpha="true"
            parallax:parallax_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"
            parallax:parallax_scaleX="true"
            parallax:parallax_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"
            parallax:parallax_alpha="true"
            parallax:parallax_scaleX="true"
            parallax:parallax_scaleY="true"
            parallax:parallax_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"
            parallax:parallax_alpha="true"
            parallax:parallax_scaleY="true"
            parallax:parallax_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"
            parallax:parallax_alpha="true"
            parallax:parallax_scaleY="true"
            parallax:parallax_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"
            parallax:parallax_scaleY="true"
            parallax:parallax_translation="fromLeft" />
    </com.example.dell.parallaxanimation.ParallaxLinearLayout>

</com.example.dell.parallaxanimation.ParallaxScrollView>

自定义属性:

<declare-styleable name="parallax_attrs">
        <!-- 透明度 -->
        <attr name="parallax_alpha" format="boolean"/>
        <!-- 平移 -->
        <attr name="parallax_translation"/>
        <!-- 颜色渐变 -->
        <attr name="parallax_fromBgColor" format="color"/>
        <attr name="parallax_toBgColor" format="color"/>

        <!-- 缩放 -->
        <attr name="parallax_scaleX" format="boolean"/>
        <attr name="parallax_scaleY" format="boolean"/>
    </declare-styleable>

    <attr name="parallax_translation">
        <flag name="fromLeft" value="0x01"/>
        <flag name="fromTop" value="0x02"/>
        <flag name="fromRight" value="0x04"/>
        <flag name="fromBottom" value="0x08"/>
    </attr>

好了 到这里呢,差不多就结束了,但最后想记录一个小知识点:
在我们的自定义属性中,有如下的赋值情况:

parallax:parallax_translation="fromLeft|fromBottom"

我们在代码中,同事加入了两个值,fromLeft和fromBottom

这个在我们自定义View的解析属性中,我们需要如何来获取其含有哪些值呢?
首先:
我的解决思路是:1:我们的自定义属性的值,都给其设置为2的几次幂,这样换算成二进制就是: 00000001
00000010
00000100
00001000
在我们设置自定义属性值的时候,我们使用了|(或)运算,所以在我们获得的值,也是经过或运算得到的值,后面,我们只需要使用原值(如上面提到的00000001)与获取到的值进行与运算,当结果与原值相等时,就说明我们设置了该属性,反之则没有。

最后是完整的代码链接:
https://gitee.com/Justh_Share/ParallaxAnimation/attach_files/download?i=114299&u=http%3A%2F%2Ffiles.git.oschina.net%2Fgroup1%2FM00%2F02%2FBF%2FPaAvDFpbAOqAG-GZANZCsFqvyAc966.rar%3Ftoken%3D052b96377b7d42717b290ecd3e690533%26ts%3D1515913450%26attname%3DParallaxAnimation.rar

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值