做者:不洗碗工做室 - catango
文章出处: Android动画
版权归做者全部,转载请注明出处前端
动画效果一直是人机交互的一个很是重要的部分,动画效果的引入,会让交互变得更加友好,让用户得到更加愉悦的体验。做为一个前端开发者,动画也是一个必会的技能。java
View Animation包含了Tween Animation、Frame Animation(Drawable Animation)。这些都是在安卓3.0以前的两种动画。android
帧动画有时也叫Drawable动画,它容许你实现像播放幻灯片同样的效果,这种动画的实质实际上是Drawable,因此这种动画的XML定义方式文件 通常放在res/drawable/目录下。帧动画的动画本质呢就是咱们的视觉残留。canvas
通常咱们会先在Drawable下面将动画资源引用好,而后在代码中调用start()/stop()来开始或者中止播放动画。固然咱们也能够在Java代码中建立逐帧动画,建立AnimationDrawable对象,而后调用 addFrame(Drawable frame,int duration)向动画中添加帧,接着调用start()和stop()而已~推荐是使用XML来定义动画。bash
具体的标签有:app
<animation-list>
:必须是根节点,包含多个< item>
元素。属性有android:oneshot true表明只执行一次,false循环执行。< item>
:animation-list的子项,包含的属性有:
举个例子,咱们在drawable中ide
< ?xml version="1.0" encoding="utf-8"?>
< animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false">
< item android:drawable="@color/black" android:duration="300"/>
< item android:drawable="@color/white" android:duration="300"/>
< /animation-list>复制代码
而后在kotlin中函数
img_test.apply {
setBackgroundResource(R.drawable.test_frame)
setOnClickListener {
if ((background as AnimationDrawable).isRunning)
(background as AnimationDrawable).stop()
else (background as AnimationDrawable).start()
}
}复制代码
这样当咱们点击图片的时候就会自动播放在drawable中设置的资源了。
注意,Animation的start方法不能在Activity的onCreate方法中调用,由于AnimationDrawable还未彻底附着到Window上。布局
Tween Animation(补间动画)只能应用于View对象,并且只支持一部分属性,如支持缩放旋转而不支持背景颜色的改变。并且对于Tween Animation,并不改变属性的值,它只是改变了View对象绘制的位置,而没有改变View对象自己,好比,你有一个Button,坐标(100,100),Width:100,Height:100,而你有一个动画使其移动(200,200),你会发现动画过程当中触发按钮点击的区域还是(100,100)-(200,200)。 动画
补间动画经过XML或Android代码定义,建议仍是使用XML文件定义,由于它更具可读性、可重用性。这里说一下,补间动画的全部父类都是它:
public abstract class Animation implements Cloneable复制代码
这个注意和属性动画的父类区分。
java类名 | xml关键字 | 描述信息 |
---|---|---|
AlphaAnimation | 放置在res/anim/目录下 | 渐变透明度动画效果 |
RotateAnimation | 放置在res/anim/目录下 | 画面转移旋转动画效果 |
ScaleAnimation | 放置在res/anim/目录下 | 渐变尺寸伸缩动画效果 |
TranslateAnimation | 放置在res/anim/目录下 | 画面转换位置移动动画效果 |
AnimationSet | 放置在res/anim/目录下 | 一个持有其它动画元素alpha、scale、translate、rotate或者其它set元素的容器 |
xml属性 | java方法 | 解释 |
---|---|---|
android:detachWallpaper | setDetachWallpaper(boolean) | 是否在壁纸上运行 |
android:duration | setDuration(long) | 动画持续时间,毫秒为单位 |
android:fillAfter | setFillAfter(boolean) | 控件动画结束时是否保持动画最后的状态 |
android:fillBefore | setFillBefore(boolean) | 控件动画结束时是否还原到开始动画前的状态 |
android:fillEnabled | setFillEnabled(boolean) | 与android:fillBefore效果相同 |
android:interpolator | setInterpolator(Interpolator) | 设定插值器(指定的动画效果,譬如回弹等) |
android:repeatCount | setRepeatCount(int) | 重复次数 |
android:repeatMode | setRepeatMode(int) | 重复类型有两个值,reverse表示倒序回放,restart表示从头播放 |
android:startOffset | setStartOffset(long) | 调用start函数以后等待开始运行的时间,单位为毫秒 |
android:zAdjustment | setZAdjustment(int) | 表示被设置动画的内容运行时在Z轴上的位置(top/bottom/normal),默认为normal |
xml属性 | java方法 | 解释 |
---|---|---|
android:fromAlpha | AlphaAnimation(float fromAlpha, …) | 动画开始的透明度(0.0到1.0,0.0是全透明,1.0是不透明) |
android:toAlpha | AlphaAnimation(…, float toAlpha) | 动画结束的透明度,同上 |
xml属性 | java方法 | 解释 |
---|---|---|
android:fromDegrees | RotateAnimation(float fromDegrees, …) | 旋转开始角度,正表明顺时针度数,负表明逆时针度数 |
android:toDegrees | RotateAnimation(…, float toDegrees, …) | 旋转结束角度,正表明顺时针度数,负表明逆时针度数 |
android:pivotX | RotateAnimation(…, float pivotX, …) | 缩放起点X坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%作为初始点、50%p表示以当前View的左上角加上父控件宽高的50%作为初始点) |
android:pivotY | RotateAnimation(…, float pivotY) | 缩放起点Y坐标,同上规律 |
xml属性 | java方法 | 解释 |
---|---|---|
android:fromXScale | ScaleAnimation(float fromX, …) | 初始X轴缩放比例,1.0表示无变化 |
android:toXScale | ScaleAnimation(…, float toX, …) | 结束X轴缩放比例 |
android:fromYScale | ScaleAnimation(…, float fromY, …) | 初始Y轴缩放比例 |
android:toYScale | ScaleAnimation(…, float toY, …) | 结束Y轴缩放比例 |
android:pivotX | ScaleAnimation(…, float pivotX, …) | 缩放起点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%作为初始点、50%p表示以当前View的左上角加上父控件宽高的50%作为初始点) |
android:pivotY | ScaleAnimation(…, float pivotY) | 缩放起点Y轴坐标,同上规律 |
xml属性 | java方法 | 解释 |
---|---|---|
android:fromXDelta | TranslateAnimation(float fromXDelta, …) | 起始点X轴坐标(数值、百分数、百分数p,譬如50表示以当前View左上角坐标加50px为初始点、50%表示以当前View的左上角加上当前View宽高的50%作为初始点、50%p表示以当前View的左上角加上父控件宽高的50%作为初始点) |
android:fromYDelta | TranslateAnimation(…, float fromYDelta, …) | 起始点Y轴从标,同上规律 |
android:toXDelta | TranslateAnimation(…, float toXDelta, …) | 结束点X轴坐标,同上规律 |
android:toYDelta | TranslateAnimation(…, float toYDelta) | 结束点Y轴坐标,同上规律 |
插值器是用来控制动画的变化速度,能够理解成动画渲染器,固然咱们也能够本身实现Interpolator 接口,自行来控制动画的变化速度,而Android中已经为咱们提供了五个可供选择的实现类:
上面这些都是补间动画的一些xml和java方法的简介,这些方法属性记不住没关系,我们能够随时查看的,最重要的仍是怎么去用。其实用法也是特别简单的,十分的套路,咱们只须要记住套路就能够。咱们就举个简单的旋转动画的例子吧。
首先是XML
< ?xml version="1.0" encoding="utf-8"?>
< rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromDegrees="0"
android:toDegrees="360"
android:duration="1000"
android:pivotX="50%"
android:pivotY="50%"
android:repeatCount="infinite"
/>复制代码
其中咱们用了@android:animaccelerate_decelerate_interpolator
这个插值器,就是上面所说的DecelerateInterpolator在动画开始的地方改变速度较快,而后开始减速。
写完XML后,在java代码中
Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_rotate);
testImg.setAnimation(animation);
animation.start();
animation.cancel();复制代码
咱们能够看到,先建立了一个Animation对象,而后在用AnimationUtils的loadAnimation方法将XML里定义的动画加载到animation对象中,而后经过View的setAnimation方法将animation传入,这样就将这个动画绑定到view上面了。最后经过animation的start和cancel方法来开始或者取消动画。 其中咱们能够为animation设置动画的监听,这个特别简单就再也不这赘述了...
Android 3.0之后引入了属性动画,属性动画能够垂手可得的实现许多View动画作不到的事。其实说白了,记住一点就行,属性动画实现原理就是修改控件的属性值实现的动画。固然,功能强大的代价就是使用起来要比较复杂。
属性动画全部的父类是它:
public abstract class Animator implements Cloneable复制代码
这个注意要和补间动画来区分。
接下来介绍一下相关的API:
使用流程:
举个例子:
//按轨迹方程来运动
private void lineAnimator() {
width = ly_root.getWidth();
height = ly_root.getHeight();
ValueAnimator xValue = ValueAnimator.ofInt(height,0,height / 4,height / 2,height / 4 * 3 ,height);
xValue.setDuration(3000L);
xValue.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 轨迹方程 x = width / 2
int y = (Integer) animation.getAnimatedValue();
int x = width / 2;
moveView(img_babi, x, y);
}
});
xValue.setInterpolator(new LinearInterpolator());
xValue.start();
}
private void moveView(View view, int rawX, int rawY) {
int left = rawX - img_babi.getWidth() / 2;
int top = rawY - img_babi.getHeight();
int width = left + view.getWidth();
int height = top + view.getHeight();
view.layout(left, top, width, height);
}复制代码
其中moveView方法是将View从新布局,xValue的值从参数列表就能够看出,而后设置每次更新的监听,在监听中每次调用moveView方法来改变View的布局。若是是组合动画的话,咱们能够这样:
//缩放效果
private void scaleAnimator(){
final float scale = 0.5f;
AnimatorSet scaleSet = new AnimatorSet();
ValueAnimator valueAnimatorSmall = ValueAnimator.ofFloat(1.0f, scale);
valueAnimatorSmall.setDuration(500);
ValueAnimator valueAnimatorLarge = ValueAnimator.ofFloat(scale, 1.0f);
valueAnimatorLarge.setDuration(500);
valueAnimatorSmall.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float scale = (Float) animation.getAnimatedValue();
img_babi.setScaleX(scale);
img_babi.setScaleY(scale);
}
});
valueAnimatorLarge.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float scale = (Float) animation.getAnimatedValue();
img_babi.setScaleX(scale);
img_babi.setScaleY(scale);
}
});
scaleSet.play(valueAnimatorLarge).after(valueAnimatorSmall);
scaleSet.start();
}复制代码
这个组合动画咱们能够用play after来设定动画实例,而后动画就先执行play的动画再执行after的实例了。固然,相似的方法还有一个with,就是两个动画会一块儿播放。
相比于ValueAnimator,ObjectAnimator可能才是咱们最常接触到的类,由于ValueAnimator只不过是对值进行了一个平滑的动画过渡,但咱们实际使用到这种功能的场景好像并很少。而ObjectAnimator则就不一样了,它是能够直接对任意对象的任意属性进行动画操做的,好比说View的alpha属性。还有ObjectAnimator在设计的时候就没有针对于View来进行设计,而是针对于任意对象的。
既然ObjectAnimator是继承自ValueAnimator的,那么它的用法应该是和ValueAnimator类似的。
ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);复制代码
其实这段代码的意思就是ObjectAnimator会帮咱们不断地改变textview对象中alpha属性的值,从1f变化到0f。而后textview对象须要根据alpha属性值的改变来不断刷新界面的显示,从而让用户能够看出淡入淡出的动画效果。
那么textview对象中是否是有alpha属性这个值呢?没有,不只textview没有这个属性,连它全部的父类也是没有这个属性的!这就奇怪了,textview当中并无alpha这个属性,ObjectAnimator是如何进行操做的呢?其实ObjectAnimator内部的工做机制并非直接对咱们传入的属性名进行操做的,而是会去寻找这个属性名对应的get和set方法,所以alpha属性所对应的get和set方法应该就是:
public void setAlpha(float value);
public float getAlpha();复制代码
ObjectAnimator的使用相比ValueAnimator简单多了,咱们只须要将它实例化后,其余的用法和ValueAnimator是同样的。
接下来要说下动画事件的监听,上面咱们ValueAnimator的监听器是 AnimatorUpdateListener,当值状态发生改变时候会回调onAnimationUpdate方法!
除了这种事件外还有:动画进行状态的监听, AnimatorListener,咱们能够调用addListener方法 添加监听器,而后重写下面四个回调方法:
加入AnimatorListener的话,四个方法你都要重写,这样写起来非常麻烦,不过Android已经给咱们提供好一个适配器类:AnimatorListenerAdapter,该类中已经把每一个接口 方法都实现好了,因此咱们这里只写一个回调方法也是能够的。
咱们在调用属性动画的时候,用到了ofInt,ofFloat,ofObject这些静态方法来建立ValueAnimator的实例,在例子中,咱们用到了ofInt,ofFloat,可是细心的同窗可能发现了,ValueAnimator还有个ofObject方法来构造ValueAnimator实例,那么这个是干什么用的呢?
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
ValueAnimator anim = new ValueAnimator();
anim.setObjectValues(values);
anim.setEvaluator(evaluator);
return anim;
}复制代码
咱们先戳进去看看这个源码的参数。第一个参数是名为TypeEvaluator的一个东西,这就是咱们要说的计算器,他是来告诉动画系统如何从初始值过渡到结束值的。前面提到过,TypeEvaluator是全部Evaluator的基类,这里再重复一遍,系统提供了如下的几种Evaluator:
固然,这些Evaluator都是TypeEvaluator实现。咱们先来看看TypeEvaluator这个接口长什么样吧:
public interface TypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);
}复制代码
就这个一个evaluate方法,简单吧?这里面的三个参数的含义依次是:
那么咱们来看看系统是如何实现TypeEvaluator这个接口的,我们就从IntEvaluator提及:
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}复制代码
实现了TypeEvaluator接口,重写了evaluate方法,其中返回的是动画的值。动画的值是多少呢?咱们从IntEvaluator的return语句中能够看出,动画的值 = 初始值 + 完成度 * (结束值 - 初始值) 这样当完成度为100%的时候,动画的值就是结束值了,一样的还有FloatEvaluator等。细心的同窗也发现了,这个TypeEvaluator里面传入的是一个泛型,这个类型也就是咱们接下来自定义的Object.
如今咱们举个自定义Evaluator的例子。
package com.example.yang.testkotlin.widget;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
/**
* @author YangCihang
* @since 17/10/18.
* email yangcihang@hrsoft.net
*/
public class CircleAnimView extends View {
public static final int RADIUS = 80;
private Point currentPoint;
private Paint mPaint;
private int mColor;//必须写这个属性的get和set,不然动画没法识别color属性
public CircleAnimView(Context context) {
super(context);
}
public CircleAnimView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init();
}
public CircleAnimView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
if (currentPoint == null) {
currentPoint = new Point(RADIUS, RADIUS);
drawCircle(canvas);
startAnimation();
} else {
drawCircle(canvas);
}
}
private void drawCircle(Canvas canvas) {
float x = currentPoint.x;
float y = currentPoint.y;
//用canvas draw,所以此控件大小应该为全屏大小才能够从左上到右下
canvas.drawCircle(x, y, RADIUS, mPaint);
}
private void startAnimation() {
Point startPoint = new Point(RADIUS, RADIUS);
Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
ObjectAnimator objectAnimator = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(),
Color.BLUE, Color.RED);
//动画集合将两个动画加到一块儿,with同时播放
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(anim).with(objectAnimator);
animatorSet.setStartDelay(1000L);
animatorSet.setDuration(3000L);
animatorSet.start();
}
/**
* 坐标变化
*/
class PointEvaluator implements TypeEvaluator<Point> {
@Override
public Point evaluate(float fraction, Point startValue, Point endValue) {
//初始值 + 完成度 * (结束值 - 初始值)
int x = (int) (startValue.x + fraction * (endValue.x - startValue.x));
int y = (int) (startValue.y + fraction * (endValue.y - startValue.y));
return new Point(x, y);
}
}
/**
* 颜色变化
*/
public class ColorEvaluator implements TypeEvaluator<Integer> {
@Override
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int alpha = (int) (Color.alpha(startValue) + fraction *
(Color.alpha(endValue) - Color.alpha(startValue)));
int red = (int) (Color.red(startValue) + fraction *
(Color.red(endValue) - Color.red(startValue)));
int green = (int) (Color.green(startValue) + fraction *
(Color.green(endValue) - Color.green(startValue)));
int blue = (int) (Color.blue(startValue) + fraction *
(Color.blue(endValue) - Color.blue(startValue)));
return Color.argb(alpha, red, green, blue);
}
}
//color的get和set方法~
public int getColor() {
return mColor;
}
public void setColor(int color) {
mColor = color;
mPaint.setColor(color);
invalidate();
}
}复制代码
在这里咱们定义了两个Evaluator类分别来表示坐标变化和颜色变化。注意,咱们要改变color属性的时候,必定要写setColor和getColor方法,缘由上面已经说过了。
Interpolator中文译名叫作插值器或者补间器。在补间动画的时候咱们介绍了几个经常使用的插值器,咱们能够回头去看看。补间动画和属性动画均可以用插值器的。并且补间动画还新增长了一个TimeInterpolator接口,该接口是用于兼容以前的Interpolator的(也就是Interpolator又继承了TimeInterpolator接口),这使得全部过去的Interpolator实现类均可以直接拿过来 放到属性动画当中使用!咱们能够调用动画对象的setInterpolator()方法设置不一样的Interpolator。好比:
animatorSet.setInterpolator(new AccelerateInterpolator(2f))复制代码
括号里面的值用于控制加速度的。
咱们自定义Interpolator其实就只要实现TimeInterpolator就能够了,重写也就只须要重写getInterpolation方法好比:
private class DecelerateAccelerateInterpolator implements TimeInterpolator {
@Override
public float getInterpolation(float input) {
if (input < 0.5) {
return (float) (Math.sin(input * Math.PI) / 2);
} else {
return 1 - (float) (Math.sin(input * Math.PI) / 2);
}
}
}复制代码
不过咱们若是须要作一些复杂的速率变化,就得须要数学功底了。