属性动画缺陷与改进

3,131 阅读5分钟

属性动画缺点与改进

1、缺点

属性动画用起来确实很爽,很多动画效果都可以使用属性动画实现。下面看几个典型的使用场景:

1.1、代码难复用

view.animate()
    .traslationX(10,20)
    .alpha(0,1)
    .start();

Animator animator = ObjectAnimator.ofFloat(view,"TranslationX",10,20,30);
animator.start();

代码写起来很简洁,直观。但是难以复用,如果另一个地方我需要使用相同效果的动画,还得再写一次一模一样的代码。

1.2、无法获取尺寸信息

很多时候我们需要根据一些尺寸信息来做动画:

public class MainActivity extends AppCompatActivity {

    private TextView mTextView;
    private LinearLayout mLinearLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

         mTextView = (TextView)findViewById(R.id.txt);
         mLinearLayout = (LinearLayout)findViewById(R.id.txt);

         //mLinearLayout 是 mTextView 的父布局,
         //移动 mTextView,使其距离 mLinearLayout 右侧 10px
         int distance = mLinearLayout.getWidth() - 10 - mTextView.getRight();

         mTextView.animate()
                .TranslationX(distance)
                .start();

    }
}

上面的代码并不能按预想的样子运行,因为 view 的绘制和 Activity的创建不在相同的线程,在 onCreate() 中无法获取 view 的尺寸信息。但是很多时候,我们需要一些尺寸信息来做动画。系统提供的 API,尺寸信息单位都是 px,而不是常用的 dp。

1.3、复杂动画实现不够简洁

AnimatorSet 可以将多个 Animator 一起或顺序执行,但是多个 AnimatorSet 要顺序执行,就只有这样写了:

 mAnimatorSet1.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {

                }

                @Override
                public void onAnimationEnd(Animator animator) {
                     mAnimatorSet2.addListener(new Animator.AnimatorListener() {
                         @Override
                         public void onAnimationStart(Animator animator) {

                         }

                         @Override
                         public void onAnimationEnd(Animator animator) {
                                    mAnimatorSet3.start()
                         }

                         @Override
                         public void onAnimationCancel(Animator animator) {

                         }

                         @Override
                         public void onAnimationRepeat(Animator animator) {

                         }
                     });
                     mAnimatorSet2.start();
                }

                @Override
                public void onAnimationCancel(Animator animator) {

                }

                @Override
                public void onAnimationRepeat(Animator animator) {

                }
            });

            mAnimatorSet1.start();

可以看见,代码略显繁琐。

2、解决方案

这是一个属性动画封装的库,解决了上面提到的问题,使用方法:

  • 添加依赖

根目录build

allprojects {
    repositories {
        jcenter()
        maven {
            url 'https://dl.bintray.com/zouzhihao/maven'
        }
    }
}

项目 build

compile 'com.runningzou.leptanimator:library:0.0.1'
  • 定义一个 LeptAnimator 子类
public class SimpleAnimator extends LeptAnimator {

    //必须覆写该构造方法,view 表示需要做动画的控件和需要测量尺寸信息的控件
    public SimpleAnimator(View... view) {
        super(view);

    }

    //target 就是构造方法中传入的 view
    //这些 View 可以用于做动画或者测量尺寸
    @Override
    public AnimatorBuilder prepare(View... target) {

            //AnimatorBuilder 提供了很多实用的方法
            return AnimatorBuilder
                    .animate(target[0]) //动画1
                    .translationX(100) //单位 dp
                    .ParentTop(10) //距离顶部10dp
                    .duration(1000)

                    .with(target[1]) //动画2,动画2与动画1同步执行
                    .leftof(target[3],10); //target[1]移动到 target[3] 的右边,距离为 10dp,仅支持对静止的 view 设置相对位置
                    .alpha(0,1)

                    .after(target[2]) //动画3,动画1,2执行完了,再执行动画3
                    .translationX(100);


    }
}

  • 使用

LeptAnimator animator = new SimpleAnimator(view1,view2,view3,view4);
animator.start();

animator.setStartListener(new ViewAnimatorListener.startListener() {
            @Override
            public void onAnimatorStart() {
                Log.d("tag","动画开始");
            }
        });

animator.setEndListener(new ViewAnimatorListener.endListener() {
            @Override
            public void onAnimatorEnd() {
                Log.d("tag","动画结束");
            }
        });

3、问题解决了吗?

3.1、代码复用

这个封装库中,动画被封装成了一个个的类,例如示例中的 SimpleAnimator,使用的时候只需要 new 一个对象,并传入需要操作的 view 就可以了。用人肯定会说了,我用 xml 写动画,代码中再加载就可以了。动画用 xml 写我一直觉得不优雅,代码可以很容易做到事(几行代码就获取一个 Animator 实例),用 xml 写,除了要增加文件数、代码行数,还会导致增加 IO 操作,效率降低(读取文件,解析文件)。

3.2、无法获取尺寸信息

LeptAnimator 的 prepare 方法大概是这样运行的

view.post(new Runnable(){
    public void run() {
        prepare();
    }
})

所以在准备动画的过程中(即 prepare 方法中),可以获取到 view 的尺寸信息。

例如:将 view View 贴近父布局右边界,间隔为 margin dp


public class SimpleAnimator extends LeptAnimator {
    @Override
    public AnimatorBuilder prepare(View... targets) {
        View view = targets[0];
        View ParentView = (View) view.getParent();

        int distance = ParentView.getWidth() - view.getLeft() - view.getWidth() - DistanceUtil.dp2px(10);
        return new AnimatorBuilder()
                        .translationX(distance);
    }
}

现在就可以计算 view 的尺寸信息了,但是每次都要计算,还是颇显麻烦,所有这个库的 AnimatorBuilder 类提供了几个方法来简化你的尺寸计算

//将 View 贴近父布局顶部,间隔为 margin dp
public AnimatorBuilder parentTop(int margin)

//将 View 贴近父布局底部,间隔为 margin dp
public AnimatorBuilder parentBottom(int margin)

//将 View 贴近父布局左边界,间隔为 margin dp
public AnimatorBuilder parentLeft(int margin)

//将 View 贴近父布局右边界,间隔为 margin dp
public AnimatorBuilder parentRight(int margin)

//将 View 移动到 target 的左侧,间隔为 rigntMargin
public AnimatorBuilder leftof(View target, int rightMargin)
//类似的方法还有
public AnimatorBuilder rightof(View target, int margin)
public AnimatorBuilder topof(View target, int margin)
public AnimatorBuilder bottomof(View target, int margin)

库中所有距离的单位均为 dp。 有了这些方法,上面的代码就可以改为

public class SimpleAnimator extends LeptAnimator {
    @Override
    public AnimatorBuilder prepare(View... targets) {
        return new AnimatorBuilder()
                        .parentRignt(10);
    }
}

利用这这些方法的组合,可以更容易地实现一些酷炫的效果。

3.3、复杂动画实现不够简洁

看看示例代码

public class SimpleAnimator extends LeptAnimator {

    //必须覆写该构造方法,view 表示需要做动画的控件和需要测量尺寸信息的控件
    public SimpleAnimator(View... view) {
        super(view);

    }

    //target 就是构造方法中传入的 view
    //这些 View 可以用于做动画或者测量尺寸
    @Override
    public AnimatorBuilder prepare(View... target) {

            //AnimatorBuilder 提供了很多实用的方法
            return AnimatorBuilder
                    .animate(target[0]) //动画1
                    .translationX(100) //单位 dp
                    .ParentTop(10) //距离顶部10dp
                    .duration(1000)

                    .with(target[1]) //动画2,动画2与动画1同步执行
                    .leftof(target[3],10); //target[1]移动到 target[3] 的右边,距离为 10dp,仅支持对静止的 view 设置相对位置
                    .alpha(0,1)

                    .after(target[2]) //动画3,动画1,2执行完了,再执行动画3
                    .translationX(100);


    }
}

  • 通过 with 可以定义同步执行的动画
  • 通过 after 可以定义顺序执行的动画

4、彩蛋

库中提供了一个有意思的功能

//设置动画执行的百分比,0.5 表示动画执行一半。
leptAnimator.setpercent(0.2);

这个功能可以实现一些有意思的效果,比如 demo 中的 ScrollActivity(具体见下一节的 gif图)

5、more

更多用法可以查看demo,下面是demo的效果图。


6、感谢

库的实现过程参考了以下开源项目: