前面给我们分析了Android
中的帧动画
和补间动画
的特点和用法
Android动画之补间动画用法最全详解
Android 动画之帧动画用法详解
Android
官方在Anrdoid 3.0
以后又推出了一种新的动画即属性动画
,既然前面的帧动画
和补间动画
能帮助我们实现大部分的Android动画效果,那么官方为什么还要推出这种新的属性动画
呢?
原因1:
补间动画
作用的对象是View
,也就是作用的对象是Android
中的控件,如ImageView
、Button
、TextView
等,也可以作用在布局上如LinearLayout
、ConstraintLayout
、RelativeLayout
等,但是对于一些不是View
的对象,无法对这些对象进行动画操作。比如我们要对某个控件的某个属性做进行动画操作,如其颜色,这个颜色也可以看成一个对象,但其并不是View
对象,补间动画
就无法实现,属性动画
可以对这个颜色值做动画, 能实现一些更加复杂的动画效果。
原因2:
补间动画只是改变了View
的视觉效果,而不会真正去改变View的属性
比如我们对一个图片进行AlphaAnimation
,并在动画前后打印其值
Log.i("MainActivity","动画开始前mImageView alpha="+mImageView.getAlpha());
animation = new AlphaAnimation(0, 1);
animation.setDuration(2000);
mImageView.startAnimation(animation);
Log.i("MainActivity","动画结束后mImageView alpha="+mImageView.getAlpha());
从打印的结果可以看出,补间动画
并没有改变View
的属性值,而属性动画
不但会帮助我们实现View
动画的一些视觉效果,而且还能改变View
的属性值。
1、属性动画都是通过ValueAnimator
类和ObjectAnimator
类来完成,其中ObjectAnimator
类是对对象做动画,ValueAnimator
类是对值做动画。
2、PropertyValueHolder类
可以同时执行多个动画,AnimatorSet
l类可以将多个动画按一定的秩序先后执行。
3、TypeEvaluator
估值器和Interpolator
差值器
我们了解了下面6个类的基本用法,就基本彻底掌握了属性动画
ObjectAnimator
类是属性动画中非常重要的一个类,可以通过该类对View
不仅可以实现一些基本的移、旋转、缩放和透明度四种基本变换动画,还能实现一些其他属性值的变换动画。
实现方式既可以通过Java代码,也可以通过XML方式来实现,下面我们来分别介绍下两种方式基本用法。
首先我们先来看一下ObjectAnimator
类最基本的方法
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
方法中第一个参数Object target
的作用对象通常是View
,也就是Android中的控件或布局。
方法中第二个参数String propertyName
通常是需要执行动画的属性,具体值如下表所示
属性 | 值的用法 |
---|---|
rotation | 以屏幕方向为轴的旋转度数 |
alpha | 透明度 |
translationX / translationY | X/Y方向的位移 |
scaleX /scaleY | X/Y方向的缩放倍数 |
rotationX / rotationY | 以X/Y轴为轴的旋转度数 |
方法中第三个参数float... values
表示属性的变换范围,该参数可以传多个值。
添加一些代码来看一下效果
ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();
该动画效果表示控件ImageView
的透明度在5s
内由1
变换到0
,再由0
变回 1
。效果如下:
ObjectAnimator
的其他方法使用如下:
ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
animator.setDuration(2000);
//动画延迟500ms执行
animator.setStartDelay(500);
//执行重复次数 +1
animator.setRepeatCount(3);
// 设置动画重复播放模式 RESTART -执行完一遍后重新执行
// REVERSE -执行完一遍后 从末位置往前执行
animator.setRepeatMode(ValueAnimator.RESTART);
//监听值变换
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Log.i("MainActivity","value:" +animation.getAnimatedValue());
}
});
animator.start();
用XML
实现对象动画
1、在res
目录下新建animator
文件夹
2、animator
文件夹下创建动画XML文件,如animator_alpha.xml
往该xml文件中输入如下代码
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="alpha"
android:valueFrom="1"
android:valueTo="0"
android:valueType="floatType" />
Java代码中通过加载该xml启动动画
ImageView imageView = findViewById(R.id.imageView);
Animator animator = AnimatorInflater.loadAnimator(Main2Activity.this, R.animator.animator_alpha);
animator.setTarget(imageView);
animator.start();
值动画通过控制值的变化,之后 手动赋值给对象的属性,从而实现动画。
ValueAnimator
的核心方法如下
ValueAnimator ofFloat(float... values) -- 浮点型数值
ValueAnimator ofInt(int... values) -- 整型数值
ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) -- 自定义对象类型
下面我们来添加值动画,在值动画的监听函数里 来获取值得变化,根据值的变化对控件设置相应的属性。这里的属性可以是控件的任意属性。
final ImageView imageView = findViewById(R.id.imageView);
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(5000);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
Log.d("MainActivity", "cuurent value is " + currentValue);
imageView.setAlpha(currentValue);
}
});
anim.start();
效果如下
从下面的打印结果中也可以看出,值动画返回了一系列值。
PropertyValueHolder
可以让前面的一些动画同时执行。
ImageView imageView = findViewById(R.id.imageView);
PropertyValuesHolder alphaProper = PropertyValuesHolder.ofFloat("alpha", 0.5f, 1f);
PropertyValuesHolder scaleXProper = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f);
PropertyValuesHolder scaleYProper = PropertyValuesHolder.ofFloat("scaleY", 0f, 1f);
PropertyValuesHolder translationXProper = PropertyValuesHolder.ofFloat("translationX", -100, 100);
PropertyValuesHolder translationYProper = PropertyValuesHolder.ofFloat("translationY", -100, 100);
PropertyValuesHolder rotationProper = PropertyValuesHolder.ofFloat("rotation", 0, 360);
ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(imageView, alphaProper,
scaleXProper, scaleYProper,translationXProper,translationYProper,rotationProper);
animator.setDuration(5000);
animator.start();
前面的PropertyValueHolder
类能实现将多个动画同时执行,AnimatorSet
类不仅能让多个动画同时执行,还能让多个动画按一定的顺序执行,同时也能穿插多个动画同时执行。
主要的方法如下:
after(Animator anim)
将现有动画插入到传入的动画之后执行
after(long delay)
将现有动画延迟指定毫秒后执行
before(Animator anim)
将现有动画插入到传入的动画之前执行
with(Animator anim)
将现有动画和传入的动画同时执行
ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator rotate = ObjectAnimator.ofFloat(imageView, "rotation", 0f, 360f);
ObjectAnimator translationX = ObjectAnimator.ofFloat(imageView, "translationX", -100, 100f);
ObjectAnimator translationY = ObjectAnimator.ofFloat(imageView, "translationY", -100, 100f);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(imageView, "scaleX", 0, 1f);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(imageView, "scaleY", 0, 1f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate)
.with(alpha)
.after(scaleX)
.before(translationX)
.after(1000)
.before(translationY)
.with(scaleY);
animSet.setDuration(5000);
animSet.start();
效果如下:
前面的动画属性的变换都是均匀变换,可以通过差值器(Interpolator)
来控制值变化的速率
ImageView imageView = findViewById(R.id.imageView);
ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 0f, 1f);
animator.setDuration(5000);
//加速查值器,参数越大,速度越来越快
animator.setInterpolator(new AccelerateInterpolator(5));
animator.start();
从下面的运行的效果可以看出,用了加速差值器以后,该图片alpha动画前期变化很慢,都后面变化越来越快。
动画名称 | 效果 |
---|---|
AccelerateInterpolator | 加速查值器,参数越大,速度越来越快 |
DecelerateInterpolator | 减速差值起,和加速查值器相反 |
AccelerateDecelerateInterpolator | 先加速后减速 |
AnticipateInterpolator | 先后退在加速前进 |
AnticipateOvershootInterpolator | 以X/Y轴为轴的旋转度数 |
BounceInterpolator | 弹球效果插值 |
CycleInterpolator | 周期运动插值 |
LinearInterpolator | 匀速插值 |
OvershootInterpolator | 先快速完成动画,再回到结束样式 |
我们还可以通过自定义类实现Interpolator
接口来实现一些自定义的差值器效果。
在前面的值动画(ValueAnimator)
中和对象动画(ObjectAnimator)
有一个传对象的方法:
ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
ObjectAnimator ofObject(Object target, String propertyName,
TypeEvaluator evaluator, Object... values)
这些方法动都需要传一个TypeEvaluator,我们先来看下这个类的源码
public interface TypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);
}
从TypeEvaluator估值器
的源码可以看出该类的作用就是告诉动画,如何从起始值过度到结束值。
Android
源码中有好几个类实现来该接口,也就是系统提供的一些默认估值器, 我们以FloatEvaluator
为例看下其实现代码。
public class FloatEvaluator implements TypeEvaluator<Number> {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
从FloatEvaluator
的实现可以看出在evaluate
方法中用结束值减去初始值,算出它们之间的差值,然后乘以fraction
这个系数,再加上初始值,那么就得到当前动画的值了
我们也可以以该方法为例 实现一个自定义的估值器实现一个背景颜色值的变化
我们先定义一个默认估值器类MyTypeEvaluator
,该类自定义了颜色过渡的方式
package com.lucashu.animation;
import android.animation.TypeEvaluator;
public class MyTypeEvaluator implements TypeEvaluator<String> {
private int mCurrentRed = -1;
private int mCurrentGreen = -1;
private int mCurrentBlue = -1;
@Override
public String evaluate(float fraction, String startValue, String endValue) {
int startRed = Integer.parseInt(startValue.substring(1, 3), 16);
int startGreen = Integer.parseInt(startValue.substring(3, 5), 16);
int startBlue = Integer.parseInt(startValue.substring(5, 7), 16);
int endRed = Integer.parseInt(endValue.substring(1, 3), 16);
int endGreen = Integer.parseInt(endValue.substring(3, 5), 16);
int endBlue = Integer.parseInt(endValue.substring(5, 7), 16);
// 初始化颜色的值
if (mCurrentRed == -1) {
mCurrentRed = startRed;
}
if (mCurrentGreen == -1) {
mCurrentGreen = startGreen;
}
if (mCurrentBlue == -1) {
mCurrentBlue = startBlue;
}
// 计算初始颜色和结束颜色之间的差值
int redDiff = Math.abs(startRed - endRed);
int greenDiff = Math.abs(startGreen - endGreen);
int blueDiff = Math.abs(startBlue - endBlue);
int colorDiff = redDiff + greenDiff + blueDiff;
if (mCurrentRed != endRed) {
mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
fraction);
} else if (mCurrentGreen != endGreen) {
mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
redDiff, fraction);
} else if (mCurrentBlue != endBlue) {
mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
redDiff + greenDiff, fraction);
}
// 将计算出的当前颜色的值组装返回
String currentColor = "#" + getHexString(mCurrentRed)
+ getHexString(mCurrentGreen) + getHexString(mCurrentBlue);
return currentColor;
}
/**
* 根据fraction值来计算当前的颜色。
*/
private int getCurrentColor(int startColor, int endColor, int colorDiff,
int offset, float fraction) {
int currentColor;
if (startColor > endColor) {
currentColor = (int) (startColor - (fraction * colorDiff - offset));
if (currentColor < endColor) {
currentColor = endColor;
}
} else {
currentColor = (int) (startColor + (fraction * colorDiff - offset));
if (currentColor > endColor) {
currentColor = endColor;
}
}
return currentColor;
}
/**
* 将10进制颜色值转换成16进制。
*/
private String getHexString(int value) {
String hexString = Integer.toHexString(value);
if (hexString.length() == 1) {
hexString = "0" + hexString;
}
return hexString;
}
}
再自定义一个View,在该类中画一个矩形框
public class MyView extends View {
private String color;
private Paint mPaint;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.WHITE);
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
mPaint.setColor(Color.parseColor(color));
invalidate();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(0, 0, 500,500, mPaint);
}
}
自顶一个View在布局文件中添加如下
<com.lucashu.animation.MyView
android:id="@+id/myview"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_marginBottom="100dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
将在估值器加到动画中,该动画作用在我们自定义的View上
MyView imageView = findViewById(R.id.myview);
ObjectAnimator anim = ObjectAnimator.ofObject(
imageView,"color", new MyTypeEvaluator(),
"#0000FF","#FF0000");
anim.setDuration(5000);
anim.start();