技术笔记

动画其实很简单——属性动画使用

2017-02-17  本文已影响78人  Gzw丶南山

属性动画介绍

上一篇我们讲了视图动画,而Android在API Level 11是推出了新的动画——属性动画,而它比视图动画强悍很多,除了View之外还可以作用在别的对象只想。还有一点很明确的区别,当我们运行视图动画后它原本View位置是不变的,哪怕我们调用了setFillAfter(true);它原本位置还是没有变,你可以通过设置侦听还检验,而属性动画却完全的被改变了。

在上一篇视图动画中我们已经介绍了我们在写动画前都需要考虑什么,从而构成一个完整的动画,我们这次会从属性动画基本动画、组合动画、布局动画和一些其他方面介绍属性动画的使用。

当然说到属性动画,最先想到的应该是Animator类,它是属性动画的基类,然后才是我们,使用到的ValueAnimator和ObjectAnimator,我们接下来主要会用到ObjectAnimator,来进行动画操作。

属性动画中的基础动画

我们最熟悉的基础动画大概就是旋转、渐变、平移和缩放了,那么我们就从我们最熟悉的部分开始。

//  这里为什么使用ofFloat设置动画呢?
//  其实还有很多方法,而ofFloat (Object target, String propertyName, 
//  float... values)是API11开始就可以使用了,所以兼容性很好
ImageView mAnimatorTarget; 
//  省略获取控件的步骤
ObjectAnimator animator = null;
//  渐变动画  
//  setAlpha()
animator = ObjectAnimator.ofFloat(mAnimatorTarget, "alpha", 1, 0);
//  平移动画 但是只是X轴上的
//  setTranslationX()
animator = ObjectAnimator.ofFloat(mAnimatorTarget, "translationX", 200);
//  旋转动画
//  setRotation()
animator = ObjectAnimator.ofFloat(mAnimatorTarget, "rotation", 0, 180);
animator.setDuration(1800);
animator.start();
//  缩放 同时对XY缩放
//  setScaleX()
//  setScaleY()
ObjectAnimator scaleX = ObjectAnimator.ofFloat(mAnimatorTarget, "scaleX", 1, 2);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(mAnimatorTarget, "scaleY", 1, 2);
 scaleX.setDuration(1800);
scaleX.start();
scaleY.setDuration(1800);
scaleY.start();

有些人可能对上面备注中的那些setAlpha(),setTranslationX(),setRotation()表示疑惑,为啥要写这个注释呢?我们应该再来看看ofFloat这个方法的第二个参数是一个String类型的属性名称,也就是你要进行什么动画,而"alpha","translationX"是要一定这么写么?写确实要这么写,但是我们搞懂为什么要这么写,这些其实拼写我们并不需要记住他们,因为它们是可以从某处找到的,我们需要打开View类,然后按Ctrl+F12,调出代码大纲,我么可以找到setAlpha(),setTranslationX(),setScaleX(),setScaleY()等等,而setXXXX()的XXX就是我们使用的第二个参数,这也解释了为什么上面缩放时候不能一步到位,而是同时进行X轴和Y轴才能有平时的效果。


View类的代码大纲.png

当然我们自定义可以属性,来实现同时缩放的效果,我们自定义个封装类,写了一个setScale,其实方法内部就是就是调用目标的setScaleX和setScaleY。

//  偷懒了 其实可以用弱引用来写的
public class WrapperView {
    private ImageView mTarget;
    private float scale;

    public WrapperView(ImageView target) {
        mTarget = target;
    }

    public void setScale(float scale) {
        this.scale = scale;
        mTarget.setScaleX(scale);
        mTarget.setScaleY(scale);
    }
}

//  这里自己设置了一个类并添加了自定义的属性
WrapperView wrapperView = new WrapperView(mAnimatorTarget);
//  这里的第二个属性名参数就可以写自己封装类的setXXX()
ObjectAnimator animator = ObjectAnimator.ofFloat(wrapperView, "scale", 1, 3);
animator.setDuration(1800);
animator.start();

组合动画

当我们提到组合动画的时候,我们肯定要区分它是串行还是并行,属性动画中提供了需要串并行的很多方法,下面我们就来一一介绍他们:

//  1.串并行的组合
//  组合 先平移然后再同时执行渐变 缩放 旋转
ObjectAnimator rotate = rotate.ofFloat(mAnimatorTarget, "rotation", 0, -45).setDuration(1800);
ObjectAnimator alpha = alpha.ofFloat(mAnimatorTarget, "alpha", 1, 0).setDuration(1800);
ObjectAnimator scaleY = scaleY.ofFloat(mAnimatorTarget, "scaleY", 1, 2).setDuration(1800);
ObjectAnimator scaleX = scaleX.ofFloat(mAnimatorTarget, "scaleX", 1, 2).setDuration(1800);
ObjectAnimator translationX = translationX.ofFloat(mAnimatorTarget, "translationX", 0, -200).setDuration(1800);
ObjectAnimator translationY = translationY.ofFloat(mAnimatorTarget, "translationY", 0, -200).setDuration(1800);
AnimatorSet set = new AnimatorSet();
//  with表示同时执行
set.play(alpha).with(scaleX).with(scaleY).with(rotate).with(translationX);
//  在alpha之前执行translationY
set.play(translationY).before(alpha);
set.start();

//  2.并行方式1  使用多个ObjectAnimator
ObjectAnimator rotate = rotate.ofFloat(mAnimatorTarget, "rotation", 0, 360).setDuration(1800);
ObjectAnimator scaleX = scaleX.ofFloat(mAnimatorTarget, "scaleX", 1, 2).setDuration(1800);
ObjectAnimator scaleY = scaleY.ofFloat(mAnimatorTarget, "scaleY", 1, 2).setDuration(1800);
//  这里面也可以调用三次start()
//  rotate.start();
//  scaleX.start();
//  scaleY.start();
//  or AnimatorSet的playTogether就可以了
AnimatorSet set = new AnimatorSet();
set.playTogether(rotate, scaleX, scaleY);
set.start();

//  并行方式2
//  一个ObjectAnimator 配合多个PropertyValuesHolder
PropertyValuesHolder rotateProperty = PropertyValuesHolder.ofFloat("rotation", 0, 360);
PropertyValuesHolder scaleXProperty = PropertyValuesHolder.ofFloat("scaleX", 1, 2);
PropertyValuesHolder scaleYProperty = PropertyValuesHolder.ofFloat("scaleY", 1, 2);
ObjectAnimator alpha = ObjectAnimator.ofPropertyValuesHolder(mAnimatorTarget, rotateProperty, scaleXProperty, scaleYProperty);
alpha.setDuration(1800);
alpha.start();

//  并行方式3 ViewPropertyAnimator实现 ...炒鸡简单
//  这里没有设置初始值, 但如果执行完并行1或者并行2 它发现现在的状态已经是下面 的状态了  所以就不会再执行了
ImageView mAnimatorTarget;
mAnimatorTarget.animate().rotation(360).scaleX(2).scaleY(2).setDuration(1800).start();

//  3.串行
ObjectAnimator rotate = rotate.ofFloat(mAnimatorTarget, "rotation", 0, 360);
ObjectAnimator scaleX = scaleX.ofFloat(mAnimatorTarget, "scaleX", 1, 2);
ObjectAnimator scaleY = scaleY.ofFloat(mAnimatorTarget, "scaleY", 1, 2);
//  这两种都可以 一个是设置延迟时间, 一个是AnimatorSet
//  rotate.setDuration(1800);
//  rotate.start();
//  scaleX.setDuration(1800);
//  scaleX.setStartDelay(1800);
//  scaleX.start();
//  scaleY.setDuration(1800);
//  scaleY.setStartDelay(3600);
//  scaleY.start();
set = new AnimatorSet();
//  串行的方法
set.playSequentially(rotate, scaleX, scaleY);
set.setDuration(1800);
set.start();

其他一些实现

1.实现倒计时功能
在Android中我们实现倒计时的功能其实方法很多,包括使用Handler + Thread,CountDownTimer,AsyncTask等等都可以实现倒计时功能,但是其实属性动画也可以实现,下面我们来看看实现代码:

//  计时器的另一种实现方式 通过ValueAnimator的onAnimationUpdate获取当前时间的值
ValueAnimator animator = ValueAnimator.ofInt(10, 0);
animator.setDuration(10000);
animator.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
                mShowCountDown.setText("" + animation.getAnimatedValue());
                }
        });

2.通过计算器按照某个轨迹运动
根据时间轴为不同时间设置不同的位置,然后手动刷新位置,从而实现动画:

ValueAnimator valueAnimator = new ValueAnimator();
float startX = mAnimatorTarget.getX();
float startY = mAnimatorTarget.getY();
valueAnimator.setDuration(2000);
//  平移值改变了Y值
valueAnimator.setObjectValues(new PointF(startX, startY), new PointF(startX + TRANS_X, startY));
valueAnimator.setEvaluator(new TypeEvaluator<PointF>() {
        @Override
        public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
                //  fraction 从0到1 表示时间轴
                //  这里轨迹为y = -(1/150)x*x + 4x
                PointF pointF = new PointF();
                float d = fraction * TRANS_X;
                pointF.x = startValue.x + d;
                //  Android坐标相反
                pointF.y = startValue.y + (1.0f / 150f) * d * d - 4 * d;
                return pointF;
        }
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
                //  手动更新位置
                PointF pointF = (PointF) animation.getAnimatedValue();
                mAnimatorTarget.setX(pointF.x);
                mAnimatorTarget.setY(pointF.y);
        }
});
valueAnimator.start();

3.关键帧
Android为我们提供了一个名叫Keyframe的一个类,官方对它描述就是,它是用来时间/对应值的一个键值对的类,它本身是抽象类,我们使用的过程系统会帮我获取他的实例并使用,下面我来看看它如何使用和使用效果:

//  效果是看上去赚了一圈又转回来
//  通过关键帧 设置时间轴0的时候为0度 时间轴一半的时候为360度 时间轴最后又回到0度
Keyframe k1 = Keyframe.ofFloat(0f, 0f);
Keyframe k2 = Keyframe.ofFloat(0.5f, 360f);
Keyframe k3 = Keyframe.ofFloat(1f, 0f);
//  通过PropertyValuesHolder配合时间轴设置旋转动画
PropertyValuesHolder valuesHolder = PropertyValuesHolder.ofKeyframe("rotation", k1, k2, k3);
//  配置动画
ObjectAnimator rotation = ObjectAnimator.ofPropertyValuesHolder(mAnimatorTarget, valuesHolder);
rotation.setDuration(2000);
rotation.start();
上面三个动画的效果图.gif

布局动画

布局动画Android为ViewGroupz准备一个过渡动画,当我向容器中添加或移除控件时都可以为其设置布局动画,使用方法是调用ViewGroup的setLayoutTransition(),设置对应的布局动画,而LayoutTransition这个类就是我们需要自己创建的,代码实现和效果如下:

//  为根布局设置LayoutTransition
mAnimatorLayoutRoot.setLayoutTransition(getLayoutTransition());

private LayoutTransition getLayoutTransition() {
    LayoutTransition layoutTransition = new LayoutTransition();
    layoutTransition.setAnimator(LayoutTransition.APPEARING, layoutTransition.getAnimator(LayoutTransition.APPEARING));
    layoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING,
    layoutTransition.getAnimator(LayoutTransition.CHANGE_APPEARING));
    layoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, layoutTransition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING));
    layoutTransition.setAnimator(LayoutTransition.CHANGING, layoutTransition.getAnimator(LayoutTransition.CHANGING));
    layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, layoutTransition.getAnimator(LayoutTransition.DISAPPEARING));
    return layoutTransition;
}

//  新增和删除控件的逻辑
final Button button = new Button(this);
button.setBackground(getResources().getDrawable(R.drawable.btn_with_ripple));
button.setTextColor(Color.WHITE);
button.setText("Button = " + count++ + "  Click to disappear");
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                LinearLayout.LayoutParams.WRAP_CONTENT);
layoutParams.setMargins(0, 32, 0, 0);
button.setLayoutParams(layoutParams);
button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        mAnimatorLayoutRoot.removeView(button);
    }
});
mAnimatorLayoutRoot.addView(button);
布局动画效果.gif

基本上的介绍以及说完了,其实还有很多细节需要去官方文档再挖掘,代码已经上传到GIthub了,地址如下:

https://github.com/GzwJaaaelu/AnimApp

我希望可以站在初学者&自学者的角度把Android中的知识点很清楚的介绍给大家,希望大家喜欢。 如果有错误希望指出来,有问题或者没看懂,都可以来问我的

上一篇下一篇

猜你喜欢

热点阅读