开发艺术探索笔记

开发艺术之Animation

2020-02-01  本文已影响0人  请叫我林锋

Android 动画可分为三种:View动画、帧动画和属性动画。帧动画也属于 View 动画的一种,只不过它和平移、旋转等常见的 View 动画在表现形式上不同。

一、View 动画

1、View 动画的种类

View 动画的四种变换效果对应 Animation 的四个子类:TranslateAnimationScaleAnimationRotateAnimationAlphaAnimation

View 动画的四种变换.png

创建方式:可以通过 XML 定义,也可以通过代码来动态创建,对于 View 动画来说,推荐采用 XML定义动画,因为 XML 格式的动画可读性更好。使用方法如下:

创建动画的 XML 文件,文件路径为:res/anim/view_animation.xml,内容如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true"
    android:shareInterpolator="true">

    <translate
        android:fromXDelta="float"
        android:fromYDelta="float"
        android:toXDelta="float"
        android:toYDelta="float" />
    <scale
        android:fromXScale="float"
        android:fromYScale="float"
        android:pivotX="float"
        android:pivotY="float"
        android:toXScale="float"
        android:toYScale="float" />
    <rotate
        android:fromDegrees="float"
        android:pivotX="float"
        android:pivotY="float"
        android:toDegrees="float" />
    <alpha
        android:fromAlpha="float"
        android:toAlpha="float" />

</set>

在定义完 xml 之后,在代码中应用如下代码,就可以实现 View 动画效果

Button mButton = (Button) findViewById(R.id.button);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.view_animation);
mButton.startAnimation(animation);

除了在 XML 中定义动画外,还可以通过代码来应用动画:

AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setDuration(300);
mButton.startAnimation(alphaAnimation);

最后,可以通过 Animation 的 setAnimationListener 方法给 View 添加过程监听,接口如下所示

    public static interface AnimationListener {
        void onAnimationStart(Animation animation);
        void onAnimationEnd(Animation animation);
        void onAnimationRepeat(Animation animation);
    }
2、自定义 View 动画

当上面的四种动画无法满足我们的需求时,可以通过继承 Animation 这个抽象类,然后重写它的 initializeapplyTransformation 方法来自定义 View 动画。

一般开发中很少用到自定义 View 动画

3、帧动画

帧动画是顺序播放一组预先定义好的图片,系统提供了另外一个类 AnimationDrawable 来使用帧动画。

使用方法:通过 XML 定义一个 AnimationDrawable,如下所示:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
  android:oneshot="false">
  <item android:drawable="@drawable/image1" android:duration="200"/>
  <item android:drawable="@drawable/image2" android:duration="200"/>
  <item android:drawable="@drawable/image3" android:duration="200"/>
</animation-list>

然后将上述 Drawable 作为 View 的背景通过 Drawable 来播放动画即可:

    Button mButton = (Button) findViewById(R.id.button);
    mButton.setBackgroundResource(R.drawable.frame_drawable);
    AnimationDrawable animationDrawable = (AnimationDrawable) mButton.getBackground();
    animationDrawable.start();

帧动画比较容易引起 OOM,所以在使用帧动画时应尽量避免使用尺寸较大大图片


二、View 动画的特殊使用场景

View 动画可以在一些特殊场景下使用,比如在 ViewGroup 中可以控制子元素的出场效果,实现不同 Activity 之间的切换效果。

1、LayoutAnimation

作用对象:ViewGroup。比如让 ListView 的每个 item 以一定的动画形式出现

使用步骤:

除了在 XML 中指定 LayoutAnimation 外,还可以通过 LayoutAnimationController 来实现,具体代码如下:

        Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
        LayoutAnimationController controller = new LayoutAnimationController(animation);
        controller.setDelay(0.5f);
        controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
        mListView.setLayoutAnimation(controller);
2、Activity 的切换效果

Activtiy 有默认切换效果,但是这个效果我们也可以自定义,主要用到 overridePendingTransition(int enterAnim, int exitAnim) 这个方法,此方法需要在 startActivity() 或者 finish() 之后被调用才能生效,它的参数含义如下:

使用方法如下:

    // 当启动一个 Activity 时
    Intent intent = new Intent(this, Main2Activity.class);
    startActivity(intent);
    overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);

    // 当 Activity 退出时
    @Override
    public void finish() {
        super.finish();
        overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
    }

三、属性动画

属性动画和 View 动画不同,它对作用对象进行了扩展,属性动画可以对任何对象做动画。属性动画中有 ValueAnimator、ObjectAnimator 和 AnimatorSet 等概念。

1、使用属性动画

属性动画默认时间间隔 300ms,默认帧率 10ms/帧。我们可以在一个时间间隔内完成对象从一个属性值到另一个属性值的改变,比如:

属性动画除了通过代码实现外,还可以通过 XML 来定义,属性动画需要定义在 res/animator/ 目录下,语法如下:

<set
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

定义完 XML 后,就可以在代码中使用上面的属性动画了:

        AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.property_animator);
        set.setTarget(mButton);
        set.start();

建议:在实际开发中建议采用代码来实现属性动画,因为通过代码实现比较简单,而且很多时候一个属性的起始值是无法提前确定的。比如让一个按钮从屏幕左边移动到右边,由于我们无法提前知道屏幕的宽度,就无法将属性动画定义在 XML 中。

2、理解插值器和估值器
3、属性动画的监听器

属性动画提供了监听器,主要有如下两个接口:

public static interface AnimatorListener {
        default void onAnimationStart(Animator animation, boolean isReverse) {
            onAnimationStart(animation);
        }
        default void onAnimationEnd(Animator animation, boolean isReverse) {
            onAnimationEnd(animation);
        }
        void onAnimationStart(Animator animation);
        void onAnimationEnd(Animator animation);
        void onAnimationCancel(Animator animation);
        void onAnimationRepeat(Animator animation);
    }
    public static interface AnimatorUpdateListener {
        // 每播放一帧就会被调用一次
        void onAnimationUpdate(ValueAnimator animation);
    }
4、对任意属性做动画

我们对 object 的属性 abc 作属性动画,如果想要动画生效,要同时满足下面两个条件:

来看一个问题:对 Button 的宽度做属性动画:

ObjectAnimator.ofInt(mButton, "width", 500).setDuration(5000).start();

代码运行后发现没效果,这是因为 Button 的 setWidth 方法并不是设置它的宽度:

    @android.view.RemotableViewMethod
    public void setWidth(int pixels) {
        mMaxWidth = mMinWidth = pixels;
        mMaxWidthMode = mMinWidthMode = PIXELS;

        requestLayout();
        invalidate();
    }

这里只设置了最大宽度和最小宽度,对应于 XML 中的 android:width 属性,而真正的宽度对应 android:layout_width。也就是这里只满足了条件 1 而未满足条件 2。

针对上述问题,有 3 种解决方法:

  1. 给你的对象加上 get 和 set 方法,如果你有权限的话

    这个方法最简单,但是往往是可不行的。因为大部分都是 Android SDK 内部实现的。

  2. 用一个类来包装原始对象,间接为其提供 get 和 set 方法

    这是一个很有用的解决方法,用起来很方便,示例代码如下:

     public void onClick(View view) {
            ViewWrapper wrapper = new ViewWrapper(mButton);
            ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();
        }
    
        public class ViewWrapper {
            private View mTarget;
    
            public ViewWrapper(View mTarget) {
                this.mTarget = mTarget;
            }
    
            public int getWidth() {
                return mTarget.getLayoutParams().width;
            }
    
            public void setWidth(int width) {
                mTarget.getLayoutParams().width = width;
                mTarget.requestLayout();
            }
        }
    
  3. 采用 ValueAnimator,监听动画过程,自己实现属性的改变

public void onClick(View view) {
    performAnimate(mButton, mButton.getWidth(), 500);
}

private void performAnimate(final View target, final int start, final int end) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        private IntEvaluator mEvaluator = new IntEvaluator();

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float fraction = animation.getAnimatedFraction();
            target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
            target.requestLayout();
        }
    });
    valueAnimator.setDuration(5000).start();
}

它会在 5000ms 内将一个数从 1 变到 100,然后动画的每一帧都会回调 onAnimationUpdate 方法。


四、使用动画的注意事项

推荐阅读:HenCoder Android 自定义 View 1-6:属性动画 Property Animation(上手篇)

上一篇下一篇

猜你喜欢

热点阅读