Android群英传读书笔记——第七章:Android动画机制与使用技巧

第七章目录

  • 7.1 Android View动画框架 
    • 7.1.1 透明度动画
    • 7.1.2 旋转动画
    • 7.1.3 位移动画
    • 7.1.4 缩放动画
    • 7.1.5 动画集合
  • 7.2 Android属性动画分析 
    • 7.2.1 ObjectAnimator
    • 7.2.2 PropertyValuesHolder
    • 7.2.3 ValueAnimator
    • 7.2.4 动画事件的监听
    • 7.2.5 AnimatorSet
    • 7.2.6 在XML中使用属性动画
    • 7.2.7 View的animate方法
  • 7.3 Android布局动画
  • 7.4 Interpolators
  • 7.5 自定义动画
  • 7.6 Android5.X SVG矢量动画机制 
    • 7.6.1 标签
    • 7.6.2 SVG常用指令
    • 7.6.3 SVG编辑器
    • 7.6.4 Android中使用SVG
    • 7.6.5 SVG动画实例
  • 7.7 Android动画特效 
    • 7.7.1 灵动菜单
    • 7.7.2 计时器动画
    • 7.7.3 下拉展开动画

第七章读书笔记

动画分为视图动画属性动画

7.1 Android View动画框架

  1. View动画定义了透明度、旋转、缩放、位移几种常见的动画
  2. 实现原理:每次绘制视图时View所在的ViewGroup中的drawChild函数获取该View的Animation的Transformation值,然后调用canvas.concat(transformToApply.getMatrix()),通过矩阵运算完成动画帧
  3. View动画使用简单,效率高,但是不具备交互性

7.1.1 透明度动画

AlphaAnimation aa = new AlphaAnimation(0,1);
aa.setDuration(1000);
view.startAnimation(aa);

7.1.2 旋转动画

RotateAnimation ra = new RotateAnimation(0,360,100,100);
ra.setDuration(1000);
view.startAnimation(ra);
  • 参数分别为旋转起始角度和中心点的坐标,以自身中心点设置旋转动画:
RotateAnimation ra = new RotateAnimation(0,360,
RotateAnimation.RELATIVE_TO_SELF,0.5F,
RotateAnimation.RELATIVE_TO_SELF,0.5F);

7.1.3 位移动画

TranslateAnimation ta = new TranslateAnimation(0,200,0,300);
ta.setDuration(1000);
view.startAnimation(ta);

7.1.4 缩放动画

ScaleAnimation sa = new ScaleAnimation(0,2,0,2);
sa.setDuration(1000);
view.startAnimation(sa);
  • 以自身中心点设置缩放动画:
ScaleAnimation sa = new ScaleAnimation(0,1,0,1,RotateAnimation.RELATIVE_TO_SELF,0.5F,
RotateAnimation.RELATIVE_TO_SELF,0.5F);
sa.setDuration(1000);
view.startAnimation(sa);

7.1.5 动画集合

通过AnimationSet,将动画以组合的形式展现出来:

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,200,0,300);
ta.setDuration(1000);
as.addAnimation(ta);

view.startAnimation(as);

系统还提供了对应的监听回调,可以得到动画的开始,结束和重复事件:

animation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
    }

    @Override
    public void onAnimationEnd(Animation animation) {
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
    }
});

7.2 Android属性动画分析

  1. Animator框架中使用最多的就是AnimatorSetObjectAnimator
  2. 属性动画通过调用属性的get、set方法来真实地控制了一个View的属性值

7.2.1 ObjectAnimator

  1. 只需通过静态工厂直接返回一个ObjectAnimator对象
  2. 参数包括一个对象和对象的属性,这个属性必须有get和set函数
  3. 调用setInterpolator来设置相应的插值器
  4. 旧版本的视图动画只是改变了显示,没有改变事件响应的位置

简单的平移动画的实现:

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

参数介绍:

  • 被操作的View
  • 要操作的属性,属性必须有get、set方法,要不无法使用
  • 可变数组,传递该属性变化的一个取值过程

一些常用的可以直接使用的属性:

  • translationX和translationY:作为一种增量来控制着View对象从它布局容器的左上角坐标偏移的位置
  • rotation、rotationX和rotationY:控制View对象围绕支点进行2D和3D旋转
  • scaleX和scaleY:控制View对象围绕它的支点进行2D缩放
  • pivotX和pivotY:控制View对象的支点位置,围绕这个支点进行旋转和缩放变换处理,默认情况下,该支点的位置就是View对象的中心点
  • x和y:它描述了View对象在它的容器中的最终位置,它是最初的左上角坐标和 translationX和 translationY值的累计和
  • alpha:它表示View对象的alpha透明度,默认值是1(不透明),0代表完全透明(不可见)

如果某个属性没有get、set方法,两种方案来解决:

  1. 自定义一个属性或者包装类
  2. 通过ValueAnimator实现

以自定义一个属性增加get、set方法为例:

private static 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();
    }
}

通过代码使用:

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

7.2.2 PropertyValuesHolder

  1. 类似视图动画的AnimationSet,可以实现多个属性动画的组合
  2. 在平移的过程中,同时改变X、Y轴的缩放,可以这样实现:
PropertyValuesHolder pvh1 = PropertyValuesHolder.ofFloat("translationX",300f);
PropertyValuesHolder pvh2 = PropertyValuesHolder.ofFloat("scaleX",1f,0,1f);
PropertyValuesHolder pvh3 = PropertyValuesHolder.ofFloat("scaleY",1f,0,1f);
ObjectAnimator.ofPropertyValuesHolder(view,pvh1,pvh2,pvh3).setDuration(1000).start();   

7.2.3 ValueAnimator

  1. ObjectAnimator继承自ValueAnimator
  2. ValueAnimator本身不提供任何动画效果,它更像一个数值发生器,用来产生具有一定规律的数字,从而让调用者来控制动画的实现过程,通过AnimatorUpdateListener监听数值的变换:
ValueAnimator animator = ValueAnimator.ofFloat(0,100);
animator.setTarget(view);
animator.setDuration(1000).start();
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float value = (float) animation.getAnimatedValue();
        //TODO use the value
    }
});

7.2.4 动画事件的监听

系统提供了属性动画的监听回调:

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

    }
});

大部分我们只关心End事件,所以Android也提供了一个AnimatorListenerAdapter来让我们选择必要的事件监听:

anim.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {

    }
});

7.2.5 AnimatorSet

  • AnimatorSet可以实现和PropertyValuesHolder一样的效果,不同的是Animator还可以实现更精确的顺序控制:
ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "translationX", 300f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(view, "scaleX", 1f, 0, 1f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(view, "scaleY", 1f, 0, 1f);
AnimatorSet set = new AnimatorSet();
set.setDuration(1000);
set.playTogether(animator1,animator2,animator3);
set.start();
  • AnimatorSet不仅仅通过playTogether(),还有其他方法控制多个效果的协同工作:playSequentially()、animSet.play().with()、before()、after()

7.2.6 在XML中使用属性动画

属性动画和视图动画一样,也可以在xml文件中编写:



在程序中使用也简单:

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

7.2.7 View的animate方法

Google给View增加了animate方法来直接驱动属性动画:

animate.animate().alpha(0).y(300).setDuration(300).withStartAction(new Runnable() {
    @Override
    public void run() {

    }
}).withEndAction(new Runnable() {
    @Override
    public void run() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {

            }
        });
    }
}).start();

7.3 Android布局动画

  • 布局动画的作用是指ViewGroup添加view的时候添加一个动画过滤效果
  • 最简单的布局动画就是在ViewGroup的XML中打开一个系统默认的效果:
android:animateLayoutChanges="true"

还可以通过LayoutAnimationController实现自定义子view的过渡效果

LayoutAnimationController 第一个参数是需要的动画,第二个参数是每个子View显示的delay时间,若时间不为0,还可以setOrder设置子View的显示顺序:

  • LayoutAnimationController.ORDER_NORMAL:顺序
  • LayoutAnimationController.ORDER_RANDOM:随机
  • LayoutAnimationController.ORDER_REVEESE:反序
LinearLayout ll = (LinearLayout) findViewById(R.id.ll);
//设置过渡动画
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);

7.4 Interpolators(插值器)

  • 插值器是可以控制动画变换速率的一个属性值,是匀速的还是先快后慢还是先慢后快还是反弹等等好多种插值器
  • 这里就不介绍了,好多种直接用就好

7.5 自定义动画

  • 需要实现它的applyTransformation的逻辑
  • 需要覆盖父类的initalize方法实现一些初始化工作

模拟电视关闭效果:

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    final Matrix matrix = t.getMatrix();
    // 缩放以及缩放的中心点
    matrix.preScale(1, 1 - interpolatedTime, mCenterwidth,mCenterheight);
    super.applyTransformation(interpolatedTime, t);
}

android.graphics.Camera中的Camera类,它封装了openGL的3D动画,从而可以风场方便地创建3D动画效果,只要移动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;
}

@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
    final Matrix matrix = t.getMatrix();
    mCamera.save();
    //使用Camera设置旋转的角度
    mCamera.rotateY(mRotateY * interpolatedTime);
    //将旋转变换作用到matrix上
    mCamera.getMatrix(matrix);
    mCamera.restore();
    //通过pre方法设置矩形作用前的偏移量来改变旋转中心
    matrix.preTranslate(mCenterWidth, mCenterHeight);
    matrix.postTranslate(-mCenterWidth, -mCenterHeight);
}

7.6 Android 5.X SVG 矢量动画机制

SVG的定义:

  • 可伸缩矢量图形(Scalable Vector Graphics)
  • 定义用于网络的基于矢量的图形
  • 使用XML格式定义图形
  • 图片在放大或改变尺寸的情况下其图形质量不会有所损失
  • 万维网联盟的标准
  • 与诸多DOM和XSL之类的W3C标准是一个整体

Android5.X之后添加了对SVG的标签的支持,其优点是:

  • 放大不会失真
  • bitmap需要为不同分辨率设计多套图标,而矢量图则不需要

7.6.1 标签

使用有以下指令:

  • M = moveto(M X,Y):将画笔移动到指定的坐标位置,但未发生绘制
  • L = lineto(L X,Y):画直线到指定的坐标位置
  • H = horizontal lineto( H X):画水平线到指定的X坐标位置
  • V = vertical lineto(V Y ):画垂直线到指定的Y坐标
  • C = curveto(C ,X1,Y1,X2,Y2,ENDX,ENDY):三次贝塞尔曲线
  • S = smooth curveto(S X2,Y2,ENDX,ENDY):三次贝塞尔曲线
  • Q = quadratic Belzier curve(Q X Y,ENDX,ENDY):二次贝塞尔曲线
  • T = smooth quadratic Belzier curvrto(T,ENDX,ENDY):映射前面路径的终点
  • A = elliptical Are(A RX,RY,XROTATION,FLAG1FLAG2,X,Y):弧线
  • Z = closepath():关闭路径

使用上面指令时,需要注意以下几点:

  • 坐标轴以(0,0)为中心,X轴水平向右,Y轴水平向下
  • 所有指令大小写均可,大写绝对定位,参照全局坐标系,小写相对定位,参照父容器坐标系
  • 指令和数据间的空格可以无视
  • 同一指令出现多次可以只用一个

7.6.2 SVG常用指令


  • 绘制直线的指令是“L”,代表从当前点绘制直线到给定点,“L”之后的参数是一个点坐标,同时,还可以使用“H”和“V”指令来绘制水平、竖直线,后面的参数是x坐标或y坐标

  • M指令类似Android绘图中的path类moveto方法,即代表画笔移动到某一点,但并不发生绘图动作

  • A指令是用来绘制一条弧线,且允许弧线不闭合,可以把A指令绘制的弧度想象成椭圆的某一段,A指令一下有七个参数 
    • RX,RY:所在的椭圆的半轴大小
    • XROTATION:椭圆的X轴与水平方向顺时针方向的夹角,可以想象成一个水平的椭圆饶中心点顺时针旋转XROTATION 的角度
    • FLAG1:只有两个值,1表示大角度弧度,0为小角度弧度
    • FLAG2:只有两个值,确定从起点到终点的方向1顺时针,0逆时针
    • X,Y轴:终点坐标

7.6.3 SVG编辑器

  • 在线可视化SVG编辑器官网:http://editor.method.ac/
  • 优秀的离线SVG编辑器:Inkscape

7.6.4 Android 中使用SVG

Android5.X提供了两个API来支持SVG:

  • VectorDrawable:在XML中可以创建一个静态的SVG图形
  • AnimatedVectorDrawable:给VectorDrawable提供动画效果

7.6.4.1 VectorDrawable

VectorDrawable的使用是通过标签来声明的:



如果做过Web的应该对viewport应该很熟悉

  • height:表示SVG的高度200dp
  • width:表示SVG的宽度200dp
  • viewportHeight:表示SVG图形被划分成100份
  • viewportWidth:表示SVG图形被划分成100份
  • 如果在绘图中使用坐标(50,50),则意味着该坐标为正中间

接下来,给标签增加显示path:



    
        
    

布局文件中这样用:

                  

7.6.4.2 AnimatedVectorDrawable

  • AnimatedVectorDrawable使用:Google工程师将其比喻为一个胶水,通过其连接静态的VectorDrawable和动态的objectAnimator

1、首先在XML文件中通过标签来声明对AnimatedVectorDrawable的使用:


    

特别注意的是:这个target里面的name要和VectorDrawable的name对应,animation要和动画资源文件对应

2、对应的VectorDrawable文件:


    
        
    

3、4、对应的animation文件:

特别注意的是:在标签和标签中添加了rotate、fillColor、pathData等属性,那么可以通过objectAnimator指定android:propertyName=”XXXX”属性来控制哪一种属性,如果指定属性为pathData,那么需要添加一个属性——android:valueType=”pathType”来告诉系统进行pathData的变换

4、在布局文件中使用:

5、在Activity中开启动画:

ImageView image = (ImageView) findViewById(R.id.image);
((Animatable)image.getDrawable()).start();

7.6.5 SVG动画实例

7.6.5.1 线图动画

1、定义我们的VectorDrawable


    
        

        
    

2、定义两个Path的objectAnimator,从path1到path2

3、定义AnimatedVectorDrawable连接VectorDrawable和objectAnimator


    
    

4、布局文件中使用这个AnimatedVectorDrawable

5、代码中启动动画,可以在点击事件中写这些

ImageView image = (ImageView) findViewById(R.id.image);
Drawable drawable = image.getDrawable();
if (drawable instanceof Animatable) {
    ((Animatable) drawable).start();
}

7.6.5.2 模拟三球仪

1、定义我们的VectorDrawable



    

        

        

            

            
                
            
        
    

2、定义两个objectAnimator,代码都是一样的

3、定义AnimatedVectorDrawable连接VectorDrawable和objectAnimator


    
    

4、布局文件中使用这个AnimatedVectorDrawable

5、代码中启动动画

ImageView image = (ImageView) findViewById(R.id.image);
Drawable drawable = image.getDrawable();
if (drawable instanceof Animatable) {
    ((Animatable) drawable).start();
}

7.6.5.3 轨迹动画

1、定义我们的VectorDrawable




    

    

2、定义两个objectAnimator,代码都是一样的


3、定义AnimatedVectorDrawable连接VectorDrawable和objectAnimator


    

4、布局文件中使用这个AnimatedVectorDrawable

5、代码中启动动画

ImageView image = (ImageView) findViewById(R.id.image);
Drawable drawable = image.getDrawable();
if (drawable instanceof Animatable) {
    ((Animatable) drawable).start();
}

SVG动画的几个基本步骤都是一样的:

1、定义VectorDrawable,相当于整一个静态的图片

2、定义ObjectAnimator,相当于创建一个动画

3、定义AnimatedVectorDrawable去连接以上两个玩意儿

4、在布局文件中使用这个AnimatedVectorDrawable,ImageView的src中使用就行

5、在代码中启动动画

7.7 动画特效

7.7.1 灵动菜单

因为具有交互性,必须使用属性动画,设置相应的插值器就可以实现了

布局文件:




    

    

    

    

    

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 mImageViews = new ArrayList();
    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;
    }
}

7.7.2 计时器动画

在这里使用了ValueAnimator,来回顾一下

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();
    }
}

7.7.3 下拉展开动画

还是使用ValueAnimator来实现

布局文件,两个不同的LinearLayout来显示:




    

        

        
    

    

        

        
    

Activity文件:

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;
    }
}

总结

这一章的动画总体来说还是比较简单,就是几个Android动画框架的使用

  1. 视图动画Animation,无法交互
  2. 属性动画Animator,可以实现交互,Google更加提倡,可以实现更加丰富的动画效果
  3. 布局动画,ViewGroup中添加子View是的一个过渡动画效果
  4. Android 5.X SVG矢量动画机制,比如一些点击事件的动画效果可以用矢量动画来实现

 

 

你可能感兴趣的:(Android群英传读书笔记)