22、动画详解

一、基本动画

1.1、帧动画

1.首先需要在res目录下创建drawable目录,再新建xml文件并指定播放形式和资源引用。
wps2068.tmp3da7a247-e5d2-4d0e-84eb-8c0a533845a1
2.在布局中创建ImageView,然后写上如下代码
wps20A7.tmp17567c05-90ed-4df0-8a13-e3311b442101

1.2、补间动画

1.渐变动画
wps20B8.tmp08744a44-d615-4f5b-b85d-e7e0b70c6590

image

2.缩放动画

wps20C8.tmp1392d039-04c5-4251-887b-e20ebb3a12d9

image


3.位移动画

wps20C9.tmp29604eae-8fbb-459e-a6b9-7a7199d583d6

image

4.旋转动画
wps20CA.tmp1c826549-62d6-49c0-91b2-a00639744ca0

image

5.动画集
  动画集是将上述动画进行合并到一起,具体请参看代码:
wps20DB.tmp5377bdc4-db1d-4953-b456-887555fb0c66

5.动画的监听和回调

对于动画事件,Android也提供了对应的监听回调,要添加相应的监听方法,代码如下所示:

animation.setAnimationListener(new AnimationListener() {
    /**动画开始时回调*/
    @Override
    public void onAnimationStart(Animation animation) {
        
    }
    /**动画重播时回调*/
    @Override
    public void onAnimationRepeat(Animation animation) {
        
    }
    /**动画结束时回调*/
    @Override
    public void onAnimationEnd(Animation animation) {
        
    }
});

二、属性动画

由于基本的动画存在局限性,它只改变显示,并不能响应事件,所以在Android 3.0之后,加入了属性动画。

1.1、ObjectAnimation

创建ObjectAnimation只需要通过静态工厂类直接返回一个ObjectAnimation对象。参数必须包括一个对象和对象的属性名字,

属性必须具备get和set方法。内部会通过反射机制来调用set方法修改对象的属性值。

同样,我们也可以通过setInterpolator设置相应插值器。

ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "translationX", 300);
animator.setDuration(1000);
animator.start();

第一个参数是需要操纵的View,第二个参数是要操纵的属性,第三个参数则是一个可变数组参数,需要传递进去该属性变化的取值过程。

在使用ObjectAnimation时,要操纵的属性必须具备get和set方法,不然则会无效。下面是常见的属性值:

image

如果一个属性没有get和set方法,我们则有两种方案来解决该问题:

  • 自定义一个属性类或包装类来间接地给这个属性增加get和set方法。
  • 通过ValueAnimation来实现。
public class WrapperView {
    private View mTarget;
    
    public WrapperView(View target){
        mTarget = target;
    }
    
    public int getWidth(){
        return mTarget.getLayoutParams().width;
    }
    
    public void setWidth(int width){
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }
}

通过以上代码就给属性包装一层,并给它提供get、set方法。使用时只需要操纵包装类就可以间接调用get、set方法了。

WrapperView wrapper = new WrapperView(mButton);
ObjectAnimator.ofInt(wrapper, "width", 500).setDuration(5000).start();

1.2、ObjectAnimator的高级用法

ObjectAnimator内部的工作机制是通过寻找特定属性的get和set方法,然后通过方法不断地对值进行改变,从而实现动画效果的。因此我们就需要在MyAnimView中定义一个color属性,
并提供它的get和set方法。这里我们可以将color属性设置为字符串类型,使用#RRGGBB这种格式来表示颜色值,代码如下所示:

wpsF262.tmpa5dcb0c7-bb85-46ad-a572-678a16517499
在使用ObjectAnimator之前我们还要完成一个非常重要的工作,就是编写一个用于告知系统如何进行颜色过度的TypeEvaluator。创建ColorEvaluator并实现TypeEvaluator接口,代码如下所示:

wpsF273.tmpd30720f2-563d-4695-b8f7-c8f3e25b26f2
复杂工作完成后,我们想实现从蓝色到红色的动画过渡,历时5秒,就可以这样写:

wpsF283.tmp6ccbe277-4704-4480-8e0b-99c8c890e695
接下来我们需要将上面一段代码移到MyAnimView类当中,让它和刚才的Point移动动画可以结合到一起播放
wpsF284.tmp4e0f7bd1-4010-4231-a09d-d31920515217

1.3、PropertyValuesHolder

类似基本动画中的AnimationSet,在属性动画中,如果针对同一个对象的多个属性,要同时作用多种动画,可以使用PropertyValuesHolder来实现。

PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX", 300f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0, 1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0, 1f);
ObjectAnimator.ofPropertyValuesHolder(mButton, pvh1, pvh2, pvh3).setDuration(1000).start();

1.4、ValueAnimator

ValueAnimator在属性动画中是非常重要的,它是整个属性动画的核心所在,ObjectAnimator继承自ValueAnimator。

ValueAnimator本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画实现过程。

通常情况下,在ValueAnimator的AnimatorUpdateListener中监听数值的变换,从而完成动画的变换。

ValueAnimator animator = ValueAnimator.ofFloat(0,100);
animator.setTarget(mButton);
animator.setDuration(1000).start();
animator.addUpdateListener(new AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float value = (Float) animation.getAnimatedValue();
        //TODO use the value
    }
});

1.5、动画事件的监听

一个完整的动画具有Start、Repeat、End、Cancel四个过程,通过Android提供了接口,可以方便地监听到这四个事件:

ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "alpha", 0.5f);
animator.addListener(new AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
        
    }  
    @Override
    public void onAnimationRepeat(Animator animation) {
        
    }
    @Override
    public void onAnimationEnd(Animator animation) {
        
    }
    @Override
    public void onAnimationCancel(Animator animation) {
        
    }
});
animator.start();

当然,大部分时候我们只关心onAnimationEnd事件,所以Android也提供了AnimatorListenerAdapter来让我们选择必要的事件进行监听

animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        
    }
});

1.6、AnimatorSet

对于一个属性同时作用多个属性的动画效果,除了PropertyValuesHolder外,还可以使用AnimatorSet更精准的控制顺序。

ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "translationX", 300f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0f, 1f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0f, 1f);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(animator1, animator2, animator3);

在属性动画中AnimatorSet正是通过playTogether()、playSwquentinally()、animSet.play.with()、befor()、after、这些方法来控制

多个动画的协同工作并对动画播放顺序进行精准控制。

1.7、XML属性动画

属性动画同视图动画一样,也可以定义在XML中,代码如下所示:

<?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>

在程序中使用XML定义的属性动画也非常的简单:

public void scaleX(View view){
    Animator anim = AnimatorInflater.loadAnimator(this, R.anim.objectscalex);
    anim.setTarget(view);
    anim.start();
}

1.8、View的animate方法

在Android4.1.2(API 16)时,Google给View增加了animate方法来直接驱动属性动画,它是属性动画一种简写形式:

view.animate().alpha(0).y(300).setDuration(3000)
.withStartAction(new Runnable() {
    @Override
    public void run() {
    }
}).withEndAction(new Runnable() {
    @Override
    public void run() {
        runOnUiThread(new Runnable() {
            public void run() {
            }
        });
    }
}).start();

三、布局动画

所谓布局动画是指作用在ViewGroup上,给VewGroup增加View时添加动画过渡效果。

最简单的布局动画是在ViewGroup的XML中,使用以下代码来打开布局动画:

android:animateLayoutChanges="true"

设置该属性后,当ViewGroup添加View时,子View会呈现逐渐显示的过渡效果,这个效果是Android默认的,且无法使用自定义的动画进行替换。

如果想自定义子View的过渡效果,可以使用LayoutAnimationController类来完成相关操作。

LinearLayout ll = (LinearLayout) findViewById(R.id.ll_root);
// 设置过渡动画
ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1);
sa.setDuration(2000);
// 设置布局动画的显示属性
LayoutAnimationController lac = new LayoutAnimationController(sa, 0.5F);
lac.setOrder(LayoutAnimationController.ORDER_NORMAL);
// 为ViewGroup设置布局动画
ll.setLayoutAnimation(lac);

LayoutAnimationController的第一个参数是需要作用的动画,dirge参数是每个子View显示delay时间。

当delay时间不为0时,可以设置子View显示的顺序:

  • LayoutAnimationController.ORDER_NORMAL    -- 顺序
  • LayoutAnimationController.ORDER_RANDOM    -- 随机
  • LayoutAnimationController.ORDER_REVERSE   -- 反序

四、Interpolators(插值器)

插值器是动画中一个非常重要的概念,通过插值器(Interpolators),可以自定义动画变换速率,类似物理中的加速度。

其作用主要是控制目标变量的变化值进行对应的变化。

1.1、Interpolator用法

补间器,它的主要作用是可以控制动画的变化速率,比如去实现一种非线性运动的动画效果。补间动画就支持Interpolator,属性动画新增TimeInterpolator接口。
下面参考TimeInterpolator实现类

image

使用属性动画时,系统默认的Interpolator其实就是一个先加速后减速的Interpolator,对应的实现类就是AccelerateDecelerateInterpolar。
我们也可以修改这一默认属性,将它替换成任意一个系统内置好的Interpolator。首先是垂直掉落的效果
wpsF295.tmp4b52504f-1284-4b35-a940-c4bedc72dde8
OK,效果非常明显,说明我们已经成功替换掉了默认的Interpolator,AccelerateInterpolator确实是生效了。但是现在的动画效果看上去仍然是怪怪的,因为一个小球从很高的地方掉落到地面上
直接就静止了,这也是不符合物理规律的,小球撞击到地面之后应该要反弹起来,然后再次落下,接着再反弹起来,又再次落下,以此反复,最后静止。这个功能我们当然可以自己去写,只不
过比较复杂,所幸的是,Android系统中已经提供好了这样一种Interpolator,我们只需要简单地替换一下就可以完成上面的描述的效果,代码如下所示:

wpsF2A6.tmp04a205e4-2138-48b2-8d0f-39e20fc58828
系统内置的Interpolator非常多,有时间可以将其他几种Interpolator实现查看效果。

1.2、Interpolator介绍

1.TimeInterpolator

首先看下TimeInterpolator的接口定义:
wpsF2A7.tmpff2a1e99-12b3-4e00-af70-6f86f25f3917
接口非常简单,只有一个getInterpolation()方法,它接受一个input参数,该参数会随着动画的运行而不断有规律的变化,且变化范围是0到1。
input值是决定fraction的值,因为input的值是由系统经过计算后传入到getInterpolation()方法中,然后可以自己实现该方法中的算法,根据input的值计算出一个返回值就是fraction。
因此,最简单的情况就是input值和fraction值是相同的,这种情况由于input值是匀速增加的,因而fraction的值也是匀速增加的,所以动画的运动情况也是匀速的。
2.LinearInterpolator
系统中内置的LinearInterpolator就是一种匀速运动的Interpolator,那么我们来看一下它的源码是怎么实现的

wpsF2B7.tmp37563b38-3bf3-43a3-93b2-16e8299b1b39
这里我们只看getInterpolation()方法,这个方法没有任何逻辑,就是把参数中传递的input值直接返回了,因此fraction的值就是等于input的值的,这就是匀速运动的Interpolator的实现方式。
3.AccelerateDecelerateInterpolator
系统默认情况下使用的是AccelerateDecelerateInterpolator,那么我们看下它的源码:
wpsF2B8.tmpeec37e96-0e63-4b05-85a8-ac034dc370f6
此时getInputerpolator逻辑明显复杂了,不再是直接将参数的input返回,二手进行较为复杂的数学运算。

1.3、自定义Interpolator

通过上上述分析,我们已经了解LinearInterpolator和AccelerateDecelerateInterpolator的实现机制,现在我们自定义Interpolator,由于属性动画默认的Interpolator是先减速后加速的过程,

我们可以修改它为先加速后减速的过程
wpsF2C9.tmp0f0e1927-5f05-4819-85ad-4d1cd6f6fcca
之后我们将自定义的DecelerateAccelerateInterpolator在代码中进行替换:
wpsF2E9.tmp80432423-9e0f-4799-8a96-ac3cb35b16d6

 

五、自定义动画

创建自定义动画只需要继承Animation类,并实现applyTransformation方法即可,

通常情况下还需要覆盖父类的initalize方法以完成一些初始化操作。

public class MyAnimation extends Animation {    
    /**完成一些初始化操作*/
    @Override
    public void initialize(int width, int height, int parentWidth,
            int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
    }
    
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        super.applyTransformation(interpolatedTime, t);
    }
}

applyTransformation类的interpolatedTime是插值器因子,由动画当前完成的百分比和当前时间所对应的插值所计算得来的,

取值范围是0 - 1.0。

第二个参数Transformation是矩阵的封装类,一般使用这个类获得当前的矩阵对象

Matrix matrix = t.getMatrix();

通过改变matrix对象,可以将动画效果实现出来,基本可以实现任意效果。

public class CustomAnimation extends Animation {   
    private int mCenterWidth;
    private int mCenterHeight;
    private float mRotateY = 0.0f;
    private Camera mCamera = new Camera();
    /**完成一些初始化操作*/
    @Override
    public void initialize(int width, int height, int parentWidth, int parentHeight) {
        super.initialize(width, height, parentWidth, parentHeight);
        // 设置默认时长
        setDuration(2000);
        // 动画结束后保留状态
        setFillAfter(true);
        // 设置默认插值器
        setInterpolator(new BounceInterpolator());
        mCenterWidth = width / 2;
        mCenterHeight = height / 2;
    }
    // 暴漏接口 -设置旋转角度
    public void setRotateY(float rotateY){
        mRotateY = rotateY;
    }   
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        Matrix matrix = t.getMatrix();
        mCamera.save();
        // 使用Camera设置旋转的角度
        mCamera.rotateY(mRotateY * interpolatedTime);
        // 将旋转变换作用到matrix上
        mCamera.getMatrix(matrix);
        mCamera.restore();
        // 通过pre方法设置矩阵作用前的偏移量来改变旋转中心
        matrix.preTranslate(mCenterWidth, mCenterHeight);
        matrix.preTranslate(-mCenterWidth, -mCenterHeight);
    }
}

调用时需要通过对外暴漏的方法来进行设置:

CustomAnimation customAnimation = new CustomAnimation();
customAnimation.setRotateY(30);
view.startAnimation(customAnimation);

六、SVG矢量动画(5.X)

七、Android动画特效

1.1、灵动菜单

参看附件中的灵动菜单demo。

1.2、计时器动画

public class MainActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tView = (TextView) findViewById(R.id.tv_view);
        tView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                // 计时器动画
                TimerAnim(tView);
            }
        });
    }
    
    public void TimerAnim(final View view){
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);
        valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                TextView tView = (TextView) view;
                tView.setText("$" + animation.getAnimatedValue());
            }
        });
        valueAnimator.setDuration(3000);
        valueAnimator.start();
    }
}

当用户点击文本后,数字会不断的增加,以此完成过渡效果。

1.3、下拉展开动画

想实现点击一个View的时候,显示下面因此的一个View,实现该功能只需要将View的visibility属性由gone设置为visible即可,

但是这个过程是瞬间完成的,我们期望的是隐藏的View的高度不断发生变化,则可以通过ValueAnimator来模拟。

public class MainActivity extends Activity {
    private LinearLayout mHiddenView;
    private float mDensity;
    private int mHiddenViewMeasuredHeight;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.drop);
        mHiddenView = (LinearLayout) findViewById(R.id.hidden_view);
        // 获取像素密度
        mDensity = getResources().getDisplayMetrics().density;
        // 获取布局的高度
        mHiddenViewMeasuredHeight = (int) (mDensity * 200 + 0.5);
    }
    public void llClick(View view) {
        if (mHiddenView.getVisibility() == View.GONE) {
            animateOpen(mHiddenView);
        } else {
            animateClose(mHiddenView);
        }
    }
    private void animateOpen(final View view) {
        view.setVisibility(View.VISIBLE);
        ValueAnimator animator = createDropAnimator(view, 0, mHiddenViewMeasuredHeight);
        animator.start();
    }
    private void animateClose(final View view) {
        int origHeight = view.getHeight();
        ValueAnimator animator = createDropAnimator(view, origHeight, 0);
        animator.addListener(new AnimatorListenerAdapter() {
            public void onAnimationEnd(Animator animation) {
                view.setVisibility(View.GONE);
            }
        });
        animator.start();
    }
    private ValueAnimator createDropAnimator(final View view, int start, int end) {
        ValueAnimator animator = ValueAnimator.ofInt(start, end);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                int value = (Integer) valueAnimator.getAnimatedValue();
                ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
                layoutParams.height = value;
                view.setLayoutParams(layoutParams);
            }
        });
        return animator;
    }
}

布局文件参看附件中的展开动画.rar

目前已经封装为一个非常方便的控件,参看附件中的任务模块

153e87b5-918d-4a71-8857-91d3fc9109cc

 

 

源码下载:

链接:http://pan.baidu.com/s/1jHMgxUm 密码:7023

转载于:https://www.cnblogs.com/pengjingya/p/5509371.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值