天师

天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

动画

发表于 2018-01-07 分类于 动画
本文字数: 14k 阅读时长 ≈ 13 分钟

动画笔记

作者:彭欢亮

时间:2018/1/7 星期日 上午 10:10:10

Android 动画的分类 :

一、帧动画

实现帧动画的两种方式

  1. 利用xml形式实现

在drawable目录下创建一个animation-list xml 文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
	<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:drawable="@mipmap/ic_setup"
android:duration="500" />

<item
android:drawable="@mipmap/ic_should_get"
android:duration="500" />

<item
android:drawable="@mipmap/ic_should_pay"
android:duration="500" />
</animation-list>

MainActivity代码

1
2
3
4
5
6
ImageView imageView = (ImageView) findViewById(R.id.iv);
imageView.setImageResource(R.drawable.animation_z);
AnimationDrawable animation = (AnimationDrawable) imageView.getDrawable();
// animation.start();
//将animationDrawable设置为imageView的背景
imageView.setBackground(animation);
  1. 帧动画实现的第二种方式,利用代码实现 (使用场景:帧动画图片太多的情况下,建议使用此方法,不用在xml文件中一个个添加)

MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
ImageView imageView = (ImageView) findViewById(R.id.iv)
AnimationDrawable anim = new AnimationDrawable();
anim.addFrame(bit2dra(R.mipmap.ic_setup), 200);
anim.addFrame(bit2dra(R.mipmap.ic_should_get), 200);
anim.addFrame(bit2dra(R.mipmap.ic_should_pay), 200);
anim.setOneShot(false);
imageView.setImageDrawable(anim);

//bit2dra 将bitmap转换为drawable对象
private Drawable bit2dra(int sourceId) {
return new BitmapDrawable(getResources(), BitmapFactory.decodeResource(getResources(), sourceId));
}

其中 anim.setOneShot(boolean arg) 表示 :帧动画是否执行一次 true表示执行一次,false表示循环

二、补间动画

  • 补间动画的分类:

补间动画又可以分为四种形式,分别是 alpha(淡入淡出),translate(位移),scale(缩放大小),rotate(旋转)。

补间动画实现方式:

  1. 利用xml实现

首先在res目录下创建一个anim文件夹,即我们xml实现补间动画都是在res/anim/目录下添加

alpha_anim.xml动画实现

1
2
3
4
5
6
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromAlpha="1.0"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:toAlpha="0" />

scale_anim.xml动画实现

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<scale xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:fromXScale="1.0"
android:fromYScale="1.0"
android:pivotX="50%"
android:pivotY="50%"
android:toXScale="0.0"
android:toYScale="0.0" />

集合动画

set_anim.xml动画集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="持续时间"
android:interpolator="@[package:]anim/interpolator_resource"
android:shareInterpolator="boolean">

<alpha
android:fromAlpha="float"
android:toAlpha="float" />

<scale
android:fromXScale="float"
android:fromYScale="float"
android:pivotX="float"
android:pivotY="float"
android:toXScale="float"
android:toYScale="float" />

<translate
android:fromXDelta="float"
android:fromYDelta="float"
android:toXDelta="float"
android:toYDelta="float" />

<rotate
android:fromDegrees="float"
android:pivotX="float"
android:pivotY="float"
android:toDegrees="float" />

</set>

MainActivity代码

1
2
3
ImageView imageView = (ImageView) findViewById(R.id.iv);
Animation animation = AnimationUtils.loadAnimation(this, R.anim.scale_anim);
imageView.startAnimation(animation);

AnimationUtils是系统实现的动画工具类

  1. 补间动画通过代码实现

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void alpha(View v){
AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
anim.setDuration(1000);
v.startAnimation(anim);
}

public void rotate(View v){
RotateAnimation anim = new RotateAnimation(0.0f, -360f, v.getWidth()/2, v.getHeight()/2);
anim.setDuration(1000);
v.startAnimation(anim);
}

public void scale(View v){
ScaleAnimation anim = new ScaleAnimation(1.0f, 0.0f, 0.0f, 1.0f, v.getWidth() / 2, v.getHeight() / 2);
anim.setDuration(1000);
v.startAnimation(anim);
}

public void translate(View v){
TranslateAnimation anim = new TranslateAnimation(0.0f, 10.0f, 100f, 200f);
anim.setDuration(1000);
v.startAnimation(anim);
}
public void set(View v){
AnimationSet anim = new AnimationSet(this, null);
AlphaAnimation alpha = new AlphaAnimation(0.0f, 1.0f);
RotateAnimation rotate = new RotateAnimation(0.0f, -360f, v.getWidth()/2, v.getHeight()/2);
anim.setDuration(1000);
anim.addAnimation(alpha);
anim.addAnimation(rotate);
v.startAnimation(anim);
}

三、 Android 属性动画(Property Animation)

属性动画相关的类:

  1. ObjectAnimator (动画的执行类)
  2. ValueAnimator (动画的执行类)
  3. AnimatorSet (用于控制一组动画的执行:线性,一起,每个动画的先后执行等)
  4. AnimatorInflater ( 用户加载属性动画的xml文件)
  5. TypeEvaluator (类型估值,主要用于设置动画操作属性的值)
  6. TimeInterpolator(时间插值)

属性动画与补间动画的区别:

补间动画有一个最大的缺陷,就是它只是改变了View的显示效果而已,并不会真正的改变View的属性。场景:如果要改变view的宽度,补间动画实现不了,而属性动画可以。

ObjectAnimator用法

ObjectAnimator提供了几个方法(ofInt、ofFloat、ofObject…),都是用来设置动画作用的元素、作用的属性、动画开始、结束、以及中间的任意个属性值。

  • translationX、translationY:这两个属性作为一种增量来控制着View对象从它布局容器的左上角坐标开始的位置。
  • rotation、rotationX、rotationY:这三个属性控制着View对象围绕它的支点进行2D和3D的旋转。
  • scaleX和scaleY:这两个属性控制着View对象围绕它的支点进行2D缩放。
  • pivotX和pivotY:这两个属性控制着View对象的支点位置,围绕这个支点进行旋转和缩放变换处理。默认情况下,该支点的位置就是View对象的中心点。
  • alpha:它表示View对象的alpha透明度。
  • x、y:这是两个简单的实用的属性,它描述了View对象在它的容器中最终的位置。
  • BackgroundColor:改变背景颜色

注意 缩放、反转等都有中心点或者轴,默认中心缩放,和中间对称线为反转线

1
2
3
ObjectAnimator.ofFloat(imageView,"rotationX",0.0f,360.f,90.0f,360.0f)
.setDuration(2000)
.start();

PropertyValuesHolder:以先将动画属性和值暂时的存储起来,后一起执行,在有些时候可以使用替换掉AnimatorSet,减少代码量。

1
2
3
4
5
6
PropertyValuesHolder scaleX = PropertyValuesHolder.ofFloat("scaleX", 1, 0, 0.5f);
PropertyValuesHolder scaleY = PropertyValuesHolder.ofFloat("scaleY", 1, 0, 0.5f);
PropertyValuesHolder alpha = PropertyValuesHolder.ofFloat("alpha", 1, 0, 0.5f);
ObjectAnimator.ofPropertyValuesHolder(imageView,scaleX,scaleY,alpha)
.setDuration(2000)
.start();

ObjectAnimator简单的用法介绍完毕下面开始介绍ValueAnimator

ValueAnimator介绍以及用法

ValueAnimator是整个属性动画中最核心的一个类,前面介绍的ObjectAnimator也是继承自ValueAnimator。通过前面对ObjectAnimator的介绍,我们知道属性动画的实现机制是通过不断的地对View属性进行操作来实现的,而初始值和结束值之间的动画过渡就是由ValueAnimator这个类负责计算的。

它的内部使用一种时间循环的机制来计算值与值之间的动画过渡,我们只需要将初始值和结束值提供给ValueAnimator,并且告诉它动画所需运行的时长,那么ValueAnimator就会自动帮助我们完成从初始值平滑过渡到结束值这样的效果。

ValueAnimator本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画的实现过程。通常情况下,在ValueAnimator的AnimatorUpdateListener中监听数值的变化,从而完成动画的切换。

实现一个计时器效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void startTimeClock() {
final ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 60);
valueAnimator.setDuration(1000 * 60);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override
public void onAnimationUpdate(ValueAnimator animation) {
int time = (int) valueAnimator.getAnimatedValue();
Log.e(TAG, "time=" + time);
mTextView.setText(time + "");
}
});
valueAnimator.start();
}

实现一个物体自由落体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void verticalRun(View view) {
int mHeight = getResources().getDisplayMetrics().heightPixels;
ValueAnimator animator = ValueAnimator.ofFloat(0, mHeight - mBlueBall.getHeight());
animator.setTarget(mBlueBall);
animator.setDuration(1000)
.start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
{
@Override
public void onAnimationUpdate(ValueAnimator animation)
{
Float value = (Float) animation.getAnimatedValue();
Log.e(TAG, "onAnimationUpdate: value = "+value );

mBlueBall.setTranslationY((Float) animation.getAnimatedValue());
}
});

}

动画的监听事件

  1. animator.addListener(AnimatorListener、AnimatorUpdateListener 对象)

实现AnimatorLisener对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {

}

@Override
public void onAnimationEnd(Animator animation) {

}

@Override
public void onAnimationCancel(Animator animation) {

}

@Override
public void onAnimationRepeat(Animator animation) {

}
});

实现AnimatorUpdateListener对象

1
2
3
4
5
6
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {

}
});

实现AnimatorLisener、AnimatorUpdateListener 区别在于:前面这个监听需要实现所有的方法,而后面这个只需要实现其中一种方法即可。

  1. animator.addUpdateListener(AnimatorUpdateListener)

上面有个计时器的例子,可以通过实现的方法onAnimationUpdate(ValueAnimator animation)拿到相应的对象

AnimatorSet介绍与使用

介绍:动画的集合,在补间动画中也有个一个动画的集合类AnimationSet,所以AnimatorSet同样的效果,可以帮助我们实现组合属性动画的效果。

AnimatorSet这个类提供了一个play()方法,如果我们向这个方法中传入一个Animator对象(ObjectAnimator或者ValueAnimator)将会返回一个AnimatorSet.Builder的实例,AnimatorSet.Builder中包含了以下四个方法:

  • after(Animator anim) : 将现有动画插入到传入的动画之后执行。
  • after(long delay):将现有的动画延迟指定的毫秒后执行。
  • before(Animator anim):将现有的动画插入到传入的动画之前执行。
  • with(Animator anim):将现有的动画和传入的动画同时执行。

例如我们想让一个TextView先从屏幕外移动到屏幕内,然后旋转360度,同时旋转过程中进行淡入淡出的效果,就可以写出如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private void multiAnimator() {
// 移动动画
ObjectAnimator transAnimator = ObjectAnimator.ofFloat(mTextView, "translationX", -500f, 300f);

// 旋转动画
ObjectAnimator rotationAnimator = ObjectAnimator.ofFloat(mTextView, "rotation", 0f, 360f);

// 淡入淡出
ObjectAnimator alphaAnimator = ObjectAnimator.ofFloat(mTextView, "alpha", 1f, 0f, 1f);

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(rotationAnimator).with(alphaAnimator).after(transAnimator);
animatorSet.setDuration(5000);
animatorSet.setInterpolator(new LinearInterpolator());
animatorSet.start();
}

Animator监听器

一个完整的动画具有start、Repeat、End、Cancel四个过程,Android提供了接口,让我们能够很容易监听到这些事件。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.e(TAG, "onAnimationStart");
}

@Override
public void onAnimationEnd(Animator animation) {
Log.e(TAG, "onAnimationEnd");
}

@Override
public void onAnimationCancel(Animator animation) {
Log.e(TAG, "onAnimationCancel");
}

@Override
public void onAnimationRepeat(Animator animation) {
Log.e(TAG, "onAnimationRepeat");
}
});

使用xml文件创建属性动画

  1. 在res下创建一个animator的文件夹,即res/animator/.xml

例如:sacleX.xml

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>  
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:propertyName="scaleX"
android:valueFrom="1.0"
android:valueTo="2.0"
android:valueType="floatType" >
</objectAnimator>
  1. 使用AnimatorInflater来加载动画的资源文件,然后设置给相应的target即可。

MainActivity.java 代码如下:

1
2
3
4
5
public void loadXml(View view) {
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.scalex);
animator.setTarget(mBlueBall);
animator.start();
}

其它类似一致,下面讲解利用xml实现属性动画集合

animator_set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>  
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together" >

<objectAnimator
android:duration="1000"
android:propertyName="scaleX"
android:valueFrom="1"
android:valueTo="0.5" >
</objectAnimator>
<objectAnimator
android:duration="1000"
android:propertyName="scaleY"
android:valueFrom="1"
android:valueTo="0.5" >
</objectAnimator>
</set>

使用set标签,有一个orderring属性设置为together,还有另一个值:sequentially(表示一个接一个执行)。

MainActivity.java代码如下

1
2
3
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.scalex);
animator.setTarget(mBlueBall);
animator.start();

代码同上。

布局动画

主要使用LayoutTransition为布局的容器设置动画,当容器中的视图层次发生变化时存在过渡的动画效果。(略)

View的anim方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
mBlueBall.animate().alpha(0)
.y(200)
.setDuration(3000)
.withStartAction(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run: withStartAction");
}
})
.withEndAction(new Runnable() {
@Override
public void run() {
Log.e(TAG, "run: withEndAction");
}
})
.start();

属性动画原理

属性动画要求动画作用的对象提供该属性的get和set方法,属性动画根据你传递的该熟悉的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果让属性动画生效,要同时满足两个条件:

  1. object必须要提供setXxx方法,如果动画的时候没有传递初始值,那么还要提供getXxx方法,因为系统要去拿xxx属性的初始值(如果这条不满足,程序直接Crash)
  2. object的setXxx对属性xxx所做的改变必须能够通过某种方法反映出来,比如会带来ui的改变啥的(如果这条不满足,动画无效果但不会Crash)

属性动画为什么不能改变Button的宽高

因为Button的内部并没有自己的getWith()、setWidth()方法,为什么这样说,button继承至TextView,持有TextView的get、set方法。所以我们对Button做宽高的改变是,没有 效果的。

  1. 解决方法 1 : 用一个类来包装原始对象,间接为其提供get和set方法

包装类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class ViewWrapper {

private View mView;
public ViewWrapper(View mView) {
this.mView = mView;
}

public void setWidth(int width) {
mView.getLayoutParams().width = width;
mView.requestLayout();
}

public int getWidth() {
return mView.getLayoutParams().width;
}

public void setHeight(int height) {
mView.getLayoutParams().height = height;
mView.requestLayout();
}

public int getHeight() {
return mView.getLayoutParams().height;
}
}

MainActivity.java 代码:

1
2
3
4
5
6
ViewWrapper viewWrapper = new ViewWrapper(mBtn);
PropertyValuesHolder proW = PropertyValuesHolder.ofInt("width", width, width * 2);
PropertyValuesHolder proH = PropertyValuesHolder.ofInt("height", height, height * 3);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(viewWrapper, proW, proH);

objectAnimator.setDuration(3000).start();
  1. 解决方法2: 采用ValueAnimator,监听动画过程,自己实现属性的改变

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    private void performAnimate(final View mTarget, final int start, final int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
//持有一个IntEvaluator对象,用来估值使用
private IntEvaluator mEvaluator = new IntEvaluator();

@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获得当前动画的进度
int value = (Integer) animation.getAnimatedValue();
Log.e(TAG, "onAnimationUpdate: curr value = " + value);
float fraction = value / 100f;
mTarget.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
mTarget.requestLayout();
mTarget.invalidate();
}
});
valueAnimator.setDuration(3000).start();
}
  • 用ValueAnimator来做动画。ValueAnimator并不会实质的做什么。所以需要后面的AnimatorUpdateListener来做一些粗活儿。这里指定的从1到100也没有什么实质的作用。并不是把按钮的宽度从1变到100。后面的代码很清晰的表达了这一点。
  • 据这个fraction的百分比来计算当前时间片对应的按钮宽度是多少。当前宽度 = 初始宽度 + fraction * (结束宽度 - 初始宽度)。

​

# Android # 动画
官网基础
  • 文章目录
  • 站点概览
欢亮

欢亮

天下事有难易乎?为之,则难者亦易矣;不为,则易者亦难矣。
9 日志
6 分类
10 标签
GitHub E-Mail
Links
  • 二松同学
  1. 1. 动画笔记
    1. 1.1. 一、帧动画
    2. 1.2. 二、补间动画
    3. 1.3. 三、 Android 属性动画(Property Animation)
© 2019 欢亮 | 121k | 1:50
由 Hexo 强力驱动 v3.9.0
|
主题 – NexT.Muse v7.3.0