View系列:动画,看我如何一步一步攻克面试官

Animation.RELATIVE_TO_SELF, 1.0f);
animSet.setFillAfter(true);
animSet.setDuration(2000);
animSet.setInterpolator(new BounceInterpolator());
animSet.addAnimation(scaleAnim);
animSet.addAnimation(rotateAnim);
animSet.addAnimation(alphaAnim);
animSet.addAnimation(traslateAnim);
ivTarget.startAnimation(animSet);

自定义Animation

private class MoveAnimation extends Animation {
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
mInterpolatedTime = interpolatedTime;
invalidate();
}
}

Frame Animation(逐帧动画)
<?xml version="1.0" encoding="utf-8"?>

//false 一直重复执行,true执行一次。





//xml中setBackgroud属性用getBackgroud()获取Drawable
//src属性,getDrawable()方法获取Drawable
ImageView mImageViewFilling = (ImageView) findViewById(R.id.imageview_animation_list_filling);
AnimationDrawable animDrawable = ((AnimationDrawable) mImageViewFilling.getBackground());
animDrawable.start();

代码中添加
rocketAnimation = new AnimationDrawable();
rocketAnimation.addFrame(getResources().getDrawable(R.drawable.rocket_thrust1, 200);
rocketAnimation.addFrame(getResources().getDrawable(R.drawable.rocket_thrust2, 200);
rocketAnimation.addFrame(getResources().getDrawable(R.drawable.rocket_thrust3, 200);
rocketImage.setBackground(rocketAnimation);

  • 需要注意的是,动画的启动需要在view和window建立连接后才可以绘制,比如上面代码是在用户触摸后启动。如果我们需要打开界面就启动动画的话,则可以在Activity的onWindowFocusChanged()方法中启动。

Property Animation(属性动画)

属性动画是指通过改变View属性来实现动画效果,包括:ValueAnimator、ObjectAnimator、TimeAnimator

ValueAnimator

该类主要针对数值进行改变,不对View进行操作

ValueAnimator animator = ValueAnimator.ofInt(0,400);
animator.setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//拿到监听结果,自己处理。
int curValue = (int)animation.getAnimatedValue();
tvTextView.layout(curValue,curValue,curValue+tv.getWidth(),curValue+tv.getHeight());
}
});
animator.setInterpolator(new LinearInterpolator());
animator.start();

监听:

/**

  • 监听器一:监听动画变化时的实时值
  • 添加方法为:public void addUpdateListener(AnimatorUpdateListener listener)
    /
    public static interface AnimatorUpdateListener {
    void onAnimationUpdate(ValueAnimator animation);
    }
    /
    *
  • 监听器二:监听动画变化时四个状态
  • 添加方法为: public void addListener(AnimatorListener listener)
    */
    public static interface AnimatorListener {
    void onAnimationStart(Animator animation);
    void onAnimationEnd(Animator animation);
    void onAnimationCancel(Animator animation);
    void onAnimationRepeat(Animator animation);
    }

/**

  • 移除AnimatorUpdateListener
    /
    void removeUpdateListener(AnimatorUpdateListener listener);
    void removeAllUpdateListeners();
    /
    *
  • 移除AnimatorListener
    */
    void removeListener(AnimatorListener listener);
    void removeAllListeners();
ObjectAnimator

ValueAnimator只能对数值进行计算,不能直接操作View,需要我们在监听器中自己去操作控件。这样就有点麻烦了,于是Google在ValueAmimator的基础上又派生出了ObjerctAnimator类,让动画直接与控件关联起来。

ObjectAnimator rotateObject = ObjectAnimator.ofFloat(tvPropertyTarget,
“Rotation”,
0, 20, -20, 40, -40, 0);
rotateObject.setDuration(2000);
rotateObject.start();

setter/getter 属性名

在View中已经实现了一些属性的setter/getter方法,在构造动画时可以直接对控件使用。

  • 要使用一个属性,必须在控件中有对应的setter/getter方法,属性setter/getter方法的命名必须以驼峰方式
  • ObjectAnimator在使用该属性的时候,会把setter/getter和属性第一个字母大写转换后的字段拼接成方法名,通过反射的方式调用该方法传值。 所以,上文中"Rotation/rotation"可以首字母可以大小写都行

//1、透明度:alpha
public void setAlpha(float alpha)

//2、旋转度数:rotation、rotationX、rotationY
public void setRotation(float rotation) //围绕Z轴旋转
public void setRotationX(float rotationX)
public void setRotationY(float rotationY)

//3、平移:translationX、translationY
public void setTranslationX(float translationX)
public void setTranslationY(float translationY)

//缩放:scaleX、scaleY
public void setScaleX(float scaleX)
public void setScaleY(float scaleY)

image-20210603130556023

自定义属性做动画

public class PointView extends View {
private float mRadius = 0;
public PointView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mRadius == 0) {
mRadius = 50;
}
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(150,200,mRadius,paint);
}

public void setRadius(float radius){
this.mRadius = radius;
invalidate();
}

public float getRadius(){
return mRadius;
}
}

//radius属性首字母大小写无所谓,最后都是要转成大些的。
ObjectAnimator pointAnim = ObjectAnimator.ofFloat(pointPropertyAnim,
“Radius”,
10, 40, 40, 80, 60, 100, 80, 120,60);
pointAnim.start();

什么时候需要用到get方法呢? 前面构造动画时传入的取值范围都是多个参数,Animator知道是从哪个值变化到哪个值。当只传入一个参数的时候,Animator怎么知道哪里是起点?这时通过get方法找到初始值。 如果没有找到get方法,会用该参数类型的默认初始值复制。如:ofInt方法传入一个值,找不到get方法时,默认给的初始值是Int类型的初始值0.

原理

image-20210603131108900ObjectAnimator的方便之处在于:

ValueAnimator只负责把数值给监听器,ObjectAnimator只负责调用set方法。至于实现,都是靠我们自己或者set中的方法。

插值器

设置动画运行过程中的进度比例,类似匀速变化、加速变化、回弹等

  • 参数input:是一个float类型,它取值范围是0到1,表示当前动画的进度,取0时表示动画刚开始,取1时表示动画结束,取0.5时表示动画中间的位置,其它类推。
  • 返回值:表示当前实际想要显示的进度。取值可以超过1也可以小于0,超过1表示已经超过目标值,小于0表示小于开始位置。(给估值器使用
  • 插值器默认每10ms刷新一次

public class PointInterpolator implements Interpolator {
/**

  • input 是实际动画执行的时间比例 0~1
  • newInput 你想让动画已经执行的比例 0~1。
  • 注意:都是比例,而不是实际的值。
  • setDuration(1000)情况下:前200ms走了3/4的路程比例,后800ms走了1/4的路程比例。
    /
    @Override
    public float getInterpolation(float input) {
    if (input <= 0.2) {//后1/4的时间,输出3/4的比例
    float newInput = input
    4;
    return newInput;
    }else {//后3/4的时间,输出1/4的比例
    float newInput = (float) (input - 0.2)/4 + 0.8f;
    return newInput;
    }
    }
    }

使用方式和默认插值器

在xml和代码中使用插值器,省略代码中使用方式

<?xml version="1.0" encoding="utf-8"?>

<scale xmlns:android=“http://schemas.android.com/apk/res/android”
// 通过资源ID设置插值器
android:interpolator="@android:anim/overshoot_interpolator"
android:duration=“3000”
android:fromXScale=“0.0”
android:fromYScale=“0.0”
android:pivotX=“50%”
android:pivotY=“50%”
android:toXScale=“2”
android:toYScale=“2” />

内置插值器动画展示

Android动画之Interpolator

Android动画插值器

作用资源ID对应的Java类
动画加速进行@android:anim/acceler

《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享

te_interpolator | Acceleraterplator |
| 快速完成动画,超出再回到到结束样式 | @android:anim/overshoot_interpolator | OvershootInterpolator |
| 先加速再减速 | @android:anim/accelerate_decelerate_interpolator | AccelerateDecelerateInterpolator |
| 先退后再加速前进 | @android:anim/anticipate_interpolator | AnticipateInterpolator |
| 先退后再加速前进,超出终点后再回终点 | @android:anim/anticipate_overshoot_interpolator | AnticipateOvershootInterpolator |
| 最后阶段弹球效果 | @android:anim/bounce_interpolator | BounceInterpolator |
| 周期运动 | @android:anim/cycle_interpolator | CycleInterpolator |
| 减速 | @android:anim/decelerate_interpolator | DecelerateInterpolator |
| 匀速 | @android:anim/linear_interpolator | LinearInterpolator |

估值器

设置 属性值 从初始值过渡到结束值 的变化具体数值

  • 参数fraction: 表示当前动画的进度(插值器返回值
  • 返回值:表示当前对应类型的取值,也就是UpdateListener接口方法中传入的值

public class PointEvaluator implements TypeEvaluator {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
int radius = (int) (startValue.getRadius() +
fraction*(endValue.getRadius() - startValue.getRadius()));
return new Point(radius);
}
}

自定义插值器、估值器、属性的使用:

public void doAnimation(){
//ObjectAnimator animator = ObjectAnimator.ofInt(mView, “Radius”, 20, 80);
ValueAnimator animatior = new ValueAnimator();
animatior.setObjectValues(new Point(20), new Point(80));
animatior.setInterpolator(new PointInterpolator());
animatior.setEvaluator(new PointEvaluator());

animatior.setDuration(2000);
animatior.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
animatior.start();
}

PropertyValuesHolder

它其中保存了动画过程中所需要操作的属性和对应的值

通过ObjectAnimator.ofFloat(Object target, String propertyName, float… values)构造的动画,ofFloat()的内部实现其实就是将传进来的参数封装成PropertyValuesHolder实例来保存动画状态,后期的各种操作也是以PropertyValuesHolder为主。

//将需要操作的多个属性和值封装起来,一起放到ObjectAnimator中,相当于set操作。
PropertyValuesHolder rotateHolder = PropertyValuesHolder.ofFloat(“Rotation”, 0, 360, 0);
PropertyValuesHolder scaleXHolder = PropertyValuesHolder.ofFloat(“scaleX”, 1, 2, 1,2,1);
PropertyValuesHolder scaleYHolder = PropertyValuesHolder.ofFloat(“scaleY”, 1, 2, 1,2,1);
ObjectAnimator objectAnim = ObjectAnimator.ofPropertyValuesHolder(ivHolderTarget,
rotateHolder,
scaleXHolder,
scaleYHolder);
objectAnim.setDuration(2000);
objectAnim.setInterpolator(new LinearInterpolator());
objectAnim.start();

KeyFrame(主要帧)

如果想要更精确的控制动画,想要控制整个动画过程的某个点或某个时段达到的值,可以通过自定义插值器或估值器来实现,但是那样又有些费事,并且不容易计算这段时间内值的变化。 这时可以用Keyframe来实现,即设置好某个时间点和值,系统会自动计算该点和上个点之间,值的变化。

/***

  • 实现左右摇晃,每边最后有震动的效果。
  • 摇晃角度100度:0.2f/0.20.4/0.40.5,分别设置不同的角度和加速器。
  • 每个比例点达到哪个角度,这在估值器中也能做到,但是需要自己算每个时间段内值的变化过程。
  • KeyFrame可以设置好 比例-值 以后,系统根据默认或设置的加速器改变:上个点和该点内的值如何变换。
  • 这样可以更精确的控制动画过程,同时也不用自己费劲去计算值因该如何变换。
    */
    Keyframe kfRotation1 = Keyframe.ofFloat(0, 0); //第一帧,如果没有该帧,会直接跳到第二帧开始动画。
    //第二帧 0.2f时达到60度,线性加速应该作用于从0~0.2f的这段时间,而不是作用在0.2~0.4f这段。因为已经定好60度是要的结果了,那么实现就应该在前面这段。
    Keyframe kfRotation2 = Keyframe.ofFloat(0.2f, 60);
    kfRotation2.setInterpolator(new LinearInterpolator());
    Keyframe kfRotation3 = Keyframe.ofFloat(0.4f, 100);
    kfRotation3.setInterpolator(new BounceInterpolator());
    Keyframe kfRotation4 = Keyframe.ofFloat(0.5f, 0);
    kfRotation4.setInterpolator(new LinearInterpolator()); //最少有2帧
    Keyframe kfRotation5 = Keyframe.ofFloat(0.7f, -60);
    kfRotation5.setInterpolator(new LinearInterpolator());
    Keyframe kfRotation6 = Keyframe.ofFloat(0.9f, -100);
    kfRotation6.setInterpolator(new BounceInterpolator());
    Keyframe kfRotation7 = Keyframe.ofFloat(1f, 0);//最后一帧,如果没有该帧,会以最后一个KeyFrame做结尾
    kfRotation7.setInterpolator(new LinearInterpolator());

Keyframe kfScaleX1 = Keyframe.ofFloat(0, 1);
Keyframe kfScaleX2 = Keyframe.ofFloat(0.01f,2.8f);
Keyframe kfScaleX3 = Keyframe.ofFloat(0.8f,2.0f);
Keyframe kfScaleX4 = Keyframe.ofFloat(1f,1.0f);

Keyframe kfScaleY1 = Keyframe.ofFloat(0, 1);
Keyframe kfScaleY2 = Keyframe.ofFloat(0.01f,2.8f);
Keyframe kfScaleY4 = Keyframe.ofFloat(0.8f,2.0f);
Keyframe kfScaleY5 = Keyframe.ofFloat(1f,1.0f);

PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofKeyframe(“rotation”, kfRotation1, kfRotation2, kfRotation3,kfRotation4, kfRotation5, kfRotation6, kfRotation7);
PropertyValuesHolder scaleXHolder = PropertyValuesHolder.ofKeyframe(“scaleX”, kfScaleX1, kfScaleX2, kfScaleX3, kfScaleX4);
PropertyValuesHolder scaleYHolder = PropertyValuesHolder.ofKeyframe(“scaleY”, kfScaleY1, kfScaleY2, kfScaleY4, kfScaleY5);

ObjectAnimator objectAnim = ObjectAnimator.ofPropertyValuesHolder(ivHolderTarget,
rotationHolder,
scaleXHolder,
scaleYHolder);
objectAnim.setDuration(1500);

AnimatorSet

AnimatorSet针对ValueAnimator和ObjectAnimator都是适用的,但一般而言,我们不会用到ValueAnimator的组合动画。

playTogether/playSequentially

无论是playTogether还是playSequentially方法,它们只是,仅仅是激活了动画什么时候开始,并不参与动画的具体操作。 例如:如果是playTogether,它只负责这个动画什么时候一起激活,至于anim1/anim2/anim3…哪个马上开始,哪个有延迟,哪个会无限重复,set都不管,只负责一起激活。 如果是playSequentially,它只负责什么时候开始激活第一个(因为有可能set设置延迟),并在第一个动画结束的时候,激活第二个,以此类推。

ObjectAnimator anim1 = ObjectAnimator.ofInt(mTv1, “BackgroundColor”, 0xffff00ff, 0xffffff00, 0xffff00ff);

ObjectAnimator anim2 = ObjectAnimator.ofFloat(mTv1, “translationY”, 0, 400, 0);
anima2.setStartDelay(2000);
anima2.setRepeatCount(ValueAnimator.INFINITE);

ObjectAnimator anim3 = ObjectAnimator.ofFloat(mTv2, “translationY”, 0, 400, 0);
anim3.setStartDelay(2000);

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(anim1, anim2, anim3);//playSequentially(按次序播放)
animatorSet.setDuration(2000);
animatorSet.setStartDelay(2000);
animatorSet.start();

play(x).with(x)
  • play(anim1).with(anim2):2000ms后set开始激活动画,anim1启动,再过2000ms后anim2启动。
  • play(anim2).with(anim1):2000ms后set开始激活动画,再过2000ms后启动anim2,并且启动anim1.
set监听

addListener监听的是AnimatorSet的start/end/cacle/repeat。不会监听anim1/anim2的动画状态的。

联合动画XML实现
单独设置和Set中设置
  • 以set为准:

//设置单次动画时长
public AnimatorSet setDuration(long duration);
//设置加速器
public void setInterpolator(TimeInterpolator interpolator)
//设置ObjectAnimator动画目标控件
public void setTarget(Object target)

ObjectAnimator anim1 = ObjectAnimator.ofFloat(mTv1, “translationY”, 0, 400, 0);
anim1.setDuration(500000000);

ObjectAnimator anim2 = ObjectAnimator.ofFloat(mTv2, “translationY”, 0, 400, 0);
anim2.setDuration(3000);//每次3000,而不是3次3000ms
anim2.setRepeatCount(3);

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(tv2TranslateY).with(tv1TranslateY);
animatorSet.setDuration(2000);//以Set为准
animatorSet.start();

setDuration()是指单个动画的时间,并不是指总共做完这个动画过程的时间。比如:anim2中设置了3000ms,重复3次。是指每次3000ms,不是3次3000ms。
另外animatorSet设置了时间以后,anim1/anim2虽然也设置了,但是这时以set为准。即,anim1/anim2的单个动画时间为2000ms。只不过anim2是每次2000ms,重复3次,共6000ms。

  • 不以set为准:setStartDelay

ObjectAnimator anim1 = ObjectAnimator.ofFloat(mTv1, “translationY”, 0, 400, 0);
ObjectAnimator anim2 = ObjectAnimator.ofFloat(mTv2, “translationY”, 0, 400, 0);
anim2.setStartDelay(2000);

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.addListener(new Animator.AnimatorListener(){…});
animatorSet.play(anim1).with(anim2);
animatorSet.setStartDelay(3000);//指的是Set的激活延迟,而不是动画延迟
animatorSet.setDuration(2000);
animatorSet.start();

setStartDelay不会覆盖单个动画的该方法,只会延长set的激活时间。所以,上面代码中动画的启动过程是:3000ms后set开始激活动画,anim1启动,再过2000ms后anim2启动。

ViewPropertyAnimator

属性动画已不再是针对于View而进行设计的了,而是一种对数值不断操作的过程,我们将属性动画对数值的操作过程设置到指定对象的属性上来,从而形成一种动画的效果。 虽然属性动画给我们提供了ValueAnimator类和ObjectAnimator类,在正常情况下,基本都能满足我们对动画操作的需求,但ValueAnimator类和ObjectAnimator类本身并不是针对View对象的而设计的,而我们在大多数情况下主要都还是对View进行动画操作的。

因此Google官方在Android 3.1系统中补充了ViewPropertyAnimator类,这个类便是专门为View动画而设计的。

  • 专门针对View对象动画而操作的类
  • 更简洁的链式调用设置多个属性动画,这些动画可以同时进行
  • 拥有更好的性能,多个属性动画是一次同时变化,只执行一次UI刷新(也就是只调用一次invalidate,而n个ObjectAnimator就会进行n次属性变化,就有n次invalidate)
  • 每个属性提供两种类型方法设置。scaleX()/scaleXBy()
  • 该类只能通过View的animate()获取其实例对象的引用
  • 自动调用start

btn.animate()
.alpha(0.5f)
.rotation(360)
.scaleX(1.5f).scaleY(1.5f)
.translationX(50).translationY(50)
.setDuration(5000);

public ViewPropertyAnimator scaleY(float value) {
animateProperty(SCALE_Y, value);
return this;
}

public ViewPropertyAnimator scaleYBy(float value) {
animatePropertyBy(SCALE_Y, value);
return this;

private void animateProperty(int constantName, float toValue) {
float fromValue = getValue(constantName);
float deltaValue = toValue - fromValue;
animatePropertyBy(constantName, fromValue, deltaValue);
}

private void animatePropertyBy(int constantName, float byValue) {
float fromValue = getValue(constantName);
animatePropertyBy(constantName, fromValue, byValue);
}

//----------scale/rotation/alpha等方法,到最后都是调用该方法-----------------------------------

/**

  • Utility function, called by animateProperty() and animatePropertyBy(), which handles the
  • details of adding a pending animation and posting the request to start the animation.
  • @param constantName The specifier for the property being animated
  • @param startValue The starting value of the property
  • @param byValue The amount by which the property will change
    */
    private void animatePropertyBy(int constantName, float startValue, float byValue) {
    // First, cancel any existing animations on this property
    if (mAnimatorMap.size() > 0) {
    Animator animatorToCancel = null;
    Set animatorSet = mAnimatorMap.keySet();
    for (Animator runningAnim : animatorSet) {
    PropertyBundle bundle = mAnimatorMap.get(runningAnim);
    // 如果在该属性上已经有动画,则结束该属性上的动画。
    if (bundle.cancel(constantName)) {
    if (bundle.mPropertyMask == NONE) {
    animatorToCancel = runningAnim;
    break;
    }
    }
    }
    if (animatorToCancel != null) {
    animatorToCancel.cancel();
    }
    }

// 封装该属性和值,放入集合中
NameValuesHolder nameValuePair = new NameValuesHolder(constantName,
startValue, byValue);
mPendingAnimations.add(nameValuePair);
mView.removeCallbacks(mAnimationStarter);
//每次操作属性都post了该Runnable类,但是每次都把原来的移除了,始终都在维护一个最新的集合post。
animatorSet) {
PropertyBundle bundle = mAnimatorMap.get(runningAnim);
// 如果在该属性上已经有动画,则结束该属性上的动画。
if (bundle.cancel(constantName)) {
if (bundle.mPropertyMask == NONE) {
animatorToCancel = runningAnim;
break;
}
}
}
if (animatorToCancel != null) {
animatorToCancel.cancel();
}
}

// 封装该属性和值,放入集合中
NameValuesHolder nameValuePair = new NameValuesHolder(constantName,
startValue, byValue);
mPendingAnimations.add(nameValuePair);
mView.removeCallbacks(mAnimationStarter);
//每次操作属性都post了该Runnable类,但是每次都把原来的移除了,始终都在维护一个最新的集合post。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值