您的位置:首页 > 移动开发 > Android开发

第七章Andorid动画深入分析(Android开发艺术探索)

2017-03-16 15:38 309 查看
Android 平台提供了一套完整的动画框架

在Android3.0之前有两种动画,一种方式是补间动画 Tween Animation、另一种叫逐帧动画 Frame Animation(也称Drawable Animation );

Android3.0以后增加了属性动画 Property Animation

Tween Animation、Frame Animation只能用于View,被归类为View

Animation。

所以Andorid的动画分为2种:

View动画(View Animation)、属性动画(Property Animation)

7.1、View动画

7.1.1、View动画的种类

View动画的四种变换效果对应着Animation的四个子类:

TranslateAnimation(平移<transalte>)
ScaleAnimatino(缩放<scale>)
RotateAnimation(旋转<rotate>)
AlphaAnimation(透明<alpha>)


要使用view的动画,首先要在res/anim下面创建动画xml

基本属性如下:



标签表示动画集合,对应AnimitionSet类,view可以是单个动画,也可以是多个动画的组合

它的两个属性:

android:interpolater:默认是@android:anim/accelerate_interpolator即加速减速插值器

android:shareInterpolater:表示集合中的动画是否和集合用一样的插值器,默认为true

1、TranslateAnimation(平移):

常用属性如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true"
android:zAdjustment="normal" >

<translate
//持续的时间
android:duration="100"
//x的起始值
android:fromXDelta="0"
//Y的起始值
android:fromYDelta="0"
//插值器
android:interpolator="@android:anim/linear_interpolator"
//x的结束值
android:toXDelta="100"
//Y的结束值
android:toYDelta="100" />

</set>


2、ScaleAnimatino(缩放)

常用属性如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true" >

<scale
//水平方向的缩放起始值
android:fromXScale="0.5"
//竖直方向的缩放起始值
android:fromYScale="0.5"
//水平方向的缩放结束值
android:toXScale="1.2"
//竖直方向的缩放结束值
android:toYScale="1.2"
//缩放的轴点的X坐标
android:pivotX="100"
//缩放的轴点的Y坐标
android:pivotY="100"/>

</set>


注意:轴点默认在View的中心,这个时候横向缩放的话,左右都会缩放、纵向的话,上下都会缩放

这个时候可以设置轴点的位置,如果在最左边那么只会缩放右边,如果在最右边那么只会缩放左边

3、RotateAnimation(旋转)

常用属性如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true" >

<rotate
//旋转开始的角度
android:fromDegrees="0"
//旋转结束的角度
android:toDegrees="90"
/旋转的轴点的X坐标
android:pivotX="100"
//旋转的轴点的Y坐标
android:pivotY="100" />
</set>


注意:轴点默认在View的中心,即view的旋转中心,围绕着view中心点和view左上角,效果明显是不一样的

4、AlphaAnimation(透明)

常用属性如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true" >

<alpha
//透明度的起始值
android:fromAlpha="0.1"
//透明度的结束值
android:toAlpha="1"
/>

</set>


每个动画都有的公共常用属性:

动画的持续时间:
andorid:duration = "100"
动画结束以后是否停留在结束位置
andorid:fillAtler = "true",true表示停留,false表示不停留


拓展:

fillBefore是指动画结束时画面停留在此动画的第一帧;
fillAfter是指动画结束是画面停留在此动画的最后一帧。
Java代码设置如下:
/*****动画结束时,停留在最后一帧*********
setFillAfter(true);
setFillBefore(false);

/*****动画结束时,停留在第一帧*********
setFillAfter(false);
setFillBefore(true);

xml设置如下:
/******动画结束时,停留在最后一帧**********
<set Android:fillAfter="true" android:fillBefore="false">

/******动画结束时,停留在第一帧**********
<set android:fillAfter="false" android:fillBefore="true">

PS:xml设置在scale标签里面设置是无效的,注意是set标签


下面来一个例子:

res/drawable/translate

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true">

<translate
android:duration="100"
android:fromXDelta="0"
android:fromYDelta="0"
android:interpolator="@android:anim/linear_interpolator"
android:toXDelta="100"
android:toYDelta="100" />

</set>


res/drawable/scale

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true">

<scale
android:fromXScale="0.5"
android:fromYScale="0.5"
android:pivotX="100"
android:pivotY="100"
android:toXScale="1.2"
android:toYScale="1.2" />

</set>


res/drawable/rotate

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true" >

<rotate
android:fromDegrees="0"
android:toDegrees="90"
android:pivotX="100"
android:pivotY="100" />
</set>


res/drawable/alpha

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true">

<alpha
android:fromAlpha="0.1"
android:toAlpha="1" />

</set>


布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.zx.eventbus.AnimationActivity">

<Button
android:id="@+id/btn_tanslate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="让我们移动" />

<Button
android:id="@+id/btn_scale"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="让我们缩放" />

<Button
android:id="@+id/btn_rotate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="让我们旋转" />

<Button
android:id="@+id/btn_alpha"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="让我们透明" />
</LinearLayout>


Activity:

package com.zx.eventbus;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;

import butterknife.Bind;
import butterknife.ButterKnife;
import butterknife.OnClick;

public class AnimationActivity extends AppCompatActivity {

@Bind(R.id.btn_tanslate)
Button btnTanslate;
@Bind(R.id.btn_scale)
Button btnScale;
@Bind(R.id.btn_rotate)
Button btnRotate;
@Bind(R.id.btn_alpha)
Button btnAlpha;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_animation);
ButterKnife.bind(this);
}

private void showAnimation(int anim, Button button) {
Animation animation = AnimationUtils.loadAnimation(this, anim);
button.startAnimation(animation);
}

@OnClick({R.id.btn_tanslate, R.id.btn_scale, R.id.btn_rotate, R.id.btn_alpha})
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn_tanslate:
showAnimation(R.anim.translate, btnTanslate);
break;
case R.id.btn_scale:
showAnimation(R.anim.scale, btnScale);
break;
case R.id.btn_rotate:
showAnimation(R.anim.rotate, btnRotate);
break;
case R.id.btn_alpha:
showAnimation(R.anim.alpha, btnAlpha);
break;
}
}
}


效果图如下:



除了xml中定义的动画外,还可以通过代码来应用动画

如:

AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
alphaAnimation.setDuration(300);
button.setAnimation(alphaAnimation);


其他动画同理

拓展:

通过Animation的setAnimationListener方法可以给view动画添加监听
AlphaAnimation alphaAnimation = new AlphaAnimation(0,1);
alphaAnimation.setDuration(300);
alphaAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});


7.1.3、自定义View动画

实际开发中很少自定义view动画,只需要知道是矩阵变换的过程就好

7.1.4、帧动画

帧动画是顺序播放一组预定好的图片

Andorid系统为我们提供了一个类:AnimationDrawable

使用步骤:

1、定义一个xml

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">

<item android:drawable="@drawable/img1" android:duration="500"/>
<item android:drawable="@drawable/img2" android:duration="500"/>
<item android:drawable="@drawable/img3" android:duration="500"/>

</animation-list>


2、让其作为view的背景即可

button.setBackgroundResource(R.drawable.animation);
AnimationDrawable animationDrawable = (AnimationDrawable) button.getBackground();
animationDrawable.start();


帧动画使用比较简单,但是容易引起oom,所以使用帧动画的时候避免使用尺寸较大的图片

7.2、View动画的特殊使用场景

7.2.1、LayoutAnimation

LayoutAnimation作用于ViewGroup,为ViewGroup指定一个动画,常常被用在listview,recycleview上面,给ViewGroup指定一个动画,那么它的子元素就也有了这个动画

为了给viewGroup子元素加上出场效果,遵循如下几个步骤:

1、定义LayoutAnimation

<layoutAnimation
xmlns:android="http://schemas.android.com/apk/res/android"
//开始动画的时间延迟
android:delay="0.5"
//子元素动画的顺序,normal(顺序),reverse(逆向展示),random(随机展示)
android:animationOrder="reverse"
//为子元素指定具体的入场动画
android:animation="@anim/anim_item"/>


2、为子元素指定具体的入场

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="300"
android:interpolator="@android:anim/accelerate_interpolator"
android:shareInterpolator="true" >

<alpha
android:fromAlpha="0.0"
android:toAlpha="1.0" />

<translate
android:fromYDelta="500"
android:toYDelta="0" />

</set>


3、为ViewGroup指定android:layoutAnimation

<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layoutAnimation="@anim/anim_layout"
android:background="#fff4f7f9"
android:cacheColorHint="#00000000"
android:divider="#dddbdb"
android:dividerHeight="1.0px"
android:listSelector="@android:color/transparent" />


除了在XML中定义,我们还可以在代码中定义

Animation animation = AnimationUtils.loadAnimation(this, R.anim.anim_item);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listview.setLayoutAnimation(controller);


7.2.2、Activity的切换效果

通常我们使用:

Intent in = new Intent(this,DemoActivity_2.class);
startActivity(in);
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);


在finish的时候:

@Override
public void finish() {
super.finish();
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
}


注意:overridePendingTransition必须在startActivity或者finish的后面

也可以在style.xml中设置

<item name="android:windowAnimationStyle">@style/SmoothWindowAnimation</item>

<style name="SmoothWindowAnimation">
<!-- 打开Activity时的入场动画 -->
<item name="android:activityOpenEnterAnimation">@anim/slide_right_enter</item>
<!-- 打开Activity时的退场动画 -->
<item name="android:activityOpenExitAnimation">@anim/slide_left_exit</item>
<!-- 关闭Activity时的入场动画 -->
<item name="android:activityCloseEnterAnimation">@anim/slide_left_enter</item>
<!-- 关闭Activity时的退场动画 -->
<item name="android:activityCloseExitAnimation">@anim/slide_right_exit</item>
</style>


7.3、属性动画

7.3.1、使用属性动画

属性动画是3.0以后引入的新动画,

常用的动画类有:ValueAnimator、ObjectAnimator、AnimatorSet

Property Animation可以定义在xml文件中

这些xml文件定义的文件路径如下: res/animator/filename.xm

基本用法如下:

<set
//together表示集合中的子动画同时播放,sequentially表示子动画顺序播放
android:ordering=["together" | "sequentially"]]]>

<objectAnimator
//子动画作用对象的名称
android:propertyName="string"
//动画的持续时长
android:duration="int"
//属性的起始值
android:valueFrom="float | int | color"
//属性的结束值
android:valueTo="float | int | color"
//动画播放前的延迟时间
android:startOffset="int"
//动画的重复次数,默认是0,-1表示无限循环
android:repeatCount="int"
//动画的重复模式,repeat连续从头到尾重复,
reverse逆向重复,第一次,从头到尾,第二次从尾到头,第三次从头到尾,第四次从尾到头(就像画椭圆一样)
android:repeatMode=["repeat" | "reverse"]
//表示android:propertyName所指定的类型
android:valueType=["intType" | "floatType"]/>

<animator
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode=["repeat" | "reverse"]
android:valueType=["intType" | "floatType"]/>

<set]]>
...
</set>
</set>


文件需要有根元素,可以使用, , or . 可以作为一个集合,而且集合中还可以存在元素。

同样也可以通过代码进行设置:

ObjectAnimator的使用:

ObjectAnimator.ofFloat();


ValueAnimator 的使用

ValueAnimator anim = ObjectAnimator.ofInt();
anim.start();


AnimatorSet 的使用

AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat();
ObjectAnimator.ofFloat();
ObjectAnimator.ofFloat();
);
set.setDuration(1000).start();


ValueAnimator是Property Animation系统的核心类,它包含了大部分api

objectAnimator继承ValueAnimator,是动画的拓展类

属性动画基本使用如下:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:ordering="together">

<objectAnimator
android:duration="300"
android:propertyName="x"
android:valueTo="200"
android:valueType="intType" />

<objectAnimator
android:duration="300"
android:propertyName="Y"
android:valueTo="200"
android:valueType="intType" />

</set>


使用如下代码调用:

AnimatorSet set = AnimationUtils.loadAnimation(this,R.animator.anim_item)
set.setTarget(button);
set.start();


在实际开发中我们建议用代码的实现属性动画,因为代码实现比较简单,更加重要的是

很多时候起始值是无法确定的,需要动态的创建动画,更加灵活

7.3.2、理解插值器和估值器

这里我们将了解2个知识点:Interpolate(插值器)和TypeEvaluator(估值器)

常用的插值器如下:



如果系统提供的插值器无法满足我们的需求,我们还可以自定义插值器:

步骤很简单:

步骤一:继承TimeInterpolator

步骤二:重写getInterpolation(float input)方法

代码如下:

public class CustomInterpolator implements TimeInterpolator {

@Override
public float getInterpolation(float input) {
// 编写相关的逻辑计算
//input *= 0.8f;
return input * input;
}
}


7.3.3、属性动画的监听器

Property Animation提供了

Animator.AnimatorListener和Animator.AnimatorUpdateListener两个监听器用于动画在播放过程中的重要动画事件。

下面是两个监听器接口和方法的一些介绍和说明:

Animator.AnimatorListen

onAnimationStart() —— 动画开始时调用;

onAnimationEnd() —— 动画结束时调用;

onAnimationRepeat() —— 动画循环播放时调用;

onAnimationCancel() —— 动画被取消时调用。不管终止的方式如何,被取消的动画仍然会调onAnimationEnd();

Animator.AnimatorUpdateListener:

onAnimationUpdate() —— 动画每播放一帧时调用。在动画过程中,可侦听此事件来获取并使用 ValueAnimator 计算出来的属性值。

利用传入事件的 ValueAnimator 对象,调用其 getAnimatedValue() 方法即可获取当前的属性值。如果使用 ValueAnimator来实现动画的话 ,则必需实现此侦听器。

7.3.4、对任意属性做动画

这里先提出一个问题:

给button加个动画,宽度从当前宽度增加200px,也许你会说用view动画搞定,但是当你做的时候你会发现view动画不支持对宽度进行动画,不过可以使用缩放动画试试:

效果如下:



这样的效果当然不是我们想要的效果,文本都被拉伸了。

下面我们使用属性动画来试试:

private void performAnimate() {
ViewWrapper viewWrapper = new ViewWrapper(btnTanslate);
ObjectAnimator.ofInt(viewWrapper, "width", 500).setDuration(1000).start();
}
private static class ViewWrapper {
private View mTarget;
public ViewWrapper(View target){
this.mTarget = target;
}

public int getWidth(){
return mTarget.getLayoutParams().width;
}
public void setWidth(int width){
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}


很显然效果达到了

拓展:

使用ValueAnimator实现动画的步骤及实践
那一般使用ValueAnimator实现动画分为以下七个步骤:
1. 调用ValueAnimation类中的ofInt(int...values)、ofFloat(String propertyName,float...values)等静态方法实例化ValueAnimator对象,并设置目标属性的属性名、初始值或结束值等值;
2.调用addUpdateListener(AnimatorUpdateListener mListener)方法为ValueAnimator对象设置属性变化的监听器;
3.创建自定义的Interpolator,调用setInterpolator(TimeInterpolator value)为ValueAniamtor设置自定义的Interpolator;(可选,不设置默认为缺省值)
4.创建自定义的TypeEvaluator,调用setEvaluator(TypeEvaluator value)为ValueAnimator设置自定义的TypeEvaluator;(可选,不设置默认为缺省值)
5.在AnimatorUpdateListener 中的实现方法为目标对象的属性设置计算好的属性值。
6.设置动画的持续时间、是否重复及重复次数等属性;
7.为ValueAnimator设置目标对象并开始执行动画。


代码如下:

/**
* 使用ValueAnimator改变Imageview的margin的值
*/
public void marginValueAnimator(){
//1.调用ofInt(int...values)方法创建ValueAnimator对象
ValueAnimator mAnimator = ValueAnimator.ofInt(0,screenWidth - mImageViewTest.getWidth());
//2.为目标对象的属性变化设置监听器
mAnimator.addUpdateListener(new AnimatorUpdateListener() {

@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 3.为目标对象的属性设置计算好的属性值
int animatorValue = (int)animation.getAnimatedValue();
MarginLayoutParams marginLayoutParams = (MarginLayoutParams) mImageViewTest.getLayoutParams();
marginLayoutParams.leftMargin = animatorValue;
mImageViewTest.setLayoutParams(marginLayoutParams);
}
});
//4.设置动画的持续时间、是否重复及重复次数等属性
mAnimator.setDuration(2000);
mAnimator.setRepeatCount(3);
mAnimator.setRepeatMode(ValueAnimator.REVERSE);
//5.为ValueAnimator设置目标对象并开始执行动画
mAnimator.setTarget(mImageViewTest);
mAnimator.start();
}


关于属性动画更多的介绍请参照

Andorid属性动画完全解析上、中、下

http://blog.csdn.net/guolin_blog/article/details/43536355

http://blog.csdn.net/guolin_blog/article/details/43816093

http://blog.csdn.net/guolin_blog/article/details/44171115

Andorid属性动画完全解析上、下

http://blog.csdn.net/lmj623565791/article/details/38067475

http://blog.csdn.net/lmj623565791/article/details/38092093

其他

http://blog.csdn.net/yegongheng/article/details/38435553

http://blog.csdn.net/xyz_lmn/article/details/38667899

拓展:

看了这么多例子,我不知道那些系统的propertyName一共有哪些,我看了下ObjectAnimator源码才发现,下面贴出来:

static {
PROXY_PROPERTIES.put("alpha", PreHoneycombCompat.ALPHA);
PROXY_PROPERTIES.put("pivotX", PreHoneycombCompat.PIVOT_X);
PROXY_PROPERTIES.put("pivotY", PreHoneycombCompat.PIVOT_Y);
PROXY_PROPERTIES.put("translationX", PreHoneycombCompat.TRANSLATION_X);
PROXY_PROPERTIES.put("translationY", PreHoneycombCompat.TRANSLATION_Y);
PROXY_PROPERTIES.put("rotation", PreHoneycombCompat.ROTATION);
PROXY_PROPERTIES.put("rotationX", PreHoneycombCompat.ROTATION_X);
PROXY_PROPERTIES.put("rotationY", PreHoneycombCompat.ROTATION_Y);
PROXY_PROPERTIES.put("scaleX", PreHoneycombCompat.SCALE_X);
PROXY_PROPERTIES.put("scaleY", PreHoneycombCompat.SCALE_Y);
PROXY_PROPERTIES.put("scrollX", PreHoneycombCompat.SCROLL_X);
PROXY_PROPERTIES.put("scrollY", PreHoneycombCompat.SCROLL_Y);
PROXY_PROPERTIES.put("x", PreHoneycombCompat.X);
PROXY_PROPERTIES.put("y", PreHoneycombCompat.Y);
}


有了这些,你传入对应的propertyName,就可以实现各种各样的功能啦

package com.nineoldandroids.animation;
里面找到的,

package android.animation;
包里面没有。

7.3.5、属性动画的工作原理

属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你提供的开始值和最终值,多次调用set方法,每次传的值都不一样。

下面我们看源码分析:

1、我们通常使用都是ObjectAnimator.ofInt().start();

那我们就从start()方法看看,

@Override
public void start() {
// See if any of the current active/pending animators need to be canceled
AnimationHandler handler = sAnimationHandler.get();
if (handler != null) {
int numAnims = handler.mAnimations.size();
//判断当前动画是否重复
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mAnimations.get(i) instanceof ObjectAnimator) {
ObjectAnimator anim = (ObjectAnimator) handler.mAnimations.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}
numAnims = handler.mPendingAnimations.size();
//判断等待动画是否重复
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mPendingAnimations.get(i) instanceof ObjectAnimator) {
ObjectAnimator anim = (ObjectAnimator) handler.mPendingAnimations.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}
numAnims = handler.mDelayedAnims.size();
//判断延迟动画是否重复
for (int i = numAnims - 1; i >= 0; i--) {
if (handler.mDelayedAnims.get(i) instanceof ObjectAnimator) {
ObjectAnimator anim = (ObjectAnimator) handler.mDelayedAnims.get(i);
if (anim.mAutoCancel && hasSameTargetAndProperties(anim)) {
anim.cancel();
}
}
}
}
if (DBG) {
Log.d(LOG_TAG, "Anim target, duration: " + getTarget() + ", " + getDuration());
for (int i = 0; i < mValues.length; ++i) {
PropertyValuesHolder pvh = mValues[i];
Log.d(LOG_TAG, "   Values[" + i + "]: " +
pvh.getPropertyName() + ", " + pvh.mKeyframes.getValue(0) + ", " +
pvh.mKeyframes.getValue(1));
}
}
super.start();
}


代码看似很多,其实做的事情很简单

首先判断

如果当前动画(mAnimations)、等待的动画(mPendingAnimations)、延迟动画(mDelayedAnims)它们之中只要有和当前动画相同的动画(通过instanceof 进行比较),那么就调用 anim.cancel();来取消当前的动画;

接下来是一段LOG日志

最后如果都不重复的话就直接调用父类的启动动画方法了(super.start());

因为ObjectAnimator继承ValueAnimator那么我们再来看看ValueAnimator的start()方法

private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
mReversing = playBackwards;
mPlayingBackwards = playBackwards;
if (playBackwards && mSeekFraction != -1) {
if (mSeekFraction == 0 && mCurrentIteration == 0) {
// special case: reversing from seek-to-0 should act as if not seeked at all
mSeekFraction = 0;
} else if (mRepeatCount == INFINITE) {
mSeekFraction = 1 - (mSeekFraction % 1);
} else {
mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
}
mCurrentIteration = (int) mSeekFraction;
mSeekFraction = mSeekFraction % 1;
}
if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
(mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
// if we were seeked to some other iteration in a reversing animator,
// figure out the correct direction to start playing based on the iteration
if (playBackwards) {
mPlayingBackwards = (mCurrentIteration % 2) == 0;
} else {
mPlayingBackwards = (mCurrentIteration % 2) != 0;
}
}
int prevPlayingState = mPlayingState;
mPlayingState = STOPPED;
mStarted = true;
mStartedDelay = false;
mPaused = false;
updateScaledDuration(); // in case the scale factor has changed since creation time
AnimationHandler animationHandler = getOrCreateAnimationHandler();
animationHandler.mPendingAnimations.add(this);
if (mStartDelay == 0) {
// This sets the initial value of the animation, prior to actually starting it running
if (prevPlayingState != SEEKED) {
setCurrentPlayTime(0);
}
mPlayingState = STOPPED;
mRunning = true;
notifyStartListeners();
}
animationHandler.start();
}


通过上述代码,我们可以看出,属性动画需要运行在looper的线程中,最终会调用 animationHandler.start();

然后调用到JNI层,最后回调回来。

这边有一个核心方法:doAnimationFrame

其中animationFrame内部调用animateValue

在初始化的时候如果属性没有提供初始值,则get方法将被会调用,我们看PropertyValuesHolder的setValue方法

private void setupValue(Object target, Keyframe kf) {
if (mProperty != null) {
Object value = convertBack(mProperty.get(target));
kf.setValue(value);
}
try {
if (mGetter == null) {
Class targetClass = target.getClass();
setupGetter(targetClass);
if (mGetter == null) {
// Already logged the error - just return to avoid NPE
return;
}
}
Object value = convertBack(mGetter.invoke(target));
kf.setValue(value);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}


可以看到get方法是通过反射调用的,当动画到下一帧的时候,调用setAnimatedValue将新的属性设置给对象,调用set方法。

set方法也是通过反射调用的

代码如下:

void setAnimatedValue(Object target) {
if (mProperty != null) {
mProperty.set(target, getAnimatedValue());
}
if (mSetter != null) {
try {
mTmpValueArray[0] = getAnimatedValue();
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: