Android开发之漫漫长途 XVII—动画

来自:马飞标

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索》以及《深入理解Android 卷Ⅰ,Ⅱ,Ⅲ》中的相关知识,另外也借鉴了其他的优质博客,在此向各位大神表示感谢,膜拜!!!


前言

该篇博客我们来说说Android动画的那些事。

Android动画简介

Android的动画是一个令人着迷的地方,不过Android的动画可以简单分为3类,View动画,属性动画。下面我们分别介绍。

View动画

View动画顾名思义其作用对象为View,包含平移、缩放、旋转、透明,这四类变化分别对应着Animation的子类TranlateAnimation、ScaleAnimation、RotateAnimation和AlphaAnimation。虽然有对应的类,不过,在Android动画中,还是建议用XML来定义,其对应的标签如下所示

View动画的XML描述语法的固定格式

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@[package:]anim/interpolator_resource"
android:shareInterpolator=["true"|"false"]>

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

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

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

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

    ...
</set>

注:
android:interpolator 表示动画集合所采用的插值器,插值器影响动画的速度,默认是@android:anim/accelerate_decelerate_interpolator,即加减速插值器,关于插值器的概念将会在下面介绍
android:shareInterpolator表示集合中的动画和集合共享同一个插值器,如果集合不指定插值器,那么子动画就需要单独指定插值器或者使用默认值。

View动画坐标系

在使用View动画时,就不得不提View的动画坐标体系,如下图,其坐标系是以View的左上角为原点,横向向右为x轴正方向,纵向向下为y轴正方向,在平移中toXDelta为正数表示以原点为参考沿x轴向右移动,为负数时,反之,旋转时正数角度表示顺时针

View动画的主体是View,更准确的说是View的副本(影子),View动画更改的只是显示,其x,y坐标仍然没有改变,响应事件的位置没有改变,也就是说view本身并没有改变。

也因此,不要使用View动画做交互性操作,例如点击。现在View动画已经很少人使用了,不过View动画简单已用,可以用来做一些简单的不需要交互的动画。

View动画属性配置中 %以及%p的含义

我们先来看一段代码

<translate 
   ...
   android:fromXDelta="0.0"
   android:toXDelta="50.0%p"
   android:fromYDelta="0.0"
   android:toYDelta="-50.0%p" />


 <scale
    ...
    android:pivotX="50.0%"
    android:pivotY="100.0%"/>

  1. android:fromXDelta="X",X>0 表示以View动画的开始位置是以当前View的原点向右偏移X个位置,同理,X<0时View动画的开始位置是以当前View的原点向左偏移X个位置

  2. android:fromXDelta="X%",X>0 表示以View动画的开始位置是以当前View的原点向右偏移View宽度的X%(View.width*X%)个位置,同理,X<0时View动画的开始位置是以当前View的原点向左偏移向右偏移View宽度的X%(View.width*X%)个位置

  3. android:fromXDelta="X%p",X>0 表示以View动画的开始位置是以当前View的父View的原点向右偏移父View宽度的X%(View.Parent.width*X%)个位置,同理,X<0时View动画的开始位置是以当前View的父View的原点向左偏移向右偏移父View宽度的X%(View.Parent.width*X%)个位置

自定义View动画

自定义View动画既简单又复杂,简单的是一个新的动画类只需继承Animation类并重写2个方法,代码如下所示

public class CustomAnimation 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);
    }
}

说他复杂呢,是因为View动画的过程实际上是矩阵变换的过程,这个涉及线性代数的知识。

View动画的特殊应用场景

LayoutAnimation
LayoutAnimation用于ViewGroup,为ViewGroup指定一个动画,这样它的所有子View在出场时都带有指定的动画效果。

<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animation="@anim/bm_anim_bottom_in"
    android:animationOrder="normal"
    android:delay="0.5" />

android:animation
为子元素指定具体的入场动画,本例中的代码如下

<set xmlns:android="http://schemas.android.com/apk/res/android"
  android:duration="500">

  <translate
      android:fromYDelta="50.0%p"
      android:interpolator="@android:anim/accelerate_decelerate_interpolator"
      android:toYDelta="0.0" />

  <alpha
      android:fromAlpha="0.0"
      android:interpolator="@android:anim/accelerate_decelerate_interpolator"
      android:toAlpha="1.0" />

</set>

android:delay
表示子元素开始动画的延迟,比如子元素的入场时间周期为500ms,那么0.5 表示每个子元素都要延迟500*0.5=250ms后才会开始播放。总体来说就是第一个子元素延迟250ms播放,第二个子元素延迟500ms播放,后面的子元素以此类推。

android:animationOrder
表示子元素动画的顺序,有三种选项:normal,reverse,random,其中normal表示顺序显示,即排在前面的子元素先开始播放入场动画;reverse表示逆向显示,即排在后面的子元素先开始播放入场动画;random则表示随机播放入场动画。

LayoutAnimation的使用

LayoutAnimation的使用比较简单,可直接在ViewGroup内指定android:layoutAnimation属性,如下所属

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/view_group"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutAnimation="@anim/bm_layout_anim_item_bottom_in"
    android:orientation="vertical">


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="1!"
        android:textSize="20sp" />


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="2!"
        android:textSize="20sp" />


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="3!"
        android:textSize="20sp" />


    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="4!"
        android:textSize="20sp" />

</LinearLayout>

也可以使用代码控制,如下所示

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //使用AnimationUtils类的静态方法loadAnimation()来加载XML中的动画XML文件
        Animation animation = AnimationUtils.loadAnimation(this, R.anim.bm_anim_bottom_in);
        ViewGroup viewGroup = findViewById(R.id.view_group);
        LayoutAnimationController layoutAnimationController = new LayoutAnimationController(animation);
        layoutAnimationController.setDelay(0.5f);
        layoutAnimationController.setOrder(LayoutAnimationController.ORDER_NORMAL);
        viewGroup.setLayoutAnimation(layoutAnimationController);
    }
}

Activity之间的切换动画

Activity有自己的默认切换效果,不过我们也能自己定义Activity的切换效果,主要用到类Activity的一个函数overridePendingTransition(int enterAnim, int exitAnim),参数说明如下

  • enterAnim:表示Activity被打开时所需要的动画资源id

  • exitAnim:表示Activity被暂停时所需要的动画资源id

启动一个Activity时,可按照下面的代码为其添加切换效果

startActivity(new Intent(this,Main2Activity.class));
overridePendingTransition(R.anim.pop_enter_anim,R.anim.pop_exit_anim);

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

需要注意的是 overridePendingTransition 必须放在 startActivity 或者finish之后,才有动画效果

插值器与估值器

TimeInterpolator 中文翻译为时间插值器,它的作用是根据时间流逝的百分比来计算出当前属性值改变的百分比。系统预置的有 LinearInterpolator(线性插值器:匀速动画)、AccelerateDecelerateInterpolator(加速减速插值器:动画两头慢中间快)和 DecelerateInterpolator(减速插值器:动画越来越慢),OvershootInterpolator (抖动动画)等。

TypeEvaluator,估值器,它的作用是根据当前属性改变的百分比来计算改变后的属性值。系统预置的有IntEvaluator(针对整型属性)和FloatEvaluator(针对浮点型属性),ArgbEvaluator(针对Color属性)
  

View动画的实际实现者Matrix

前面也说过View动画的过程实际上是矩阵变换的过程,Matrix就是操作矩阵变换的类。Matrix是一个3*3的矩阵,默认创建一个Matrix的对象的时候该矩阵是一个单位矩阵。

if (mMatrix == null) {
   mMatrix = new Matrix();//新建对象时默认矩阵是一个3*3的单位矩阵
else {
   mMatrix.reset();//reset方法将该矩阵重置为一个3*3的单位矩阵
}

默认的单位矩阵如下图,

那么这个单位矩阵表示什么意思呢,我们再来看下图。

上面的数值与下面的属性一一对应,
MSCALE_X|Y对应的是缩放变化,
MTRANS_X|Y对应的是平移变化,
MSKEW_X|Y对应的是错切变化

下面我以平移动画为例说明该矩阵如何作用于动画的。我们假设当前View的原点坐标为,

那么我们想让该View的原点横向移动,

纵向移动

那么用矩阵表示该过程是

这个行列式前面表示变换的矩阵即Matrix,后面的矩阵是我们View的坐标矩阵,也就是说View平移动画的过程是把坐标矩阵点乘一个变换矩阵得到最后的结果矩阵。其他的变化与平移变换类似,更改Matrix矩阵中相关分量即可。

本篇总结

本篇呢,对Android的View动画做了一个比较深入的讲解以及分析,有不到之处还请指出。

下篇预告

下篇呢,继续对Android动画的另外一个大类属性动画作分析


此致,敬礼

推荐↓↓↓
安卓开发
上一篇:Flutter最佳入门方式:写一个计算器 下一篇:Android开发面试经—常见面试官提问Android题