阅读 43

Android 动画学习笔记

1、属性动画

使用 ObjectAnimator 的时候,有一点非常重要,那就是操纵的属性要具有 get,set 方法,不然ObjectAnimator 就无法起效。下面这些事一些常用的,可以直接使用属性动画的属性值。translationX ,translationY,scaleX,ScaleY,pivotX,pivotY,x,y,alpha。

但是,有些属性是没有 get set 方法的,那么难道我们就不能用了吗?不是的。

private static class WrapperView{
    private View mTarget;

    public WrapperView(View target){
        mTarget = target;
    }

    public getWidth(){
        return mTarget.getLayoutParams().width;
    }

    public void setWidth(int width){
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayput();
    }
}

// 通过以上代码给属性包了一层,并给它提供了 get set 的方法。使用如下:

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

类似的,如果想实现视图动画中的AnimationSet,在属性动画中可以使用 PropertyValuesHolder 来实现。

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

实例:

只展示 activity 中的代码。点击之后,自身变淡,四个方向上弹出四个图标。再点击一次,收回四个图标。

public class PropertyTest extends Activity implements View.OnClickListener {

    private int[] mRes = {R.id.imageView_a, R.id.imageView_b, R.id.imageView_c,
            R.id.imageView_d, R.id.imageView_e};
    private List<ImageView> mImageViews = new ArrayList<ImageView>();
    private boolean mFlag = true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.property);
        for (int i = 0; i < mRes.length; i++) {
            ImageView imageView = (ImageView) findViewById(mRes[i]);
            imageView.setOnClickListener(this);
            mImageViews.add(imageView);
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.imageView_a:
                if (mFlag) {
                    startAnim();
                } else {
                    closeAnim();
                }
                break;
            default:
                Toast.makeText(PropertyTest.this, "" + v.getId(),
                        Toast.LENGTH_SHORT).show();
                break;
        }
    }

    private void closeAnim() {
        ObjectAnimator animator0 = ObjectAnimator.ofFloat(mImageViews.get(0),
                "alpha", 0.5F, 1F);
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImageViews.get(1),
                "translationY", 200F, 0);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImageViews.get(2),
                "translationX", 200F, 0);
        ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImageViews.get(3),
                "translationY", -200F, 0);
        ObjectAnimator animator4 = ObjectAnimator.ofFloat(mImageViews.get(4),
                "translationX", -200F, 0);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(500);
        set.setInterpolator(new BounceInterpolator());
        set.playTogether(animator0, animator1, animator2, animator3, animator4);
        set.start();
        mFlag = true;
    }

    private void startAnim() {
        ObjectAnimator animator0 = ObjectAnimator.ofFloat(
                mImageViews.get(0),
                "alpha",
                1F,
                0.5F);
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(
                mImageViews.get(1),
                "translationY",
                200F);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(
                mImageViews.get(2),
                "translationX",
                200F);
        ObjectAnimator animator3 = ObjectAnimator.ofFloat(
                mImageViews.get(3),
                "translationY",
                -200F);
        ObjectAnimator animator4 = ObjectAnimator.ofFloat(
                mImageViews.get(4),
                "translationX",
                -200F);
        AnimatorSet set = new AnimatorSet();
        set.setDuration(500);
        set.setInterpolator(new BounceInterpolator());
        set.playTogether(
                animator0,
                animator1,
                animator2,
                animator3,
                animator4);
        set.start();
        mFlag = false;
    }
}
复制代码

View Code

 

2、视图动画

视图动画主要有四种,平移,旋转,缩放,透明度四种变化。视图动画要避免交互,但是其效率高,使用方便。下面看一个具体的实例:

xml 文件

<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:paddingLeft="@dimen/activity_horizontal_margin"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    tools:context=".MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Alpha"
        android:layout_margin="10dp"
        android:onClick="btnAlpha" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Rotate"
        android:layout_margin="10dp"
        android:onClick="btnRotate" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Rotate_self"
        android:layout_margin="10dp"
        android:onClick="btnRotateSelf" />
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Translate"
        android:layout_margin="10dp"
        android:onClick="btnTranslate" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Scale"
        android:layout_margin="10dp"
        android:onClick="btnScale" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Scale_Self"
        android:layout_margin="10dp"
        android:onClick="btnScaleSelf" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Anim Set"
        android:layout_margin="10dp"
        android:onClick="btnSet" />

</LinearLayout>
复制代码

View Code

设置了 5 个按钮,每个按钮都设置了点击的方法。

activity 中的代码:注意点击方法传入的是 View,这样就可以直接处理啦。

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void btnAlpha(View view) {
        AlphaAnimation aa = new AlphaAnimation(0, 1);
        aa.setDuration(1000);
        view.startAnimation(aa);
    }

    public void btnRotate(View view) {
        RotateAnimation ra = new RotateAnimation(0, 360, 100, 100);
        ra.setDuration(1000);
        view.startAnimation(ra);
    }

    public void btnRotateSelf(View view) {
        RotateAnimation ra = new RotateAnimation(0, 360,
                RotateAnimation.RELATIVE_TO_SELF, 0.5F,
                RotateAnimation.RELATIVE_TO_SELF, 0.5F);
        ra.setDuration(1000);
        view.startAnimation(ra);
    }

    public void btnTranslate(View view) {
        TranslateAnimation ta = new TranslateAnimation(0, 200, 0, 300);
        ta.setDuration(1000);
        view.startAnimation(ta);
    }

    public void btnScale(View view) {
        ScaleAnimation sa = new ScaleAnimation(0, 2, 0, 2);
        sa.setDuration(1000);
        view.startAnimation(sa);
    }

    public void btnScaleSelf(View view) {
        ScaleAnimation sa = new ScaleAnimation(0, 1, 0, 1,
                Animation.RELATIVE_TO_SELF, 0.5F,
                Animation.RELATIVE_TO_SELF, 0.5F);
        sa.setDuration(1000);
        view.startAnimation(sa);
    }

    public void btnSet(View view) {
        AnimationSet as = new AnimationSet(true);
        as.setDuration(1000);

        AlphaAnimation aa = new AlphaAnimation(0, 1);
        aa.setDuration(1000);
        as.addAnimation(aa);

        TranslateAnimation ta = new TranslateAnimation(0, 100, 0, 200);
        ta.setDuration(1000);
        as.addAnimation(ta);

        view.startAnimation(as);
    }

}
复制代码

View Code

 

3、ValueAnimator

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

一个完整的动画具有start, repeat, end, cancel 四个过程,通过Android 提供了接口,可以很方便的监听这四个事件,代码如下所示。

ObjectAnimator anim = ObjectAnimator.ofFloat(view, "alpha", 0.5f);
anim .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){}

});
anim.start();
复制代码

当然大部分时候,我们都只关心 onAnimationEnd 事件,所以 Android 也提供了一个 AnimatorListenerAdapter 来让我们选择必要的必要的事情进行监听,代码如下所示:

anim.addListener(new AnimatorListenerAdapter() {
    public void onAnimationEnd(Animator animation) {
        // TODO
    }
});
复制代码

实例:

点击之后,会弹出一个下拉 item,再点击一次就消失。主要就是通过 ValueAnimator  实现的。通过中间数值的变化,来设置其高度的变化,达到动画的效果。我们监听动画结束的时候,然后就设置让该 item 看不见。

package com.imooc.anim;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;

public class DropTest 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 * 40 + 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;
    }
}
复制代码

View Code

另一个实例,定时器,将数值的变化体现在界面上。

public class TimerTest extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.timer);
    }

    public void tvTimer(final View view) {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 100);
        valueAnimator.addUpdateListener(
                new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                ((TextView) view).setText("$ " +
                        (Integer) animation.getAnimatedValue());
            }
        });
        valueAnimator.setDuration(3000);
        valueAnimator.start();
    }
}
复制代码

4、AnimatorSet

该属性能够精确的控制的发生的顺序。其他方法还有:animSet.play().with(), playSequentially(), playTogether(), before(), after()。

ObjectAnimator pvh1 = ObjectAnimator.ofFloat(view, "translationX",300f);
ObjectAnimator pvh2 = ObjectAnimator.ofFloat(view, "scaleX",1f, 0, 1f);
ObjectAnimator pvh3 = ObjectAnimator.ofFloat(view, "scaleY",1f, 0, 1f);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(pvh1, pvh2, pvh3);
set.start();
复制代码

5、使用 XML 

一个是属性动画的XML,另一个是帧动画。

帧动画是顺序播放一组预先定义好的图片,类似于电影播放。不同于 视图动画,系统提供了另一个类 AnimationDrawable 来使用帧动画。帧动画比较简单,首先需要通过 XML 来定义一个 AnimationDrawable,如下所示。

文件地址:res/drawable/frame_test.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/ic_launcher" android:duration="500"></item>
    <item android:drawable="@drawable/s1" android:duration="500"></item>
    <item android:drawable="@drawable/s2" android:duration="500"></item>
</animation-list>
复制代码

然后将上述的 Drawable 作为 View 的背景并通过 Drawable 来播放动画即可:

Button btn = (Button) findViewById(R.id.shen);
btn.setBackgroundResource(R.drawable.frame_test);
AnimationDrawable drawable = (AnimationDrawable) btn.getBackground();
drawable.start();
复制代码

帧动画比较简单,但是容易引起 OOM (内存不足) , 所以在使用帧动画的时候,尽量避免使用过多尺寸较大的图片。

6、自定义动画

创建自定义动画非常简单,只需要实现它的 applyTransformation 的逻辑就可以啦。不过,通常情况下,还需要覆盖父类的 initialize 方法来实现一些初始化工作。

applyTransformation( float interpolatedTime, Transformation t)
复制代码

applyTransformation 有两个参数,第一个是插值器的事件因子,取值范围 0-1。

第二个参数是矩阵的封装类,一般使用这个类来获得当前的矩阵对象。通过改变获得矩阵对象来将动画实现出来。

两个实例:一个是电视关闭的动画,一个是自定义的3D自定义动画效果。

在 3D 自定义动画效果中,我们使用了一个 camera,该类是 android.graphics.Camera中的 camera 类,它封装了 openGL 的 3D 动画,从而可以很方便的创建 3D 动画效果。

 1 public class CustomAnim extends Animation {
 2 
 3     private int mCenterWidth;
 4     private int mCenterHeight;
 5     private Camera mCamera = new Camera();
 6     private float mRotateY = 0.0f;
 7 
 8     @Override
 9     public void initialize(int width,
10                            int height,
11                            int parentWidth,
12                            int parentHeight) {
13 
14         super.initialize(width, height, parentWidth, parentHeight);
15         // 设置默认时长
16         setDuration(2000);
17         // 动画结束后保留状态
18         setFillAfter(true);
19         // 设置默认插值器
20         setInterpolator(new BounceInterpolator());
21         mCenterWidth = width / 2;
22         mCenterHeight = height / 2;
23     }
24 
25     // 暴露接口-设置旋转角度
26     public void setRotateY(float rotateY) {
27         mRotateY = rotateY;
28     }
29 
30     @Override
31     protected void applyTransformation(
32             float interpolatedTime,
33             Transformation t) {
34         final Matrix matrix = t.getMatrix();
35         mCamera.save();
36         // 使用Camera设置旋转的角度
37         mCamera.rotateY(mRotateY * interpolatedTime);
38         // 将旋转变换作用到matrix上
39         mCamera.getMatrix(matrix);
40         mCamera.restore();
41         // 通过pre方法设置矩阵作用前的偏移量来改变旋转中心
42         matrix.preTranslate(mCenterWidth, mCenterHeight);
43         matrix.postTranslate(-mCenterWidth, -mCenterHeight);
44     }
45 }
复制代码

View Code

public class CustomTV extends Animation {

    private int mCenterWidth;
    private int mCenterHeight;
    private Camera mCamera = new Camera();
    private float mRotateY = 0.0f;

    @Override
    public void initialize(int width,
                           int height,
                           int parentWidth,
                           int parentHeight) {

        super.initialize(width, height, parentWidth, parentHeight);
        // 设置默认时长
        setDuration(1000);
        // 动画结束后保留状态
        setFillAfter(true);
        // 设置默认插值器
        setInterpolator(new AccelerateInterpolator());
        mCenterWidth = width / 2;
        mCenterHeight = height / 2;
    }

    // 暴露接口-设置旋转角度
    public void setRotateY(float rorateY) {
        mRotateY = rorateY;
    }

    @Override
    protected void applyTransformation(
            float interpolatedTime,
            Transformation t) {
        final Matrix matrix = t.getMatrix();
        matrix.preScale(1,
                1 - interpolatedTime,
                mCenterWidth,
                mCenterHeight);
    }
}
复制代码

View Code

activity 文件的内容:

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void btnAnim(View view) {
        CustomAnim customAnim = new CustomAnim();
        customAnim.setRotateY(30);
        view.startAnimation(customAnim);
    }

    public void imgClose(View view) {
        CustomTV customTV = new CustomTV();
        view.startAnimation(customTV);
    }
}
复制代码

View Code

 XML文件:

<RelativeLayout 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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <Button
        android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="btnAnim"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="40dp" />

    <ImageView
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:id="@+id/imageView"
        android:onClick="imgClose"
        android:background="@mipmap/ic_launcher"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>
复制代码

View Code

主要就是两个按钮。

7、LayoutAnimation 

LayoutAnimation 作用于 ViewGroup,为 ViewGroup 制定一个动画,这样当它的子元素出场时都会有这种动画效果。这种效果常常被用于 ListView 上。

LayoutAnimation 也是一个 视图动画,为了给 ViewGroup 子元素加上出场效果,要遵循如下几个步骤。

定义 LayoutAnimation,如下所示:

// res/anim/anim_layout.xml
<layoutAnimation
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:delay="0.5"
    android:animationOrder="reverse"
    android:animation="@anim/anim_item"/>
复制代码

android:delay 表示子动画的事件延迟。比如入场动画周期是300ms,那么 0.5 表示每个子元素都要延迟 150 ms 才能播放入场动画。

android:animationOrder 表示子动画播放顺序,有三种选项:normal(顺序播放), reverse(逆序播放)和 random (随机)。

android:animation 表示为子元素制定具体的入场动画。

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

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

    <translate
        android:fromXDelta="1500"
        android:toXDelta="0" />
</set>
复制代码

接下去为 viewGroup 指定  android:layoutAnimation 属性:  android:layoutAnimation="@anim/anim_layout"。这样,它里面的子元素就会有出场动画了。如下所示:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutAnimation="@anim/anim_layout">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="shenjaiqi"
        android:id="@+id/shen"/>

</RelativeLayout>
复制代码

除了使用 xml 指定 LayoutAnimation 外,还可以通过 LayoutAnimationController 来实现,具体代码如下所示。

ListView listView = (ListView) layout.findViewById(R.id.list);
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);
复制代码

8、Activity 的切换效果

Activity 有默认的切换效果,但是这个效果我们可以自定义的,主要用到 

overridePendingTransition(int enterAnim, int exitAnim)这个方法,这个方法必须在 

startActivity 或 finish() 之后被调用才能生效,它的参数含义如下:

  enterAnim: Activity 被打开的时候,所需的动画资源 id;

  exitanim:Activity 被关闭的时候,所需的动画资源 id;

当启动一个 Activity 的时候,可以按照下面方式添加自定义效果:

Intent intent = new Intent(this, TestActivity.class);
startActivity(intent);
overridePendingTransition(R.anim.enter_anim, R.anim.exit_anim);
复制代码

当 Activity 推出的时候,也可以按照下面的方式添加自定义切换效果:

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

使用动画的注意事项:

1、OOM问题:主要出现在帧动画中,图片较多且大的话极易出现OOM;

2、内存泄漏:属性动画中,有一类无限循环的动画,要在 Activity 退出时及时停止;视图动画一般不存在此类问题;

3、兼容性问题:动画在 3.0 一下的系统有兼容性问题,要做好适配工作;

4、View 动画是对 View 的影响做动画,并不是真正的改变 View 的状态,因此有时候会出现动画完成后View 无法隐藏的现象,即 setVisibility(View.GONE) 失效了,这个时候只要调用 view.clearAnimation() 清除 View 动画即可解决此问题。

5、不要使用px: 尽量使用 dp,

6、动画元素的交互:在 3.0 以下的系统,动画完成后,仍是在老位置触发单机事件;3.0 开始,属性动画的淡季事件触发为移动后的位置,View 动画不变。

7、硬件加速:使用过程中,建议开启硬件加速,提高动画的流畅性。

文章分类
Android
文章标签