安卓动画系列之五, 属性动画PropertyAnimation(下) - 通过官方例子深入了解

这里继续之前写的上篇属性动画PropertyAnimation(上)之初步印象 来写下篇,了解一下自定义的对象如何调用实现属性动画,还有AnimatorSet的一些灵活用法.本来也尝试像之前那样写demo去讲,但发现android官方在这方面已经提供了非常好的例子,于是就拿官方的这个小球下落回弹来作为例子,深入的了解属性动画的用法吧. 代码中的注释我已经非常详细,所以不再另外写出来了.过一遍代码,相信我们自己以后也能灵活运用属性动画了.


先上效果图:



代码:

ShapeHolder:

package com.test.android.objectanimationmain;

import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape;

/**
 * ShapeHolder作为属性动画的对象,必须设置setter和getter方法才能使动画生效.
 * 这个类本质上也是通过ShapeDrawable来创建可视化的实体对象.
 * 里面都是主要的getter和setter方法
 */
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 void setPaint(Paint value) {
        paint = value;
    }
    public Paint getPaint() {
        return paint;
    }

    public void setX(float value) {
        x = value;
    }
    public float getX() {
        return x;
    }
    public void setY(float value) {
        y = value;
    }
    public float getY() {
        return y;
    }
    public void setShape(ShapeDrawable value) {
        shape = value;
    }
    public ShapeDrawable getShape() {
        return shape;
    }
    public int getColor() {
        return color;
    }
    public void setColor(int value) {
        shape.getPaint().setColor(value);
        color = value;
    }
    public void setGradient(RadialGradient value) {
        gradient = value;
    }
    public RadialGradient getGradient() {
        return gradient;
    }

    public void setAlpha(float alpha) {
        this.alpha = alpha;
        shape.setAlpha((int)((alpha * 255f) + .5f));
    }

    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 ShapeHolder(ShapeDrawable s) {
        shape = s;
    }

}


主类方法:

 public class MyAnimationView extends View {

        private static final int RED = 0xffFF8080;
        private static final int BLUE = 0xff8080FF;
        private static final int CYAN = 0xff80ffff;
        private static final int GREEN = 0xff80ff80;

        public final ArrayList<ShapeHolder> balls = new ArrayList<ShapeHolder>();
        AnimatorSet animation = null;

        public MyAnimationView(Context context) {
            super(context);

            // Animate background color
            // Note that setting the background color will automatically invalidate the
            // view, so that the animated color, and the bouncing balls, get redisplayed on
            // every frame of the animation.
            //设置背景颜色的动画
            ValueAnimator colorAnim = ObjectAnimator.ofInt(this, "backgroundColor", RED, BLUE);
            colorAnim.setDuration(3000);
            //设置属性值计算器
            colorAnim.setEvaluator(new ArgbEvaluator());
            colorAnim.setRepeatCount(ValueAnimator.INFINITE);
            colorAnim.setRepeatMode(ValueAnimator.REVERSE);
            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());

            // Bouncing animation with squash and stretch
            //获取触摸的事件X,Y坐标
            float startY = newBall.getY();
            //小球最后下落点的Y坐标,由屏幕高度减去小球高度得到
            float endY = getHeight() - 50f;
            //获取shapeholder的高度
            float h = (float)getHeight();
            float eventY = event.getY();
            int duration = (int)(500 * ((h - eventY)/h));
            //这里设置了"y","x"这些属性,但是系统针对newBall这对象是没有setX(), setY()的方法支持的.
            //所以我们在newBall所继承的自定义的ShapeHolder了中,必须自己去实现setX() setY()
            //和getX() , getY()方法,这样才能是属性动画在使用过程中可以根据时间不断的getter和setter.
            //这里就是我们所使用自定义的对象去调用属性动画的关键地方.
            //换言之,如果ShapeHolder中将x,y换成locationX,locationY,只要类中实现类似setLocationY(),getLocationY()方法就行了,
            //下面这句照样可以换成ValueAnimator bounceAnim = ObjectAnimator.ofFloat(newBall, "locationY", startY, endY);
            ValueAnimator bounceAnim = ObjectAnimator.ofFloat(newBall, "y", startY, endY);
            bounceAnim.setDuration(duration);
            //设置插值器,由慢到快.这样小球(也就是一个圆)在下落的过程中是从慢到快的,看起来是受了地心引力的影响...更真实一些
            bounceAnim.setInterpolator(new AccelerateInterpolator());
            //这里创建的squashAnim1属性动画,是实现小球掉落在底部的Y坐标时的压扁效果.X坐标偏移25f
            ValueAnimator squashAnim1 = ObjectAnimator.ofFloat(newBall, "x", newBall.getX(),
                    newBall.getX() - 25f);
            //这里设置的动画时间只有bounceAnim动画时间的1/4,因为压扁的过程是迅速的,所以要时间值小很多才合理
            squashAnim1.setDuration(duration/4);
            //设置重复次数为1次
            squashAnim1.setRepeatCount(1);
            //设置重复模式是逆向返回起点
            squashAnim1.setRepeatMode(ValueAnimator.REVERSE);
            //设置插值器,一直减速
            squashAnim1.setInterpolator(new DecelerateInterpolator());
            //这里一连串的squashAnim1,squashAnim2等等都是构成小球压扁效果的动画,宽度增加至50
            ValueAnimator squashAnim2 = ObjectAnimator.ofFloat(newBall, "width", newBall.getWidth(),
                    newBall.getWidth() + 50);
            squashAnim2.setDuration(duration/4);
            squashAnim2.setRepeatCount(1);
            //因为压扁完还要恢复原状,这里必须是逆向返回起始值,使用ValueAnimator.REVERSE
            squashAnim2.setRepeatMode(ValueAnimator.REVERSE);
            squashAnim2.setInterpolator(new DecelerateInterpolator());
            //依旧是构成压扁效果的动画,小球压扁后Y轴上移25f,也就是半个小球的高度,看起来是压扁到恢复原状的效果了.
            ValueAnimator stretchAnim1 = ObjectAnimator.ofFloat(newBall, "y", endY,
                    endY + 25f);
            stretchAnim1.setDuration(duration/4);
            stretchAnim1.setRepeatCount(1);
            stretchAnim1.setInterpolator(new DecelerateInterpolator());
            stretchAnim1.setRepeatMode(ValueAnimator.REVERSE);
            //依旧是构成压扁效果的动画,小球高度减小25.
            ValueAnimator stretchAnim2 = ObjectAnimator.ofFloat(newBall, "height",
                    newBall.getHeight(), newBall.getHeight() - 25);
            stretchAnim2.setDuration(duration/4);
            stretchAnim2.setRepeatCount(1);
            stretchAnim2.setInterpolator(new DecelerateInterpolator());
            stretchAnim2.setRepeatMode(ValueAnimator.REVERSE);
            //小球回弹动画,弹回原点.
            ValueAnimator bounceBackAnim = ObjectAnimator.ofFloat(newBall, "y", endY,
                    startY);
            bounceBackAnim.setDuration(duration);
            bounceBackAnim.setInterpolator(new DecelerateInterpolator());
            // Sequence the down/squash&stretch/up animations
            //这里是AnimatorSet的灵活用法,上篇中只讲了AnimatorSet的playSequentially()和playTogether()方法
            //AnimatorSet作为一个动画容器,这里规定了bounceAnim在squashAnim1动画前面播放,
            //然后squashAnim1播放的同时又播放squashAnim2,stretchAnim1,stretchAnim2这三个动画
            //最后规定bounceBackAnim这个小球弹起动画必须放在stretchAnim2这个动画后面去播放,也就是放最后.
            //这些规则,保证了小球从下落到底部,然后在底部产生压扁效果,然后恢复原状,再弹回原点的整个过程.
            AnimatorSet bouncer = new AnimatorSet();
            bouncer.play(bounceAnim).before(squashAnim1);
            bouncer.play(squashAnim1).with(squashAnim2);
            bouncer.play(squashAnim1).with(stretchAnim1);
            bouncer.play(squashAnim1).with(stretchAnim2);
            bouncer.play(bounceBackAnim).after(stretchAnim2);

            // Fading animation - remove the ball when the animation is done
            //这里动画定义透明度从可见到完全不可见,就是小球弹回原点后消失了的效果.
            ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
            //消失的时间很短,所以这个值不要设置过长.
            fadeAnim.setDuration(250);
            //添加监听接口,当动消失的动画结束,就将小球(ShapeHolder的子类)从队列中删去.
            fadeAnim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    balls.remove(((ObjectAnimator)animation).getTarget());

                }
            });

            // Sequence the two animations to play one after the other
            AnimatorSet animatorSet = new AnimatorSet();
            //这里规定消失动画必须在整个下落回弹结束后才开始
            animatorSet.play(bouncer).before(fadeAnim);

            // Start the animation
            //启动动画
            animatorSet.start();

            //拦截触摸事件,返回true.
            return true;
        }

        private ShapeHolder addBall(float x, float y) {
            //创建椭圆Shape
            OvalShape circle = new OvalShape();
            //X和Y都是50f,则代表这个椭圆circle是一个半径为25f的圆
            circle.resize(50f, 50f);
            //上篇分析过ShapeDrawable的源码,可知下面是使用OvalShape去创建shapedrawable对象
            ShapeDrawable drawable = new ShapeDrawable(circle);
            ShapeHolder shapeHolder = new ShapeHolder(drawable);
            shapeHolder.setX(x - 25f);
            shapeHolder.setY(y - 25f);
            //下面是通过随机的方法去生成红绿蓝三个值.,从而组合成ARGB的值,设置为该圆的颜色
            int red = (int)(Math.random() * 255);
            int green = (int)(Math.random() * 255);
            int blue = (int)(Math.random() * 255);
            int color = 0xff000000 | red << 16 | green << 8 | blue;
            //每个ShapeDrawable对象都有自己的paint,直接getPaint()就能获取了
            Paint paint = drawable.getPaint(); //new Paint(Paint.ANTI_ALIAS_FLAG);
            int darkColor = 0xff000000 | red/4 << 16 | green/4 << 8 | blue/4;
            //给上面生成的ARGB值,添加圆的中心到边缘颜色从深到浅的渐变效果
            RadialGradient gradient = new RadialGradient(37.5f, 12.5f,
                    50f, color, darkColor, Shader.TileMode.CLAMP);
            paint.setShader(gradient);
            //到了这一步,圆的颜色效果已经确定了
            shapeHolder.setPaint(paint);
            balls.add(shapeHolder);
            return shapeHolder;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            for (int i = 0; i < balls.size(); ++i) {
                //画出小球
                ShapeHolder shapeHolder = balls.get(i);
                canvas.save();
                canvas.translate(shapeHolder.getX(), shapeHolder.getY());
                shapeHolder.getShape().draw(canvas);
                canvas.restore();
            }
        }
    }


在自己新建的Activity的view中直接container.addView(new MyAnimationView(getActivity())); 就能看到效果了.


上面官方的例子没有用到自定义属性计算器Evaluator,于是为了和上面的效果对比明显,就写了一个奇葩的.

 /**
     * 自定义属性计算器
     */
    public class MyEvaluator implements TypeEvaluator
    {
        @Override
        public Object evaluate(float fraction, Object startValue, Object endValue) {
            fraction = (Float)startValue - (Float)endValue/10;
            return fraction;
        }
    }

然后在第一个动画中去设置它:

bounceAnim.setEvaluator(new MyEvaluator());

这样看看效果,是不是点击屏幕的时候,小球不再是直接从按下的地方开始下落,而是从按下的Y轴坐标更往上一些的地方下落.



值得注意的是,如果要使用自定义的对象去调用属性动画,必须在属性动画所用到的对象中设置相应属性的getter和setter方法才行.不然是没有效果的.


以上demo所用到的方法已经是比较复杂的属性动画的进阶用法,相关说明也在注释写上,所有复杂的动画都不是一两个动画就一蹴而就的,复杂的东西都由较为简单的东西去组合而成.就像代码中AnimatorSet的动画集制定了各种播放规则,组合了5,6个动画才实现一个小球从下落到底部,然后压扁恢复原状,再弹起,最后消失,这样的过程.


最后,希望能给你提供一点点帮助.感谢你阅读本文.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值