Android 属性动画

2022-02-17  本文已影响0人  gaookey
image.png

属性动画是增强版的补间动画

与补间动画类似的是,属性动画也需要定义如下几个属性。

属性动画的 API

除此之外,属性动画还需要利用一个 Evaluator(计算器),该工具类控制属性动画如何计算属性值。Android 提供了如下 Evaluator

Android 8 还为 AnimatorSet 新增了如下方法。

通过调用 setCurrentPlayTime() 方法,Android 允许 AnimatorSet 动画直接调到指定时间点进行播放,不需要总是从头开始播放;通过 reverse() 方法则允许对动画进行倒播--以前可能需要定义两组动画,其中一组用于正向播放(比如放大、淡入);另一组用于反向播放(比如缩小、淡出),有了倒播功能之后,只需定义一组动画即可。

1. 使用 ValueAnimator 创建动画

使用 ValueAnimator 创建动画可按如下4个步骤进行。

  1. 调用 ValueAnimatorofint()ofFloat()ofObject() 静态方法创建 ValueAnimator 实例。
  2. 调用 ValueAnimatorsetXxx() 方法设置动画持续时间、插值方式、重复次数等。
  1. 调用 ValucAnimatorstart() 方法启动动画。
  2. ValueAnimator 注册 AnimatorUpdateListener 监听器,在该监听器中可以监听 ValueAnimator 计算出来的值的改变,并将这些值应用到指定对象上。
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f);
animator.setDuration(1000);
animator.start();

自定义的 Evaluator 计算器:

ValueAnimator animation = ValueAnimator.ofObject (MyTypeEvaluator (), startVal, endVal) ;
animation.setDuration (1000);
animation. start();

在上面的代码片段中,ValueAnimator 仅仅是计算动画过程中变化的值,并没有把这些计算出来的值应用到任何对象上,因此也不会显示任何动画。
如果希望使用 ValueAnimator 创建动画,还需要注册一个监听器:AnimatorUpdateListener,该监听器负责更新对象的属性值。在实现这个监听器时,可以通过 getAnimatedValue() 方法来获取当前帧的值,并将该计算出来的值应用到指定对象上。当该对象的属性持续改变时,该对象也就呈现出动画效果了。

2. 使用 ObjectAnimator 创建动画

ObjectAnimator 继承了 ValueAnimator,因此它可以直接将 ObjectAnimator 在动画过程中计算出来的值应用到指定对象的指定属性上(ValueAnimator 则需要注册一个监听器来完成这个工作)。因此使用 ObjectAnimator 就不需要注册 AnimatorUpdateListener 监听器了。
使用 ObjectAnimatorofInt()ofFloat()ofObject() 静态方法创建 ObjectAnimator 时,需要指定具体的对象,以及对象的属性名。

ObjectAnimator anim = ObjectAnimator.ofFloat (foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();

ValueAnimator 不同的是,使用 ObjectAnimator 有如下几个注意点。

使用属性动画

属性动画既可作用于 UI 组件,也可作用于普通的对象(即使它没有在UI 界面上绘制出来)。

定义属性动画有如下两种方式。

  1. 使用 ValueAnimatorObjectAnimator 的静态工厂方法来创建动画。
  2. 使用资源文件来定义动画。

使用属性动画的步骤如下。

  1. 创建 ValueAnimatorObjectAnimator 对象--既可从 XML 资源文件加载该动画资源,也可直接调用 ValueAnimatorObjectAnimator 的静态工厂方法来创建动画。
  2. 根据需要为 Animator 对象设置属性。
  3. 如果需要监听 Animator 的动画开始事件、动画结束事件、动画重复事件、动画值改变事件,并根据事件提供相应的处理代码,则应该为 Animator 对象设置事件监听器。
  4. 如果有多个动画需要按次序或同时播放,则应使用 AnimatorSet 组合这些动画。
  5. 调用 Animator 对象的 start() 方法启动动画。

MainActivity

public class MainActivity extends AppCompatActivity {
    // 定义小球大小的常量
    public static final float BALL_SIZE = 50f;
    // 定义小球从屏幕上方下落到屏幕底端的总时间
    public static final float FULL_TIME = 1000f;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout container = findViewById(R.id.container);
        // 设置该窗口显示MyAnimationView组件
        container.addView(new MyAnimationView(this));
    }

    static class MyAnimationView extends View implements ValueAnimator.AnimatorUpdateListener {
        List<ShapeHolder> balls = new ArrayList<>();

        public MyAnimationView(Context context) {
            super(context);
            setBackgroundColor(Color.WHITE);
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            // 如果触碰事件不是按下、移动事件
            if (event.getAction() != MotionEvent.ACTION_DOWN &&
                    event.getAction() != MotionEvent.ACTION_MOVE) {
                return false;
            }
            // 在事件发生点添加一个小球(用一个圆形代表)
            ShapeHolder newBall = addBall(event.getX(), event.getY());
            // 计算小球下落动画开始时的y坐标
            float startY = newBall.getY();
            // 计算小球下落动画结束时的y坐标(落到屏幕最下方,就是屏幕高度减去小球高度)
            float endY = getHeight() - BALL_SIZE;
            // 获取屏幕高度
            float h = getHeight();
            float eventY = event.getY();
            // 计算动画的持续时间
            int duration = (int) (FULL_TIME * ((h - eventY) / h));
            // 定义小球“落下”的动画:
            // 让newBall对象的y属性从事件发生点变化到屏幕最下方
            ObjectAnimator fallAnim = ObjectAnimator.ofFloat(newBall, "y", startY, endY);
            // 设置fallAnim动画的持续时间
            fallAnim.setDuration(duration);
            // 设置fallAnim动画的插值方式:加速插值
            fallAnim.setInterpolator(new AccelerateInterpolator());
            // 为fallAnim动画添加监听器
            // 当ValueAnimator的属性值发生改变时,将会激发该监听器的事件监听方法
            fallAnim.addUpdateListener(this);
            // 定义对newBall对象的alpha属性执行从1到0的动画(即定义渐隐动画)
            ObjectAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
            // 设置动画持续时间
            fadeAnim.setDuration(250);
            // 为fadeAnim动画添加监听器
            fadeAnim.addListener(new AnimatorListenerAdapter() {
                // 当动画结束时
                @Override
                public void onAnimationEnd(Animator animation) {
                    // 动画结束时将该动画关联的ShapeHolder删除
                    balls.remove(((ObjectAnimator) animation).getTarget());
                }
            });
            // 为fadeAnim动画添加监听器
            // 当ValueAnimator的属性值发生改变时,将会激发该监听器的事件监听方法
            fadeAnim.addUpdateListener(this);
            // 定义一个AnimatorSet来组合动画
            AnimatorSet animatorSet = new AnimatorSet();
            // 指定在播放fadeAnim之前,先播放fallAnim动画
            animatorSet.play(fallAnim).before(fadeAnim);
            // 开始播放动画
            animatorSet.start();
            return true;
        }

        private ShapeHolder addBall(float x, float y) {
            // 创建一个圆
            OvalShape circle = new OvalShape();
            // 设置该圆的宽、高
            circle.resize(BALL_SIZE, BALL_SIZE);
            // 将圆包装成Drawable对象
            ShapeDrawable drawable = new ShapeDrawable(circle);
            // 创建一个ShapeHolder对象
            ShapeHolder shapeHolder = new ShapeHolder(drawable);
            // 设置ShapeHolder的x、y坐标
            shapeHolder.setX(x - BALL_SIZE / 2);
            shapeHolder.setY(y - BALL_SIZE / 2);
            int red = (int) (Math.random() * 255);
            int green = (int) (Math.random() * 255);
            int blue = (int) (Math.random() * 255);
            // 将red、green、blue三个随机数组合成ARGB颜色
            int color = -0x1000000 + red << 16 | (green << 8) | blue;
            // 获取drawable上关联的画笔
            Paint paint = drawable.getPaint();
            // 将red、green、blue三个随机数除以4得到商值组合成ARGB颜色
            int darkColor = (-0x1000000 | (red / 4 << 16) | (green / 4 << 8) | blue / 4);
            // 创建圆形渐变
            RadialGradient gradient = new RadialGradient(37.5f, 12.5f, BALL_SIZE,
                    color, darkColor, Shader.TileMode.CLAMP);
            paint.setShader(gradient);
            // 为shapeHolder设置paint画笔
            shapeHolder.setPaint(paint);
            balls.add(shapeHolder);
            return shapeHolder;
        }

        @Override
        public void onDraw(Canvas canvas) {
            // 遍历balls集合中的每个ShapeHolder对象
            for (ShapeHolder shapeHolder : balls) {
                // 保存canvas的当前坐标系统
                canvas.save();
                // 坐标变换:将画布坐标系统平移到shapeHolder的X、Y坐标处
                canvas.translate(shapeHolder.getX(), shapeHolder.getY());
                // 将shapeHolder持有的圆形绘制在Canvas上
                shapeHolder.getShape().draw(canvas);
                // 恢复Canvas坐标系统
                canvas.restore();
            }
        }

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 指定重绘该界面
            this.invalidate();  // ①
        }
    }
}

ShapeHolder

public class ShapeHolder {
    private float x = 0, y = 0;
    private ShapeDrawable shape;
    private int color;
    private RadialGradient gradient;
    private float alpha = 1f;
    private Paint paint;

    public ShapeHolder(ShapeDrawable s) {
        shape = s;
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    public ShapeDrawable getShape() {
        return shape;
    }

    public void setShape(ShapeDrawable shape) {
        this.shape = shape;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public RadialGradient getGradient() {
        return gradient;
    }

    public void setGradient(RadialGradient gradient) {
        this.gradient = gradient;
    }

    public float getAlpha() {
        return alpha;
    }

    public void setAlpha(float alpha) {
        this.alpha = alpha;
    }

    public Paint getPaint() {
        return paint;
    }

    public void setPaint(Paint paint) {
        this.paint = paint;
    }
}
image.gif

实例:大珠小珠落玉盘

MainActivity

public class MainActivity extends AppCompatActivity {
    // 定义小球大小的常量
    public static final float BALL_SIZE = 50f;
    // 定义小球从屏幕上方下落到屏幕底端的总时间
    public static final float FULL_TIME = 1000f;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout container = findViewById(R.id.container);
        // 设置该窗口显示MyAnimationView组件
        container.addView(new MyAnimationView(this));

    }

    class MyAnimationView extends View {
        List<ShapeHolder> balls = new ArrayList<>();

        MyAnimationView(Context context) {
            super(context);
            // 加载动画资源
            ObjectAnimator colorAnim = (ObjectAnimator) AnimatorInflater.loadAnimator(MainActivity.this,
                    R.animator.color_anim);
            colorAnim.setEvaluator(new ArgbEvaluator());
            // 对该View本身应用属性动画
            colorAnim.setTarget(this);
            // 开始指定动画
            colorAnim.start();
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            // 如果触碰事件不是按下、移动事件
            if (event.getAction() != MotionEvent.ACTION_DOWN &&
                    event.getAction() != MotionEvent.ACTION_MOVE) {
                return false;
            }
            // 在事件发生点添加一个小球(用一个圆形代表)
            ShapeHolder newBall = addBall(event.getX(), event.getY());
            // 计算小球下落动画开始时的y坐标
            float startY = newBall.getY();
            // 计算小球下落动画结束时的y坐标(落到屏幕最下方,就是屏幕高度减去小球高度)
            float endY = getHeight() - BALL_SIZE;
            // 获取屏幕高度
            float h = getHeight();
            float eventY = event.getY();
            // 计算动画的持续时间
            int duration = (int) (FULL_TIME * ((h - eventY) / h));
            // 定义小球“落下”的动画
            // 让newBall对象的y属性从事件发生点变化到屏幕最下方
            ObjectAnimator fallAnim = ObjectAnimator.ofFloat(newBall, "y", startY, endY);
            // 设置fallAnim动画的持续时间
            fallAnim.setDuration(duration);
            // 设置fallAnim动画的插值方式:加速插值
            fallAnim.setInterpolator(new AccelerateInterpolator());
            // 定义小球“压扁”的动画:该动画控制小球的x坐标“左移”半个球
            ObjectAnimator squashAnim1 = ObjectAnimator.ofFloat(newBall,
                    "x", newBall.getX(), newBall.getX() - BALL_SIZE / 2);
            // 设置squashAnim1动画的持续时间
            squashAnim1.setDuration(duration / 4);
            // 设置squashAnim1动画重复1次
            squashAnim1.setRepeatCount(1);
            // 设置squashAnim1动画的重复方式
            squashAnim1.setRepeatMode(ValueAnimator.REVERSE);
            // 设置squashAnim1动画的插值方式:减速插值
            squashAnim1.setInterpolator(new DecelerateInterpolator());
            // 定义小球“压扁”的动画:该动画控制小球的宽度加倍
            ObjectAnimator squashAnim2 = ObjectAnimator.ofFloat(newBall, "width",
                    newBall.getWidth(), newBall.getWidth() + BALL_SIZE);
            // 设置squashAnim2动画的持续时间
            squashAnim2.setDuration(duration / 4);
            // 设置squashAnim2动画重复1次
            squashAnim2.setRepeatCount(1);
            // 设置squashAnim2动画的重复方式
            squashAnim2.setRepeatMode(ValueAnimator.REVERSE);
            // 设置squashAnim2动画的插值方式:减速插值
            squashAnim2.setInterpolator(new DecelerateInterpolator());
            // 定义小球“拉伸”的动画:该动画控制小球的y坐标“下移”半个球
            ObjectAnimator stretchAnim1 = ObjectAnimator.ofFloat(newBall,
                    "y", endY, endY + BALL_SIZE / 2);
            // 设置stretchAnim1动画的持续时间
            stretchAnim1.setDuration(duration / 4);
            // 设置stretchAnim1动画重复1次
            stretchAnim1.setRepeatCount(1);
            // 设置stretchAnim1动画的重复方式
            stretchAnim1.setRepeatMode(ValueAnimator.REVERSE);
            // 设置stretchAnim1动画的插值方式:减速插值
            stretchAnim1.setInterpolator(new DecelerateInterpolator());
            // 定义小球“拉伸”的动画:该动画控制小球的高度减半
            ObjectAnimator stretchAnim2 = ObjectAnimator.ofFloat(newBall, "height",
                    newBall.getHeight(), newBall.getHeight() - BALL_SIZE / 2);
            // 设置stretchAnim2动画的持续时间
            stretchAnim2.setDuration(duration / 4);
            // 设置squashAnim2动画重复1次
            stretchAnim2.setRepeatCount(1);
            // 设置squashAnim2动画的重复方式
            stretchAnim2.setRepeatMode(ValueAnimator.REVERSE);
            // 设置squashAnim2动画的插值方式:减速插值
            stretchAnim2.setInterpolator(new DecelerateInterpolator());
            // 定义小球“弹起”的动画
            ObjectAnimator bounceBackAnim = ObjectAnimator.ofFloat(newBall, "y", endY, startY);
            // 设置持续时间
            bounceBackAnim.setDuration(duration);
            // 设置动画的插值方式:减速插值
            bounceBackAnim.setInterpolator(new DecelerateInterpolator());
            // 使用AnimatorSet按顺序播放“下落/压扁&拉伸/弹起动画
            AnimatorSet bouncer = new AnimatorSet();
            // 定义在squashAnim1动画之前播放fallAnim下落动画
            bouncer.play(fallAnim).before(squashAnim1);
            // 由于小球在“屏幕”下方弹起时,小球要被压扁
            // 即:宽度加倍、x坐标左移半个球,高度减半、y坐标下移半个球
            // 因此此处指定播放squashAnim1的同时
            // 还播放squashAnim2、stretchAnim1、stretchAnim2
            bouncer.play(squashAnim1).with(squashAnim2);
            bouncer.play(squashAnim1).with(stretchAnim1);
            bouncer.play(squashAnim1).with(stretchAnim2);
            // 指定播放stretchAnim2动画之后,播放bounceBackAnim弹起动画
            bouncer.play(bounceBackAnim).after(stretchAnim2);
            // 定义对newBall对象的alpha属性执行从1到0的动画(即定义渐隐动画)
            ObjectAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
            // 设置动画持续时间
            fadeAnim.setDuration(250);
            // 为fadeAnim动画添加监听器
            fadeAnim.addListener(new AnimatorListenerAdapter() {
                // 当动画结束时
                @Override
                public void onAnimationEnd(Animator animation) {
                    // 动画结束时将该动画关联的ShapeHolder删除
                    balls.remove(((ObjectAnimator) animation).getTarget());
                }
            });
            // 再次定义一个AnimatorSet来组合动画
            AnimatorSet animatorSet = new AnimatorSet();
            // 指定在播放fadeAnim之前,先播放bouncer动画
            animatorSet.play(bouncer).before(fadeAnim);
            // 开始播放动画
            animatorSet.start();
            return true;
        }

        private ShapeHolder addBall(float x, float y) {
            // 创建一个椭圆
            OvalShape circle = new OvalShape();
            // 设置该椭圆的宽、高
            circle.resize(BALL_SIZE, BALL_SIZE);
            // 将椭圆包装成Drawable对象
            ShapeDrawable drawable = new ShapeDrawable(circle);
            // 创建一个ShapeHolder对象
            ShapeHolder shapeHolder = new ShapeHolder(drawable);
            // 设置ShapeHolder的x、y坐标
            shapeHolder.setX(x - BALL_SIZE / 2);
            shapeHolder.setY(y - BALL_SIZE / 2);
            int red = (int) (Math.random() * 255);
            int green = (int) (Math.random() * 255);
            int blue = (int) (Math.random() * 255);
            // 将red、green、blue三个随机数组合成ARGB颜色
            int color = -0x1000000 + red << 16 | (green << 8) | blue;
            // 获取drawable上关联的画笔
            Paint paint = drawable.getPaint();
            // 将red、green、blue三个随机数除以4得到商值组合成ARGB颜色
            int darkColor = (-0x1000000 | (red / 4 << 16) | (green / 4 << 8) | blue / 4);
            // 创建圆形渐变
            RadialGradient gradient = new RadialGradient(37.5f, 12.5f, BALL_SIZE,
                    color, darkColor, Shader.TileMode.CLAMP);
            paint.setShader(gradient);
            // 为shapeHolder设置paint画笔
            shapeHolder.setPaint(paint);
            balls.add(shapeHolder);
            return shapeHolder;
        }

        @Override
        public void onDraw(Canvas canvas) {
            // 遍历balls集合中的每个ShapeHolder对象
            for (ShapeHolder shapeHolder : balls) {
                // 保存canvas的当前坐标系统
                canvas.save();
                // 坐标变换:将画布坐标系统平移到shapeHolder的X、Y坐标处
                canvas.translate(shapeHolder.getX(), shapeHolder.getY());
                // 将shapeHolder持有的圆形绘制在Canvas上
                shapeHolder.getShape().draw(canvas);
                // 恢复Canvas坐标系统
                canvas.restore();
            }
        }
    }
}

ShapeHolder

public class ShapeHolder {
    private float x = 0, y = 0;
    private ShapeDrawable shape;
    private int color;
    private RadialGradient gradient;
    private float alpha = 1f;
    private Paint paint;

    public ShapeHolder(ShapeDrawable s) {
        shape = s;
    }

    public float getWidth() {
        return shape.getShape().getWidth();
    }

    public void setWidth(float width) {
        Shape s = shape.getShape();
        s.resize(width, s.getHeight());
    }

    public float getHeight() {
        return shape.getShape().getHeight();
    }

    public void setHeight(float height) {
        Shape s = shape.getShape();
        s.resize(s.getWidth(), height);
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    public ShapeDrawable getShape() {
        return shape;
    }

    public void setShape(ShapeDrawable shape) {
        this.shape = shape;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public RadialGradient getGradient() {
        return gradient;
    }

    public void setGradient(RadialGradient gradient) {
        this.gradient = gradient;
    }

    public float getAlpha() {
        return alpha;
    }

    public void setAlpha(float alpha) {
        this.alpha = alpha;
    }

    public Paint getPaint() {
        return paint;
    }

    public void setPaint(Paint paint) {
        this.paint = paint;
    }
}

animator/color_anim.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:propertyName="backgroundColor"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    android:valueFrom="#FF8080"
    android:valueTo="#8080FF"
    android:valueType="intType" />
image.gif

摘抄至《疯狂Android讲义(第4版)》

上一篇下一篇

猜你喜欢

热点阅读