Android 2D Graphics

1. Outline

本文主要从以下三个大的方面来说明一下2D Graphic 绘图的一些相关函数及应用。

  • Color,Paint,Canvas,Typeface,Bitmap五个基本对象的概念,以及使用的方法
  • SurfaceView在app中的使用
  • 动画的使用

Android画图最基本的三个对象是(Color,Paint,Canvas)
三个类都存放在 android.graphics包下

  1. Color :颜色对象,相当于现实生活中的 调料
  2. Paint : 画笔对象,相当于现实生活中画图用的 笔,主要的还是对‘画笔’进行设置
  3. Canvas : 画布对象,相当于现实生活中画图用的画纸或者画布

而Typeface是一个字体对象,同样在android.graphics包,这个类的作用是获取字体,创建字体,以及设置字体。

其中Paint,Canvas需要配合使用,而Color和Typeface可以在普通的View中单独使用

2. Canvas

2.1 综述

Class Overview
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).

A Canvas works for you as a pretense, or interface, to the actual surface upon which your graphics will be drawn — it holds all of your "draw" calls. Via the Canvas, your drawing is actually performed upon an underlying Bitmap, which is placed into the window.

我们可以把这个Canvas理解成系统提供给我们的一块内存区域(但实际上它只是一套画图的API,真正的内存是下面的Bitmap),而且它还提供了一整套对这个内存区域进行操作的方法,所有的这些操作都是画图API。

Drawing to a Canvas, is better when your application needs to regularly re-draw itself. Applications such as video games should be drawing to the Canvas on its own.

In the same thread as your UI Activity, wherein you create a custom View component in your layout, call invalidate() and then handle the onDraw() callback.

In the event that you're drawing within the onDraw() callback method, the Canvas is provided for you and you need only place your drawing calls upon it.

如果我们需要使用Canvas绘制自定义图形
1)需要继承 View 这个类
2)并要实现一个带Context参数的构造函数,因为父类中,没有隐式无参的构造函数
3)需重写父类中的onDraw方法,一切的画图操作将在这进行

2.2 获取方式

Canvas的获取方式有以下三种:

1 . 在View的OnDraw方法中,回调方法OnDraw会将Canvas做为参数传入,方法中直接使用即可。
一个典型的Canvas使用方法如下图所示。

Paste_Image.png

In the event that you're drawing within the onDraw() callback method, the Canvas is provided for you and you need only place your drawing calls upon it.

To start, extend the View class (or descendant thereof) and define the onDraw() callback method. This method will be called by the Android framework to request that your View draw itself. This is where you will perform all your calls to draw through the Canvas, which is passed to you through the onDraw() callback.

对于这方法,view在绘制之后onDraw 方法不会被反复调用,需要我们调用View的invalidate方法来触发相应view的onDraw方法再次被调用。

The Android framework will only call onDraw() as necessary. Each time that your application is prepared to be drawn, you must request your View be invalidated by calling invalidate(). This indicates that you'd like your View to be drawn and Android will then call your onDraw() method (though is not guaranteed that the callback will be instantaneous).

这种方法的试用范围,主要是不要求高显示刷新速度的简单游戏.对于速度有较高要求的应用中,可以使用下面的方法.

2 . 使用专门的SurfaceView的canvas来画图。这种方式最大的区别就是SurfaceView中定义了一个专门的线程来完成画图工作,应用程序不需要等待View的刷图,提高性能。这种方法主要用在游戏,高品质动画方面的画图。

SurfaceView中使用SurfaceHolder.lockCanvas()来获取Canvas,我们后面会在专门的SurfaceView的章节中讲到Canvas的使用,这里暂时先跳过。

  1. 除了前面提到的两种间接获取的方式,我们也可以自己创建Canvas来使用。

However, if you need to create a new Canvas, then you must define the Bitmap upon which drawing will actually be performed. The Bitmap is always required for a Canvas. You can set up a new Canvas like this:

 Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
  �Canvas c = new Canvas(b);

Now your Canvas will draw onto the defined Bitmap. After drawing upon it with the Canvas, you can then carry your Bitmap to another Canvas with one of the Canvas.drawBitmap(Bitmap,...) methods. It‘s recommended that you ultimately draw your final graphics through a Canvas offered to you by View.onDraw() orSurfaceHolder.lockCanvas() .

需要注意的是,如果使用自定义的Canvas,我们最后依然需要通过调用系统提供的Canvas的Canvas.drawBitmap(Bitmap,...)方法,将canvas最终绘制出来。

2.3 使用

Canvas的使用很简单,我们主要就是使用系统提供的一些API来绘制指定图形,主要的API有:

drawRect(RectF rect, Paint paint) //绘制区域,参数一为RectF一个区域 
drawPath(Path path, Paint paint) //绘制一个路径,参数一为Path路径对象
drawLine(float startX, float startY, float stopX, float stopY, Paintpaint) //画线
drawPoint(float x, float y, Paint paint) //画点
drawText(String text, float x, floaty, Paint paint)  //绘制文本,参数一是String类型的文本,参数二x轴,参数三y轴
drawOval(RectF oval, Paint paint)//画椭圆,参数一是扫描区域�
drawCircle(float cx, float cy, float radius,Paint paint)// 绘制圆,参数一是中心点的x轴,参数二是中心点的y轴,参数三是半径
�drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter, Paint paint)//画弧,参数一是RectF对象,一个矩形区域椭圆形的界限用于定义在形状、大小、电弧,参数二是起始角(度)在电弧的开始,参数三是扫描角度

3 Paint

3.1 综述

Canvas在绘制具体形状时,一个必不可少的对象就是Paint对象。

这个对象就是我们前面提到的画笔对象。

Class Overview
The Paint class holds the style and color information about how to draw geometries, text and bitmaps.

其实Paint对象就是一个包含了对画笔各种属性设置方法的类,其中主要的方法如下:

setARGB(int a, int r, int g, int b) // 设置 Paint对象颜色,参数一为alpha透明值
setAlpha(int a) // 设置alpha不透明度,范围为0~255
setAntiAlias(boolean aa) // 是否抗锯齿
setColor(int color)  // 设置颜色,这里Android内部定义的有Color类包含了一些常见颜色定义
setTextScaleX(float scaleX)  // 设置文本缩放倍数,1.0f为原始
setTextSize(float textSize)  // 设置字体大小
setUnderlineText(booleanunderlineText)  // 设置下划线

这样,我们首先设置好画笔对象,然后使用相应的画笔来在Canvas上绘制具体的图案,就可以产生如下图效果。

Paste_Image.png

3.2 Shader

在使用paint设置画笔的同时,我们也可以给画笔来设置shader对象来为画笔增加更多的效果。
Shader类专门用来渲染图像以及一些几何图形,Shader下面包括几个直接子类,分别是:

BitmapShader    : 图像渲染
LinearGradient  : 线性渐变
RadialGradient  : 环形渐变
SweepGradient   : 扫描渐变---围绕一个中心点扫描渐变就像电影里那种雷达扫描
ComposeShader   : 组合渲染

Shader类的使用,都需要先构建Shader对象,然后通过Paint的setShader方法设置渲染对象,然后设置渲染对象,然后再绘制时使用这个Paint对象即可。

简单的shader使用如下图所示。分别使用了LinearGradient,RadialGradient和SweepGradient。

4 Bitmap

4.1 综述

我们在前面已经提到过,Canvas下面真正的内存区域实际上是Bitmap。
而实际上,Bitmap的作用不止于此。

Bitmap是Android系统中的图像处理的最重要类之一。它还可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。

我们可以通过以下几种不同的方法来从不同的来源中获取一个Bitmap:

Resource   : BitmapFactory.decodeResource()
File       : BitmapFactory.decodeFile()
Byte Array : BitmapFactory.decodeByteArray()
Stream     : BitmapFactory.decodeStream()

Bitmap的绘制很简单,只需要调用Canvas.drawBitmap(Bitmap,...)方法即可。

4.2 Bitmap的缩放和旋转

我们有三种办法可以对一个指定的位图进行缩放。
1 .使用Bitmap.createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter)方法,将现有bitmap转化为一个指定大小的bitmap

2 . 我们也可以使用BitmapFactory.Options,在创建位图时就进行缩放:

BitmapFactory.Options mOptions = new BitmapFactory.Options();
mOptions.inSampleSize = 4;
Bitmap mBitmap = BitmapFactory.decodeFile("/sdcard/image.jpg", options);

这段代码将把位图缩小到1/4.

3 .我们也可以使用Matrix 类来实现更灵活的缩放。
Matrix,顾名思义,是一个矩阵类,对于缩放,我们有两种方法可以使用: preScale(float sx, float sy)和postScale(float sx, float sy).

这两种方法都是将现有bitmap做了矩阵坐标的变换。
不同的是,pre方法使用的变换公式是:M' = M * S(sx, sy)
而post方法使用的变换公式是:M‘ = S(sx, sy) * M。

例如如果我们使用:mirrorMatrix.preScale(-0.2f, 0.2f);
这个矩阵变换的结果是矩阵中所有的点做了左右颠倒,并且缩放了0.2倍

配置好Matrix之后,只需要调用Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height, Matrix m, boolean filter) 方法,将设置好的Matrix 传入,我们就可以生成一个缩放且颠倒的Bitmap。

位图的旋转和缩放类似。
使用Matrix 类来实现旋转。
mirrorMatrix.preRotate(30);
实际上是执行了M‘ = M * R(degrees)这个矩阵变换,最终的效果是位图被向右旋转了30°

Bitmap旋转和缩放的效果如下图所示。

Paste_Image.png

4.3 Bitmap的常见问题

由于bitmap底层对应了内存的分配和使用,所以如果使用不当,有可能会导致内存问题的产生,一般常见的内存问题有两大类。

4.3.1 内存溢出

android系统中读取位图Bitmap时.分给虚拟机中图片的堆栈大小是有限的,所以如果堆栈分配过多的时候,就会导致内存溢出。如下面这段代码:

ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>();
for (int i = 0; i < 10; i++) {
      Bitmap mConvertedBitmap = Bitmap.createBitmap(mBitmap, 0, 0,
     mBitmap.getWidth(), mBitmap.getHeight(), mirrorMatrix, false);
     bitmaps.add(mConvertedBitmap);
}

执行这段代码就会发生内存溢出,导致应用FC。只有十张普通大小的图片,一旦使用不当,也会导致内存溢出的发生。因此,我们一定要及时对不使用的bitmap调用Bitmap.recycle方法,防止内存溢出的发生。

4.3.2 回收不当

我们上一点中,需要及时对不使用的bitmap调用Bitmap.recycle方法,但是也需要注意的是,一定要保证bitmap在被recycle后,不要再使用这个bitmap。

否则将会发生Canvas: trying to use a recycled bitmap的异常被抛出,从而导致应用FC。

由于Bitmap直接涉及到了内存的操作,出问题的可能性较大,Google也给出了很多针对Bitmap的优化手段:
Loading Large Bitmaps Efficiently:
http://developer.android.com/intl/zh-cn/training/displaying-bitmaps/load-bitmap.html
(上文的核心思想是加载压缩后的图片,主要就是通过设置options.inJustDecodeBounds = true,第一次加载图片获取图片大小,根据图片大小来设置压缩参数,第二次才真正加载图片)
Caching Bitmaps:
http://developer.android.com/intl/zh-cn/training/displaying-bitmaps/cache-bitmap.html
(上文核心就是使用LruCache)

5 Typeface

Class Overview

The Typeface class specifies the typeface and intrinsic style of a font. This is used in the paint, along with optionally Paint settings like textSize, textSkewX, textScaleX to specify how text appears when drawn (and measured).

Typeface的很简单,主要就是一个字体的辅助类。一般情况下,我们就是使用字体文件创建一个Typeface,然后使用Typeface即可。

//Default Font
Typeface mType = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
mPaint.setTypeface(mType);

// Custom Font : /assets/fonts/abc.ttf
Typeface mType = Typeface.createFromAsset(getContext().getAssets(),"fonts/aaa.ttf");
mPaint.setTypeface(mType);

//Custom Font : /sdcard/abc.ttf
Typeface mType = Typeface.createFromFile("sdcard/abc.ttf");
mPaint.setTypeface(mType);

6 SurfaceView

我们前面提到,在对速度有较高的要求的应用中,如一些实时性要求高的游戏中,或者摄像头预览、视频播放来说,它们的UI都比较复杂,而且要求能够进行高效的绘制,这种情况下我们应该使用surfaceView。

SurfaceView拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行行绘制。又由于不占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应。

对于那些具有SurfaceView的窗口来说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer,用来单独描述它的绘图表面,以区别于它的宿主窗口的绘图表面。

用来描述SurfaceView的Layer的Z轴位置是小于用来其宿主Activity窗口的Layer的Z轴位置的,但是前者会在后者的上面挖一个“洞”出来,以便它的UI可以对用户可见。实际上,SurfaceView在其宿主Activity窗口上所挖的“洞”只不过是在其宿主Activity窗口上设置了一块透明区域。

说完前面的理论知识,我们来看下SurfaceView是如何使用的。
典型的使用方法如下图所示。

Paste_Image.png
  1. 首先我们需要一个继承自SurfaceView的类,实现一个带Context参数的构造函数

  2. 其次需要通过getHolder函数来获取一个SurfaceHolder,它是一个接口,其作用就像一个关于Surface的监听器。提供访问和控制SurfaceView背后的Surface 相关的,它通过三个回调方法,让我们可以感知到Surface的创建、销毁或者改变

  3. 我们实现SurfaceHolder.Callback下面的三个函数

  4. 最后,我们就可以在Surface被创建后,通过我们前面提过的方法lockCanvas来获取到Canvas,从而在它上面作画了。

最后,我们来详细的展开说明一下SurfaceHolder中的几个重要函数:

  1. abstract Canvas lockCanvas()
    获取一个Canvas对象,并锁定。所得到的Canvas对象,其实就是Surface中一个成员。

  2. abstract Canvas lockCanvas(Rect dirty)
    同上。但只锁定dirty所指定的矩形区域,因此效率更高。

  3. abstract void unlockCanvasAndPost(Canvascanvas)
    当修改Surface中的数据完成后,释放同步锁,并提交改变。
    注意,只有在这个函数被调用后,对Canvas所做的修改才能真正被显示出来。

从显示效果来说,SurfaceView和普通View并无明显区别,但是响应速度有大幅度提升。

7 Animation

最后,我们来了解一下Android中动画的使用方法。

目前android动画有三大类:

  1. View Animation(Tween Animation):补间动画,给出两个关键帧,通过一些算法将给定属性值在给定的时间内在两个关键帧间渐变。
  2. Drawable Animation(Frame Animation):帧动画,就像GIF图片,通过一系列Drawable依次显示来模拟动画的效果
  3. Property Animation :属性动画,这个是在Android 3.0中才引进的

我们将在后面依次介绍这三种动画。

7.1 View Animation

View animation只能应用于View对象,而且只支持一部分属性,如支持缩放旋转而不支持背景颜色的改变。

而且对于View animation,它只是改变了View对象绘制的位置,而没有改变View对象本身。比如,你有一个Button,坐标(100,100),Width:200,Height:50,而你有一个动画使其变为Width:100,Height:100,你会发现动画过程中触发按钮点击的区域仍是(100,100)-(300,150)。

View Animation就是一系列View形状的变换,如大小的缩放,透明度的改变,位置的改变,动画的定义既可以用代码定义也可以用XML定义。

可以给一个View同时设置多个动画,比如从透明至不透明的淡入效果,与从小到大的放大效果,这些动画可以同时进行,也可以在一个完成之后开始另一个。

用XML定义的动画放在/res/anim/文件夹内。

动画的定义既可以用代码定义也可以用XML定义,因为后者更为灵活,因此建议用XML定义,我们在此也以学习XML定义的动画为主。

Paste_Image.png

XML文件的根元素可以为<alpha>,<scale>,<translate>, <rotate> 元素或<set>(表示以上几个动画的集合,如上面的第四个例子,set可以嵌套)。
默认情况下,所有动画是同时进行的,可以通过startOffset属性设置 各个动画的开始偏移(开始时间)来达到动画顺序播放的效果。

动画的播放逻辑很简单:

Animation an =  AnimationUtils.loadAnimation(this, R.anim.anim);
imageview.startAnimation(an);

Load到指定的动画配置文件,然后对需要播放动画的View使用startAnimation即可。

7.2 Drawable Animation

Drawable Animation(Frame Animation):帧动画,就像GIF图片,通过一系列Drawable依次显示来模拟动画的效果。在XML中的定义方式如下:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android" 
       android:oneshot="false">
       <item android:drawable="@drawable/fish001" android:duration="150"/>
       <item android:drawable="@drawable/fish002" android:duration="150"/>
       <item android:drawable="@drawable/fish003" android:duration="150"/>
       <item android:drawable="@drawable/fish004" android:duration="150"/>
</animation-list>

必须以<animation-list>为根元素,以<item>表示要轮换显示的图片,duration属性表示各项显示的时间。
代码中使用也很简单:

ImageView  imageview = (ImageView) findViewById(R.id.ImageView01);
imageview.setBackgroundResource(R.anim.animation);
AnimationDrawable  mframeAnimation = (AnimationDrawable) imageview.getBackground();
mframeAnimation.start();

将一个ImageView调用setBackgroundResource,将动画设置为他的Background,生成一个AnimationDrawable 对象,然后使用它的start方法即可播放。

7.3 Property Animation

在Property Animation中,改变的是对象的实际属性,如Button的缩放,Button的位置与大小属性值都改变了。而且Property Animation不止可以应用于View,还可以应用于任何对象。Property Animation只是表示一个值在一段时间内的改变,当值改变时要做什么事情完全是你自己决定的。

在Property Animation中,可以对动画应用以下属性:

  • Duration:动画的持续时间
  • TimeInterpolation:属性值的计算方式,如先快后慢
  • TypeEvaluator:根据属性的开始、结束值与TimeInterpolation计算出的因子计算出当前时间的属性值
  • Repeat Count and behavoir:重复次数与方式,如播放3次、5次、无限循环,可以此动画一直重复,或播放完时再反向播放
  • Animation sets:动画集合,即可以同时对一个对象应用几个动画,这些动画可以同时播放也可以对不同动画设置不同开始偏移
  • Frame refreash delay:多少时间刷新一次,即每隔多少时间计算一次属性值,默认为10ms,最终刷新时间还受系统进程调度与硬件的影响

一般可以设置的属性如下:

1)translationX 和 translationY:这两个属性控制了View所处的位置,它们的值是由layout容器设置的,是相对于坐标原点(0,0左上角)的一个偏移量。
2)rotation, rotationX 和 rotationY:控制View绕着轴点(pivotX和pivotY)旋转。
3)scaleX 和 scaleY:控制View基于pivotX和pivotY的缩放。
4)pivotX 和 pivotY:旋转的轴点和缩放的基准点,默认是View的中心点。
5)x 和 y:描述了view在其父容器中的最终位置,是左上角左标和偏移量(translationX,translationY)的和。
6)aplha:透明度,1是完全不透明,0是完全透明。

属性动画有多种设置方式,我们逐一说明.

7.3.1 XML定义

<set xmlns:android="http://schemas.android.com/apk/res/android"   
    android:ordering="together">  
    <objectAnimator  
        android:duration="2000"  
        android:propertyName="scaleX"  
        android:repeatCount="1"  
        android:repeatMode="reverse"  
        android:valueFrom="1.0"  
        android:valueTo="2.0" >  
    </objectAnimator>  
    <objectAnimator  
        android:duration="2000"  
        android:propertyName="scaleY"  
        android:repeatCount="1"  
        android:repeatMode="reverse"  
        android:valueFrom="1.0"  
        android:valueTo="2.0" >  
    </objectAnimator>  
</set>  

用一个set可以包裹多个objectAnimator ,可以使用android:ordering属性来指定这些动画是同时播放还是顺序播放.xml推荐放在res下的animator目录.

在XML中要利用objectAnimator来定义我们的某个属性的效果,所以:

1)propertyName 当然是必须的,理论上这个属性的值可以是对象中的任何有get/set方法的属性,不过我们这里是对View来说,所以一般而言,就是上面新加的那几种新属性了。
2)duration 持续时间
3)valueFrom 和 valueTo:这就是动画开始和结束时,这个属性相对应的值了,如果我们是缩放的话,当然就是倍数了,如果是平移(Translation)的话,那么就是距离了。
4)repeatCount 和 repeatMode:这两个和View Animation 中是一样的,就不多说了。

xml定义完之后在代码中使用即可:

        TextView textView = (TextView) findViewById(R.id.hello_text);
        final AnimatorSet objectAnimator = (AnimatorSet) AnimatorInflater
                .loadAnimator(this, R.animator.scalex);
        objectAnimator.setTarget(textView);
        textView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                objectAnimator.start();
            }
        });

调用AnimatorSet 的start就是开始播放动画了.

7.3.2 代码直接定义动画

7.3.2.1 直接创建AnimatorSet对象

        Button button = (Button) findViewById(R.id.button1);
        final AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(
                ObjectAnimator.ofFloat(button, "alpha", 1, 0, 1),
                ObjectAnimator.ofFloat(button, "translationX", 0f, 400f, 0f),
                ObjectAnimator.ofFloat(button, "rotation", 0, 180, 360));
        animatorSet.setDuration(1000);
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                animatorSet.start();
            }
        });

通过playTogether将三种动画同时播放,ofFloat必须写正确的属性名称.

7.3.2.2 使用PropertyValuesHolder对象创建

        Button button2 = (Button) findViewById(R.id.button2);
        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat(
                "translationX", 0f, 300f, 0f);
        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat(
                "translationY", 0f, 300f, 0f);
        final ValueAnimator translateAnimator = ObjectAnimator
                .ofPropertyValuesHolder(button2, pvhX, pvhY);
        translateAnimator.setDuration(2000);
               translateAnimator.setInterpolator(new BounceInterpolator());
        button2.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                translateAnimator.start();
            }
        });

上面除了设置动画,还有设置了插值器(TimeInterpolator),也就是如下一句:

 translateAnimator.setInterpolator(new BounceInterpolator());

插值器的意思就是对动画的播放进行干预,android提供了九个预置的插值器供我们使用:

1)AccelerateDecelerateInterpolator:先加速再减速。
2)AccelerateInterpolator:一直加速。
3)AnticipateInterpolator: 反向 ,先向相反方向改变一段再加速播放
4)AnticipateOvershootInterpolator: 反向加回弹,先向相反方向改变,再加速播放,会超出目的值然后缓慢移动至目的值
5)BounceInterpolator: 跳跃,快到目的值时值会跳跃,最后像个小球弹几下。
6)CycleInterpolator:循环,动画循环一定次数,值的改变为一正弦函数:Math.sin(2 * mCycles * Math.PI * input)
7)DecelerateInterpolator:一直减速。
8)LinearInterpolator:线性,这个就是我们上面讲到的很均匀的了。
9)OvershootInterpolator:到了终点之后,超过一点,再往回走。有个参数可以定义,超过的力度。

使用起来很方便,直接给ValueAnimator 实例设置setInterpolator即可.

7.3.2.3 使用ViewPropertyAnimator对象创建

        Button button3 = (Button) findViewById(R.id.button3);
        button3.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                v.animate().translationX(100f).alpha(0).setInterpolator(new CycleInterpolator((float) 0.5))
                        .setListener(new AnimatorListenerAdapter() {
                            @Override
                            public void onAnimationEnd(Animator animator) {
                                final Button button = (Button) findViewById(R.id.button3);
                                button.animate().alpha(1).translationX(0f).setInterpolator(new OvershootInterpolator())
                                        .start();
                            }
                        }).start();
            }
        });

先利用view的animate() 方法返回一个ViewPropertyAnimator对象,然后调用其translationX,alpha等方法.
上面也有一个额外的点,就是setListener方法来增加了一个AnimatorListenerAdapter.
AnimatorListenerAdapter可以实现诸如onAnimationStart onAnimationEnd onAnimationCancel等方法在特定的时间点来调用特定的代码.
诸如上面的代码,就是在动画播放之后,继续再播放一段动画.

7.4 一些额外的

7.4.1 ViewFilpper

在app的开发中,我们有时需要在不同的View间切换,实现幻灯片播放一样的效果,这里我们可以使用系统提供的ViewFilpper类。

ViewFilpper 是Android官方提供的一个View容器类,继承于ViewAnimator类,用于实现页面切换,也可以设定时间间隔,让它自动播放。
ViewFilpper的Layout里面可以放置多个View。

通过下面两个属性,可以设置ViewFilpper中View切换时的进入和退出时的动画。

android:inAnimation="@anim/push_top_in"
android:outAnimation="@anim/push_down_out“

在代码中,我们可以调用startFlipping()来开始自动播放,也可以通过showPrevious和showNext来控制播放前一张或后一张.


Paste_Image.png

7.4.2 overridePendingTransition

前面我们提到了View间切换可以使用ViewFilpper,而在两个 Activity 切换时,我们如何播放动画呢?

Android同样给我们提供了方便的办法来播放这种动画,使用overridePendingTransition()函数。

overridePendingTransition(R.anim.push_top_in, R.anim.push_down_out);

第一个参数是进入的动画,第二个参数是退出的动画。

注意这个函数必须在 StartActivity() 或 finish() 或者onPause()之后立即调用。

推荐阅读更多精彩内容