昨天刷抖音的时候,被一个在路灯下诵读《战国策》、《尚书》、《左转》的流浪汉感动。
今天为了致敬大师,以大师的图片作为素材来演示一下Android中的属性动画。
Android的动画分为补间动画、帧动画、属性动画。(本文中的实例工程下载地址:)
补间动画:透明度、缩放、旋转、平移,通过设置动画的起点、终点、执行时间及插值器来计算某一时间点中相应的值,从而补充这个时间点上的动画,最终实现从起始点到终点的平滑过渡。它直接作用的对象是VIEW视图,并不是VIEW的某一个属性,如translationX属性、translationY属性。
帧动画:播放一组图片,我们小时候都玩过,就是不断翻书,每一页上的小人就会连贯的动起来。
属性动画:通过改变VIEW的属性,如X,Y,alpha,rotate属性来实现平移、透明度、旋转等。而且还可以自定义属性。补间动画与属性动画最大的区别是在平移上,一个按钮执行了补件动画的X平移后,你点击它是不会有反应的,只有点击按钮最开始的那个位置才会响应,这说明补间动画不是真正的平移,而是在目标位置重新绘制了一个该按钮的副本,并非真身。
今天我们来着重介绍一下属性动画。我们定义一个按钮,通过单击按钮来执行一些示例动画。
1. 布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/btStartAnimate"
android:background="@mipmap/king"
android:layout_centerInParent="true"
android:onClick="startAnimate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="start!"
/>
</RelativeLayout>
在此我们为Button定义了一个单击响应函数startAnimate,在这个函数里我们将演示一些属性动画的用法。
2. MainActivity.java代码
2.1 演示补间动画的“假移动”
package com.example.propertyanimate;
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.graphics.PointF;
import android.media.tv.TvContract;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.AnimationUtils;
import android.view.animation.AnticipateInterpolator;
import android.view.animation.BounceInterpolator;
import android.view.animation.CycleInterpolator;
import android.view.animation.OvershootInterpolator;
import android.widget.Button;
import android.widget.Toast;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
/**
* 动画测试
* @param v: 就是布局里的BUTTON
*/
int position = 0;
public void startAnimate(final View v)
{
//补间动画START:
// 当按钮移动到目标位置时,点击按钮是不起作用的,即不会弹出“start Animate”,
//只有在原来的位置处单击才会响应,这说明它并没有真正的移动,只是在目标位置处又重新绘制了一个按钮。
Toast.makeText(this,"start Animate",Toast.LENGTH_LONG).show();
Animation anim = AnimationUtils.loadAnimation(this,R.anim.translate);
v.startAnimation(anim);
}
在此,我们在startAnimate里先演示了下补件动画,用来说明补件动画的平移是“假平移”。当第一次点击按钮时,按钮移动到了右下角,这时你在右下角点击该按钮时,该按钮没有任何响应(不弹出任何Toast提示),只有点击原位置处的时候才会有提示,这说明它还在原地。上面代码中的R.anim.translate动画XML文件如下:
<?xml version="1.0" encoding="utf-8"?>
<translate
xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="50%p"
android:toYDelta="50%p"
android:fillAfter="true"
android:duration="500">
</translate>
2.2 用属性动画来实现平移
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(v,"translationX",0,500);
objectAnimator.setDuration(2000);
objectAnimator.start();
我们可以把这几行代码放在startAnimate函数里,单击按钮来演示按钮水平移动。我们着重看一下ObjectAnimator.ofFloat函数的这几个参数:
v: 表示动画作用在按钮这个view上
translationX: 按钮的属性,表示水平位置。
0,500 这是一组浮点数,可以有多个参数。0表示按钮的起始位置、500表示相对于起始点的距离。
最终执行效果是:按钮从原来的位置水平向右移动500px。当我们第二次点击按钮时会重复执行此动画,但是还是从最初的位置水平右移500px,而不是从第一次执行的末尾位置开始向右移动500px。你可以这样给出这组数值0,100,500,按钮会先移动到100px处,然后再移动到500px处。注意:这里的距离都是相对于按钮最初起始点的相对位移。
除了X轴平移外,我们还可以实现以下与补件动画同样的动画效果:
1. Y轴平移
ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(v,"translationY",0,500);
objectAnimatorY.setDuration(3000);
objectAnimatorY.start();
2. Z轴平移
ObjectAnimator objectAnimatorZ = ObjectAnimator.ofFloat(v,"translationZ",0,20);
objectAnimatorZ.setDuration(3000);
objectAnimatorZ.start();
3. 旋转
ObjectAnimator objectAnimatorRotation = ObjectAnimator.ofFloat(v,"rotationY",0,360);
objectAnimatorRotation.setDuration(5000);
objectAnimatorRotation.start();
绕Y轴旋转,当然你可以改成rotationX,rotationZ.
4. 缩放
ObjectAnimator objectAnimatorScaleX = ObjectAnimator.ofFloat(v,"scaleX",0,1);
objectAnimatorScaleX.setDuration(5000);
objectAnimatorScaleX.start();
ObjectAnimator objectAnimatorScaleY = ObjectAnimator.ofFloat(v,"scaleY",0,1);
objectAnimatorScaleY.setDuration(5000);
objectAnimatorScaleY.start()
宽、高 缩放:从小变大。
2.3 同时执行多个动画
2.3.1 方法1:使用AnimatorSet
//X
ObjectAnimator objectAnimatorX = ObjectAnimator.ofFloat(v,"translationX",0,500);
objectAnimatorX.setDuration(500);
//Y
ObjectAnimator objectAnimatorY = ObjectAnimator.ofFloat(v,"translationY",0,500);
objectAnimatorY.setDuration(500);
//将上面2个动画添加到集合里
ArrayList<Animator> animatorArrayList = new ArrayList<>();
animatorArrayList.add(objectAnimatorX);
animatorArrayList.add(objectAnimatorY);
AnimatorSet animationSet = new AnimatorSet();
//同时执行集合里的多个动画
animationSet.playTogether(animatorArrayList);
animationSet.start();
2.3.2 方法2:监听“属性值”时并行执行动画
ObjectAnimator animator = ObjectAnimator.ofFloat(v,"hello",0,100);
animator.setDuration(2000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float)valueAnimator.getAnimatedValue();
//valueAnimator.getAnimatedFraction();实质就是value/100
v.setScaleX((float)(0 + value/100));
v.setScaleY((float)(0 + value/100));
}
});
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
Log.i("AnimatorListener","onAnimationStart");
}
@Override
public void onAnimationEnd(Animator animator) {
Log.i("AnimatorListener","onAnimationEnd");//只能监听到END
}
@Override
public void onAnimationCancel(Animator animator) {
Log.i("AnimatorListener","onAnimationCancel");
}
@Override
public void onAnimationRepeat(Animator animator) {
Log.i("AnimatorListener","onAnimationRepeat");
}
});
animator.start();
这里ObjectAnimator.ofFloat(v,"hello",0,100)与之前的不一样,其中"hello"是一个VIEW不存在的属性,这个动画只关心值得变化:从。0到100。 为何ObjectAnimator可以这样用,是因为ObjectAnimator继承于ValueAnimator,而ValueAnimator就可以这样用,只关心值得变化。我们使用为ObjectAnimator对象设置如下监听器就可以监听动画执行情况,可以获取到某一时刻动画执行的进度值:
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float)valueAnimator.getAnimatedValue();
//valueAnimator.getAnimatedFraction();实质就是value/100
v.setScaleX((float)(0 + value/100));
v.setScaleY((float)(0 + value/100));
}
});
在这里我们调用valueAnimator.getAnimatedValue()来获取动画执行过程中某一时间点的值。执行总时长为2000毫秒,由animator.setDuration(2000)决定。比如当动画执行到1秒时,这里的value一般情况应该是50. 我们在这个监听函数里主要并行执行宽、高两个缩放动画:v.setScaleX, v.setScaleY. 缩放的范围是0-1,某一时间点缩放的比例刚好是 (0-100)值动画执行的进度比例,即value/100,也就是valueAnimator.getAnimatedFraction(); 这里的运行效果是:一张图片从无到原始大小的放大效果。
2.3.3 方法3:使用ValueAnimator
原理同方法2一样,因为方法2中的ObjectAnimator继承于ValueAnimator。
//方法3:直接使用ValueAnimator,原理与方法2一样
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f,100f);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float value = (float)valueAnimator.getAnimatedValue();
//valueAnimator.getAnimatedFraction();实质就是value/100
v.setScaleX((float)(0 + value/100));
v.setScaleY((float)(0 + value/100));
}
});
valueAnimator.start();
这次使用的是ObjectAnimator的父类ValueAnimator,与ObjectAnimator的区别是 ofFloat函数不用传递“属性”了,在上一方法2中ObjectAnimator.ofFloat不得不传递一个参数:属性名。同样,在ValueAnimator中添加监听动画值得变化,即0-100在2000毫秒内的变化,原理和过程同方法2,在此不再赘述。
2.3.4 方法4:PropertyValuesHolder
创建多个PropertyValuesHolder,每一个PropertyValuesHolder关联一个动画。最后一起执行多个PropertyValuesHolder。
PropertyValuesHolder propertyValuesHolderScaleX = PropertyValuesHolder.ofFloat("scaleX",1f,0.7f,1f);
PropertyValuesHolder propertyValuesHolderScaleY = PropertyValuesHolder.ofFloat("scaleY",1f,0.7f,1f);
PropertyValuesHolder propertyValuesHolderAlpha = PropertyValuesHolder.ofFloat("alpha",1f,0.7f,1f);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(v,
propertyValuesHolderScaleX,
propertyValuesHolderScaleY,
propertyValuesHolderAlpha);
objectAnimator.setDuration(2000);
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = valueAnimator.getAnimatedFraction();
float value = (float) valueAnimator.getAnimatedValue();
Log.i("PropertyValuesHolder","fraction="+fraction+",value="+value);
}
});
objectAnimator.start();
代码分析:
Step1. 我们在这里创建了3个PropertyValuesHolder,propertyValuesHolderScaleX,propertyValuesHolderScaleY,propertyValuesHolderAlhap,分别是代表缩放宽、高、透明度。
Step2. 使用ObjectAnimator.ofPropertyValuesHolder函数,以Step1中的3个PropertyValuesHolder为参数创建一个ObjectAnimator对象,这时ObjectAnimator相当于一个AnimateSet。
Step3. objectAnimator.start()启动动画,相当于同时执行了3个动画。
我们发现每一个PropertyValuesHolder动画都有3个值,1, 0.7, 1这几个值叫关键帧。例如缩放:
PropertyValuesHolder.ofFloat("scaleX",1f,0.7f,1f); 表示图片先从原始大小缩放到原大小的70%,然后再恢复成原大小。我们通过objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()){....}函数中打印的日志可以看出:当动画执行到fraction = 0.5时,即执行了一半时,图片会缩小到最中间的关键帧 0.7,即原大小的70%。也就是说从1到0.7与从0.7到1的缩放动画所经历的时间是相等的,因为它们是关键帧。
2.4 属性动画估值器
为什么要用估值器,那我们先来看一下估值器有什么作用。我们发现上面的例子中,大多数属性都是VIEW自带的属性,而且动画执行过程中某一时刻对应的动画值都是由系统计算出来的,代码如下:
objectAnimator.setDuration(2000);
objectAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = valueAnimator.getAnimatedFraction();
float value = (float) valueAnimator.getAnimatedValue();
Log.i("PropertyValuesHolder","fraction="+fraction+",value="+value);
}
});
objectAnimator.start();
安卓系统是通过valueAnimator.getAnimatedValue();这个API函数来获取2000毫秒期间某一时间点动画执行到的属性值。计算方法是:根据动画执行的进度比例(时间比例)来计算出当时的属性值。然而这种计算方法我们不能控制和修改。为了我们可以自定义某一时刻属性值的计算方法(算法),我们就得用估值器,利用来自定义值的计算方法。我们先看代码,用代码来讲解估值器如何使用,以下代码实现了一个抛物线动画,就是VIEW沿着抛物线的轨迹移动.
//估值器: 某一时间点的 属性值是 自定义计算出来的。
ValueAnimator valueAnimator = new ValueAnimator();
valueAnimator.setObjectValues(new PointF(0,0));
valueAnimator.setDuration(4000);
//设置一个估值器来计算控件的X坐标与Y坐标
valueAnimator.setEvaluator(new TypeEvaluator<PointF>() {
@Override
public PointF evaluate(float fraction, PointF start, PointF end) {
PointF pointF = new PointF();
pointF.x = 100f * (fraction * 4);// s = vt; y = 1/2g t*t
pointF.y = 0.5f*100*(fraction*4)*(fraction*4);
Log.i("onAnimationUpdate","x0="+pointF.x+",y0="+pointF.y+",fraction="+fraction);
return pointF;
}
});
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
v.setX(pointF.x);
v.setY(pointF.y);
Log.i("onAnimationUpdate","x="+pointF.x+",y="+pointF.y);
}
});
valueAnimator.start();
第一步:valueAnimator.setObjectValues(new PointF(0,0)); 设置一个动画的初始值为(0,0),表示VIEW的起始坐标(X=0,Y=0),在这里我们可以把值的类型设置为Point对象,这完全颠覆了我们之前只能设置float、int等基本类型的习惯。
第二步:valueAnimator添加一个估值器:
valueAnimator.setEvaluator(new TypeEvaluator<PonitF>()
evaluate(float fraction,PonitF start, PointF end){....}
);
我们发现泛型参数是我们自己的 "属性对象类型"PointF. 然后重新evaluate函数,在这个函数内我们自己计算出我们的value;
PointF pointF = new PointF();
pointF.x = 100f * (fraction * 4);// s = vt; y = 1/2g t*t
pointF.y = 0.5f*100*(fraction*4)*(fraction*4);
Log.i("onAnimationUpdate","x0="+pointF.x+",y0="+pointF.y+",fraction="+fraction);
return pointF;
在这里我们new了一个PonitF,并计算它的x,y坐标,计算方法就是 x = vt(速度*时间), y = 1/2*g*t*t(g是个常量加速度)。最后将这个PointF当做一个Value返回。到时候我们的值监听器就会在某一时间进度上返回这个PointF,作为animation.getAnimatedValue()的返回值。值监听器的代码如下:
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
PointF pointF = (PointF) animation.getAnimatedValue();
v.setX(pointF.x);
v.setY(pointF.y);
Log.i("onAnimationUpdate","x="+pointF.x+",y="+pointF.y);
}
});
在动画值更新监听器里通过调用animation.getAnimatedValue()返回某一时刻的值:PointF。然后我们把View的坐标(x,y)设置成PointF中的(x,y). 最终VIEW的运行轨迹就是一条抛物线。
2.5 加速器
//设置加速器
ObjectAnimator oa = ObjectAnimator.ofFloat(v, "translationY", 0f,300);
oa.setDuration(500);
//设置加速器---
// oa.setInterpolator(new AccelerateInterpolator(5));//加速
// oa.setInterpolator(new AccelerateDecelerateInterpolator());//先加速后减速
// oa.setInterpolator(new AnticipateInterpolator(8)); //
oa.setInterpolator(new OvershootInterpolator());//滑出
// oa.setInterpolator(new CycleInterpolator(4)); //振荡
// oa.setInterpolator(new BounceInterpolator()); //阻尼,回弹
oa.start();
通过setInterpolator函数来为动画设置加速器,比如第一个oa.setInterpolator(new AccelerateInterpolator(5));设置了一个“”速度增加“ 的加速器,动画的运行效果是平移速度越来越快。其他几个“加速器” 大家可以尝试运行一下看看效果。