【Android】详解Android动画

目录结构:

contents structure [+]

在这篇文章中,笔者会详细介绍Android的动画。安卓中的动画大致分为tweened animation(补间动画)、frame-by-frame animation(逐帧动画)、Property Animation(属性动画),这个三个概念之间有点重合,下面介绍三种属性动画的产生时间顺序,其中补间动画和逐帧动画是Android1.0中被加入的,随着时间的推移简单的动画已经不能满足需求了,在Android4.0之后就加入了属性动画。本文还会介绍View、surfaceView和GLSurfaceView之间的比较。

1.补间动画

补间动画(tweened animation)都继承自android.view.animation.Animation抽象类,android.view.animation.Animation有五个直接实现子类:AlphaAnimation,AnimationSet,RotateAnimation,ScaleAnimation,TranslateAnimation。
其中AlphaAnimation,RotateAnimation,ScaleAnimation,TranslateAnimation是效果动画类,而AnimationSet是用于完成一系列组合动画的。

1.1 使用java代码实现Alpha、Rotate、Scale、Translate动画

下面这个栗子,演示了Alpha(淡入淡出)、Rotate(旋转)、Scale(缩放)、Translate(移动)的效果:
xml文件布局如下:

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal" >
         <Button
            android:id="@+id/rotateButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="旋转" />
         <Button
            android:id="@+id/scaleButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="缩放" />
         <Button
            android:id="@+id/alphaButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="淡入淡出" />
         <Button
            android:id="@+id/translateButton"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="移动" />
    </LinearLayout>
         <ImageView
             android:id="@+id/image"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_centerHorizontal="true"
             android:layout_centerVertical="true"
             android:src="@drawable/ic_launcher" />

java代码如下:

public class MainActivity extends Activity {
    private Button rotateButton = null;
    private Button scaleButton = null;
    private Button alphaButton = null;
    private Button translateButton = null;
    private ImageView image = null;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        rotateButton = (Button)findViewById(R.id.rotateButton);
        scaleButton = (Button)findViewById(R.id.scaleButton);
        alphaButton = (Button)findViewById(R.id.alphaButton);
        translateButton = (Button)findViewById(R.id.translateButton);
        image = (ImageView)findViewById(R.id.image);
        
        rotateButton.setOnClickListener(new RotateButtonListener());
        scaleButton.setOnClickListener(new ScaleButtonListener());
        alphaButton.setOnClickListener(new AlphaButtonListener());
        translateButton.setOnClickListener(new TranslateButtonListener());
    }
    class AlphaButtonListener implements OnClickListener{
        public void onClick(View v) {
            //创建一个AnimationSet对象,参数为Boolean型,
            //true表示使用Animation的interpolator,false则是使用自己的
            AnimationSet animationSet = new AnimationSet(true);
            //创建一个AlphaAnimation对象,参数从完全的透明度,到完全的不透明
            AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
            //设置动画执行的时间
            alphaAnimation.setDuration(2000);
            //将alphaAnimation对象添加到AnimationSet当中
            animationSet.addAnimation(alphaAnimation);
            //使用ImageView的startAnimation方法执行动画
            image.startAnimation(animationSet);
        }
     }
    class RotateButtonListener implements OnClickListener{
        public void onClick(View v) {
            AnimationSet animationSet = new AnimationSet(true);
            //参数1:从哪个旋转角度开始
            //参数2:转到什么角度
            //后4个参数用于设置围绕着旋转的圆的圆心在哪里
            //参数3:确定x轴坐标的类型,有ABSOLUT绝对坐标、RELATIVE_TO_SELF相对于自身坐标、RELATIVE_TO_PARENT相对于父控件的坐标
            //参数4:x轴的值,0.5f表明是以自身这个控件的一半长度为x轴
            //参数5:确定y轴坐标的类型
            //参数6:y轴的值,0.5f表明是以自身这个控件的一半长度为x轴
            RotateAnimation rotateAnimation = new RotateAnimation(0, 360,
                   Animation.RELATIVE_TO_SELF,0.5f,
                   Animation.RELATIVE_TO_SELF,0.5f);
            rotateAnimation.setDuration(2000);
            animationSet.addAnimation(rotateAnimation);
            image.startAnimation(animationSet);
        }
    }
    class ScaleButtonListener implements OnClickListener{
        public void onClick(View v) {
                AnimationSet animationSet = new AnimationSet(true);
                //0f从相对0点开始(若x和y的开始点都是0,那么就是从一个点开始),1f表示目前一个控件长度的位置(若x和y的结束点都是1f,那么就是缩放到原本大小。)
                //参数1:x轴的初始值
                //参数2:x轴收缩后的值
                //参数3:y轴的初始值
                //参数4:y轴收缩后的值
                //参数5:确定x轴坐标的类型
                //参数6:x轴的值,0.5f表明是以自身这个控件的一半长度为x轴
                //参数7:确定y轴坐标的类型
                //参数8:y轴的值,0.5f表明是以自身这个控件的一半长度为x轴
                ScaleAnimation scaleAnimation = new ScaleAnimation(
                       0f, 1f,0f,1f,
                       Animation.RELATIVE_TO_SELF,0.5f,
                       Animation.RELATIVE_TO_SELF,0.5f);
                scaleAnimation.setDuration(2000);
                animationSet.addAnimation(scaleAnimation);
                image.startAnimation(animationSet);
        }
    }
    class TranslateButtonListener implements OnClickListener{
        public void onClick(View v) {
                AnimationSet animationSet = new AnimationSet(true);
                //参数1~2:x轴的开始位置,0f代表从当前x轴点移动,1f代表以右移当前控件长度开始
                //参数3~4:y轴的开始位置,0f代表从当前y轴点移动,1f代表下移当前控件长度开始
                //参数5~6:x轴的结束位置
                //参数7~8:y轴的结束位置
                TranslateAnimation translateAnimation =
                   new TranslateAnimation(
                       Animation.RELATIVE_TO_SELF,0f,
                       Animation.RELATIVE_TO_SELF,0f,
                       Animation.RELATIVE_TO_SELF,0f,
                       Animation.RELATIVE_TO_SELF,2f);
                translateAnimation.setDuration(2000);
                animationSet.addAnimation(translateAnimation);
                image.startAnimation(animationSet);
       }
   }
}

效果图如下:

1.2 通过xml文件实现Alpha、Rotate、Scale、Translate动画

上面是在java代码中使用的使用Animation,这样的方式方便调试、运行,但是代码的重用性却不好,下面通过xml来实现Animation。

1.2.1 步骤

1) 在res文件夹下面建立anim文件夹
2) 创建xml文件,并加入set标签
3) 向set标签中加入rotate,alpha,scale或者translate标签
4) 使用AnimationUtils类加载xml文件

1.2.2 xml实现Animation案例

建立如下图的文件格式

alpha.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator">
    <!-- fromAlpha和toAlpha是起始透明度和结束时透明度 -->
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:duration="2000"/>
</set>
alpha.xml

rotate.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator">
    <!--
        fromDegrees:开始的角度
        toDegrees:结束的角度,+表示是正的
        pivotX:用于设置旋转时的x轴坐标
        例
           1)当值为"50",表示使用绝对位置定位
           2)当值为"50%",表示使用相对于控件本身定位
           3)当值为"50%p",表示使用相对于控件的父控件定位
        pivotY:用于设置旋转时的y轴坐标
      -->
    <rotate
        android:fromDegrees="0"
        android:toDegrees="+360"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="2000"/>
</set>
rotate.xml

scale.xml文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator">
   <!--
       起始x轴坐标
           止x轴坐标
           始y轴坐标
           止y轴坐标
           轴的坐标
           轴的坐标
     -->
   <scale
       android:fromXScale="1.0"
       android:toXScale="0.0"
       android:fromYScale="1.0"
       android:toYScale="0.0"
       android:pivotX="50%"
       android:pivotY="50%"
       android:duration="1000"/>
</set>
scale.xml

translate.xml文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator">
    <!--
           始x轴坐标
           止x轴坐标
           始y轴坐标
           止y轴坐标
      -->
    <translate
        android:fromXDelta="0%"
        android:toXDelta="100%"
        android:fromYDelta="0%"
        android:toYDelta="100%"
        android:duration="2000"/>
</set>
translate.xml

java调用代码如下:

public class MainActivity extends Activity {
    private Button rotateButton = null;
    private Button scaleButton = null;
    private Button alphaButton = null;
    private Button translateButton = null;
    private ImageView image = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Interpolator t;
        rotateButton = (Button) findViewById(R.id.rotateButton);
        scaleButton = (Button) findViewById(R.id.scaleButton);
        alphaButton = (Button) findViewById(R.id.alphaButton);
        translateButton = (Button) findViewById(R.id.translateButton);
        image = (ImageView) findViewById(R.id.image);

        rotateButton.setOnClickListener(new RotateButtonListener());
        scaleButton.setOnClickListener(new ScaleButtonListener());
        alphaButton.setOnClickListener(new AlphaButtonListener());
        translateButton.setOnClickListener(new TranslateButtonListener());
    }

    class AlphaButtonListener implements OnClickListener {
        public void onClick(View v) {
            // 使用AnimationUtils装载动画配置文件
            Animation animation = AnimationUtils.loadAnimation(
                    MainActivity.this, R.anim.alpha);
            // 启动动画
            image.startAnimation(animation);
        }
    }

    class RotateButtonListener implements OnClickListener {
        public void onClick(View v) {
            Animation animation = AnimationUtils.loadAnimation(
                    MainActivity.this, R.anim.rotate);
            image.startAnimation(animation);
        }
    }

    class ScaleButtonListener implements OnClickListener {
        public void onClick(View v) {
            Animation animation = AnimationUtils.loadAnimation(
                    MainActivity.this, R.anim.scale);
            image.startAnimation(animation);
        }
    }

    class TranslateButtonListener implements OnClickListener {
        public void onClick(View v) {
            Animation animation = AnimationUtils.loadAnimation(
                    MainActivity.this, R.anim.translate);
            image.startAnimation(animation);
        }
    }
}
MainActivity.java

1.3 动画叠加

在上面我们介绍了AlphaAnimation,RotateAnimation,ScaleAnimation,TranslateAnimation,但这些只能是单独表现的动画。如果想把这些动画融合到一起,那么应该使用AnimationSet类,AnimationSet是Animation的派生类,它主要用于将多个动画效果融合在一起。

融合的代码如下:

       //定义AnimationSet对象
        AnimationSet animationSet = new AnimationSet(true);
        //定义淡入淡出动画
        AlphaAnimation alphaAnimation = new AlphaAnimation(1, 0);
        //定义旋转动画
        RotateAnimation rotateAnimation = new RotateAnimation(0, 360,
               Animation.RELATIVE_TO_SELF,0.5f,
               Animation.RELATIVE_TO_SELF,0.5f);
        rotateAnimation.setDuration(1000);//设置旋转动画的结束时间
        //将rotateAnimation添加到animationSet中
        animationSet.addAnimation(rotateAnimation);
        //将alphaAnimation添加到animationSet中
        animationSet.addAnimation(alphaAnimation);
        //开始动画
        image.startAnimation(animationSet);

如果想通过配置xml文件的方式来实现的话,只需要在<set></set>中多定义一组动画即可

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true">
    <!-- fromAlpha和toAlpha是起始透明度和结束时透明度 -->
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="0.0"
        android:startOffset="500"
        android:duration="500"/>
    <translate
        android:fromXDelta="0%"
        android:toXDelta="100%"
        android:fromYDelta="0%"
        android:toYDelta="100%"
        android:duration="2000"/>
</set>

1.4 动画速率

上面我们使用的所有动画速率都是默认的,
Interpolator定义了动画变化的速率,在Animations框架当中定义了一下几种Interpolator

AccelerateDecelerateInterpolator:在动画开始与结束的地方速率改变比较慢,在中间的时候速率快。
AccelerateInterpolator:在动画开始的地方速率改变比较慢,然后开始加速
CycleInterpolator:动画循环播放特定的次数,速率改变沿着正弦曲线
DecelerateInterpolator:在动画开始的地方速率改变比较慢,然后开始减速
LinearInterpolator:动画以均匀的速率改变

Interpolator在xml文件中的使用主要分为以下几种情况:

a)在set标签中

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator"/>

b)如果在一个set标签中包含多个动画效果,如果想让这些动画效果共享一个Interpolator。

 android:shareInterpolator="true"

c)如果不想共享一个interpolator,则设置android:shareInterpolator="true",并且需要在每一个动画效果处添加interpolator。

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

Interpolator在java代码中的使用,又可以分为以下几种情况:


a)如果是在代码上设置共享一个interpolator,则可以在AnimationSet设置interpolator。

AnimationSet animationSet = new AnimationSet(true);
animationSet.setInterpolator(new AccelerateInterpolator());

b)如果不设置共享一个interpolator则可以在每一个Animation对象上面设置interpolator。

//false不使用默认的AnimationSet
AnimationSet animationSet = new AnimationSet(false);
alphaAnimation.setInterpolator(new AccelerateInterpolator());
rotateAnimation.setInterpolator(new DecelerateInterpolator());

2.逐帧动画

Frame-By-Frame Animations(逐帧动画)是一帧一帧的格式显示动画效果。类似于电影胶片拍摄的手法。
逐帧动画依靠AnimationDrawable类,AnimationDrawable对ImageView进行动画时,原来的ImageView中是不能设置初始图片的。

2.1 实现小熊快跑动画效果

下面使用小熊快跑这个动画来讲解AnimationDrawable的使用

bear.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/littlebear1" android:duration="50"/>
    <item android:drawable="@drawable/littlebear2" android:duration="50"/>
    <item android:drawable="@drawable/littlebear3" android:duration="50"/>
    <item android:drawable="@drawable/littlebear4" android:duration="50"/>
    <item android:drawable="@drawable/littlebear5" android:duration="50"/>
    <item android:drawable="@drawable/littlebear6" android:duration="50"/>
    <item android:drawable="@drawable/littlebear7" android:duration="50"/>
</animation-list>
bear.xml

activity_main.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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >
    <Button
           android:id="@+id/startbutton"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:text="开始"/>
           <Button
              android:id="@+id/endbutton"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_toEndOf="@+id/startbutton"
              android:text="停止" />
          <ImageView
           android:id="@+id/image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:layout_centerVertical="true"/>
</RelativeLayout>
activity_main.xml

java代码:

public class MainActivity extends Activity {

    Button startbutton=null;
    Button endbutton=null;
    ImageView imageView=null;
    AnimationDrawable animationDrawable=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        startbutton = (Button)findViewById(R.id.startbutton);
        imageView = (ImageView)findViewById(R.id.image);
        startbutton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                   imageView.setBackgroundResource(R.anim.bear);
                   animationDrawable = (AnimationDrawable)
                      imageView.getBackground();
                   animationDrawable.start();
            }
        });
        endbutton=(Button)findViewById(R.id.endbutton);
        endbutton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View paramView) {
                if(animationDrawable!=null){
                    animationDrawable.stop();
                }
            }
        });
    }
}
MainActivity.java

效果图:

2.2 Movie 类的使用(GIF动图)

说起逐帧动画,肯定大家会想到的就是GIF动图,上面我们使用AnimationDrawable来绘制逐帧动画,但这样未免太过于麻烦,在Android API中还提供了另外一个类就是android.graphics.Movie。接下来看看这个案例:

customGifView文件

import java.io.InputStream;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Movie;
import android.util.AttributeSet;
import android.view.View;

public class CustomGifView extends View {

 private InputStream gifInputStream;
 private Movie gifMovie;
 private int movieWidth, movieHeight;
 private long movieDuration;
 private long mMovieStart;

 public CustomGifView(Context context) {
  super(context);
  init(context);
 }

 public CustomGifView(Context context, AttributeSet attrs) {
  super(context, attrs);
  init(context);
 }

 public CustomGifView(Context context, AttributeSet attrs, 
   int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  init(context);
 }

 private void init(Context context){
  setFocusable(true);
  gifInputStream = context.getResources()
    .openRawResource(R.drawable.YOUR_GIF);
  /**
   * Movie 提供了三个decodeXXX方法,分别是
   * decodeByteArray(byte[] data, int offset, int length)
   * decodeFile(String pathName)
   * decodeStream(InputStream is)
   */
  gifMovie = Movie.decodeStream(gifInputStream);
  movieWidth = gifMovie.width();
  movieHeight = gifMovie.height();
  //持续获得持续时间
  movieDuration = gifMovie.duration();
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, 
   int heightMeasureSpec) {
  setMeasuredDimension(movieWidth, movieHeight);
 }

 public int getMovieWidth(){
  return movieWidth;
 }

 public int getMovieHeight(){
  return movieHeight;
 }

 public long getMovieDuration(){
  return movieDuration;
 }

 @Override
 protected void onDraw(Canvas canvas) {

        long now = android.os.SystemClock.uptimeMillis();
        if (mMovieStart == 0) {   // first time
            mMovieStart = now;
        }
        
        if (gifMovie != null) {
            int dur = gifMovie.duration();
            if (dur == 0) {
                dur = 1000;
            }
            int relTime = (int)((now - mMovieStart) % dur);//设置要被显示的帧
            gifMovie.setTime(relTime);
            gifMovie.draw(canvas, 0, 0);
            invalidate();
        }
     }
 }
CustomGifView.java

可以在XML中使用:

<Your_PackageName.CustomGifView
        android:id="@+id/gifview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

修改硬件加速关闭:

android:hardwareAccelerated="false"

View.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

然后就可以看到GIF动图了。

3.LayoutAnimationController

LayoutAnimationController用于对layout中或View Group中的子元素进行动画的,每个子元素使用的都是相同的动画,但是每个子元素的启动时间不一样。如果想要定义自己的延迟启动时间,那么可以重写LayoutAnimationController类的getDelayForView(android.view.View)方法。


下面是使用对ListView使用LayoutAnimationController的案例,通过这个案例我们来讲解一下LayoutAnimationController是如何工作的:

list_anim.xml文件

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:shareInterpolator="true">
    <alpha
       android:fromAlpha="0.0"
       android:toAlpha="1.0"
       android:duration="2000"/>
</set>
list_anim.xml

list_anim_layout.xml文件

<?xml version="1.0" encoding="utf-8"?>

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

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

<LinearLayout
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <ListView
       android:id="@+id/list"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:scrollbars="vertical"
        android:layoutAnimation="@anim/list_anim_layout"
        />
       <Button
        android:id="@+id/button"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="测试"/>
</LinearLayout>
</RelativeLayout>
activity_main.xml

activity_item.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal"
    android:paddingLeft="10dip"
    android:paddingRight="10dip"
    android:paddingTop="1dip"
    android:paddingBottom="1dip">
    <TextView android:id="@+id/name"
       android:layout_width="180dip"
       android:layout_height="30dip"
       android:textSize="5pt"
       android:singleLine="true" />
    <TextView android:id="@+id/sex"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent"
       android:textSize="5pt"
       android:singleLine="true"/>
</LinearLayout>
activity_item.xml

mainActivity.java文件

public class MainActivity extends Activity {
    private Button button = null;
    private ListView listView = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listView = (ListView)findViewById(R.id.list);
        button=(Button)findViewById(R.id.button);
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                   listView.setAdapter(createListAdapter());
            }
        });
    }
    private ListAdapter createListAdapter() {
        List<HashMap<String,String>> list =
            new ArrayList<HashMap<String,String>>();
        HashMap<String,String> m1 = new HashMap<String,String>();
        m1.put("name", "bauble");
        m1.put("sex", "male");
        HashMap<String,String> m2 = new HashMap<String,String>();
        m2.put("name", "Allorry");
        m2.put("sex", "male");
        HashMap<String,String> m3 = new HashMap<String,String>();
        m3.put("name", "Allotory");
        m3.put("sex", "male");
        HashMap<String,String> m4 = new HashMap<String,String>();
        m4.put("name", "boolbe");
        m4.put("sex", "male");
        list.add(m1);
        list.add(m2);
        list.add(m3);
        list.add(m4);
        SimpleAdapter simpleAdapter = new SimpleAdapter(
               this,list,R.layout.activty_item,new String[]{"name","sex"},
               new int[]{R.id.name,R.id.sex});
        return simpleAdapter;
     }
}
mainActivity.java

效果图:

4.属性动画

4.1 基本简介

属性动画是在Android3.0之后加入的,在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。


ValueAnimator是属性动画机制中最核心的一个类,除了ValueAnimator类,AnimatorSet、ObjectAnimator类也是属性动画机制中的核心类,其中ObjectAnimator是ValueAnimator的派生类。下面笔者会详细介绍这个三个类。

4.2 ValueAnimator类

现在开始介绍ValueAnimator类的使用,ValueAnimator提供了一种简单的定时引擎,它可以计算动画的属性值,然后通过手动将变化的值设置到对象属性上。默认情况下,ValueAnimator使用的是非线性的Interpolation(AccelerateDecelerateInterpolator),AccelerateDecelerateInterpolator类对象的Interpolator在开始的时候加速,在动画结束的时候减速。也可以通过setInterpolator(TimeInterpolator)方法来设置自己的Interpolator。


valueAnimator有如下几个比较常用的方法:

//将开始值以浮点数值的形式过度到结束值
public static ValueAnimator ofFloat (float... values)
//将开始值以整数的形式过度到结束值
public static ValueAnimator ofInt (int... values)
//使用指定的TypeEvaluator对象,将 开始值以对象的形式过度到结束值。
public static ValueAnimator ofObject (TypeEvaluator evaluator, Object... values)
//开始动画
public void start();
//结束动画
public void end();
//取消动画
public void cancle();
//添加更新监听器
public void addUpdateListener (ValueAnimator.AnimatorUpdateListener listener)
//设置自定义的TimerInterpolator
public void setInterpolator (TimeInterpolator value)
//设置执行时间
public ValueAnimator setDuration (long duration)
//设置启动的延迟时间
public void setStartDelay (long startDelay)

下面通过改变按钮的宽度来展示ValueAnimator的用法,

xml布局如下:

    <Button
        android:id="@+id/button1"
        style="?android:attr/buttonStyleSmall"
        android:layout_width="200px"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="属性动画" />

java代码如下:

public class MainActivity extends Activity {
    Button button=null;
    ValueAnimator valueAnimator=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        button=(Button)findViewById(R.id.button1);
        //对指定Button对象的with属性创建ValueAnimator对象
        valueAnimator = ValueAnimator.ofInt(button.getLayoutParams().width, 500);
        //设置持续时间
        valueAnimator.setDuration(2000);
        //添加AnimatorUpdateListener监听器,每当属性值改变就会调用onAnimationUpdate方法
        valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animator) {
                //获得变化的值
                int currentValue = (Integer) animator.getAnimatedValue();
                Log.i("info", currentValue+"");
                //重新设置属性值
                button.getLayoutParams().width=currentValue;
                //重新显示
                button.requestLayout();
            }
        });
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View paramView) {
                valueAnimator.start();
            }
        });
    }
}

效果图:


观察上面的代码我们知道,ValueAnimator只是负责值的改变,若想要把改变后的值重新赋给对象,那么这个过程应该由程序员应该手动来完成。幸好官方提供了ObjectAnimator类,来帮我们实现这个过程。

4.3 ObjectAnimator类

ObjectAnimator是ValueAnimator的派生类,它对ValueAnimator进行了改进,它可以直接对对象的属性进行动画设置,但是被动画的属性必须要提供set/get方法。
由于ObjectAnimator派生自ValueAnimator,ObjectAnimator能使用ValueAnimator中的大部分方法(除private外)。

下面这个案例展示了利用ObjectAnimator来实现旋转、平移、缩放、淡入淡出

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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:id="@+id/move"
            android:text="平移"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"/>
        <Button
            android:id="@+id/rotate"
            android:text="旋转"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/scale"
            android:text="缩放"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <Button
            android:id="@+id/alpha"
            android:text="淡入淡出"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher" />
</RelativeLayout>

java的调用代码如下:

public class MainActivity extends Activity {
    Button move=null;
    Button rotate=null;
    Button scale=null;
    Button alpha=null;
    ImageView image=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        move= (Button)findViewById(R.id.move);
        rotate=(Button)findViewById(R.id.rotate);
        scale=(Button)findViewById(R.id.scale);
        alpha=(Button)findViewById(R.id.alpha);
        
        image=(ImageView)findViewById(R.id.image);
        
        move.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(image,"TranslationX",0,100,0);//移动
                objectAnimator.setDuration(2000);
                objectAnimator.start();
            }
        });
        
        rotate.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(image,"Rotation",0,360);//旋转
                objectAnimator.setDuration(2000);
                objectAnimator.start();
            }
        });
        
        scale.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(image,"ScaleX",1,2,1);//缩放
                objectAnimator.setDuration(2000);
                objectAnimator.start();
            }
        });
        
        alpha.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(image,"Alpha",1,0,1);//淡入淡出
                objectAnimator.setDuration(2000);
                objectAnimator.start();                
            }
        });
    }
}

效果图:


下面介绍一些常用的属性的名:

属性 作用 数值类型
Alpha 控制View的透明度 float
TranslationX 控制X方向的位移 float
TranslationY 控制Y方向的位移 float
ScaleX 控制X方向的缩放倍数 float
ScaleY 控制Y方向的缩放倍数 float
Rotation 控制以屏幕方向为轴的旋转度数 float
RotationX 控制以x轴为轴的旋转度数 float
RotationY 控制以Y轴为轴的旋转度数 float

4.4 AnimatorSet类

AnimatorSet类可以实现Animation的组合动画,其中的Animation可以按照指定的顺序或交叉方式进行显示。
该类有一些常用方法:

AnimatorSet.play(Animator anim)   :播放当前动画
AnimatorSet.after(long delay)     :将现有动画延迟x毫秒后执行
AnimatorSet.with(Animator anim)   :将现有动画和传入的动画同时执行
AnimatorSet.after(Animator anim)  :将现有动画插入到传入的动画之后执行
AnimatorSet.before(Animator anim) :将现有动画插入到传入的动画之前执行

下面是一个混合旋转和移动动画案例:
xml布局文件:

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始"
        android:id="@+id/start"/>
    <ImageView
        android:id="@+id/image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:src="@drawable/ic_launcher" />

java代码:

public class MainActivity extends Activity {
    ImageView image=null;
    Button start=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        image=(ImageView)findViewById(R.id.image);
        start=(Button)findViewById(R.id.start);

        start.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator transX=ObjectAnimator.ofFloat(image, "TranslationX", 0,100,0);
                ObjectAnimator transY=ObjectAnimator.ofFloat(image, "TranslationY", 0,100,0);

                ObjectAnimator rotate=ObjectAnimator.ofFloat(image, "Rotation", 0,360);

                AnimatorSet animatorSet=new AnimatorSet();
                Builder builder= animatorSet.play(transX).with(transY);//移动
                builder.before(rotate);//旋转
                animatorSet.setDuration(2000);
                //启动
                animatorSet.start();
            }
        });

    }
}

效果图:


4.5 估值器(TypeEvaluator)

接下来我们继续讲解估值器,估值器(TypeEvaluator)是一个接口,该接口允许开发人员自定义动画中的属性值。

TypeEvaluator有一些已知的实现类,例如ArgbEvaluator、FloatEvaluator、IntEvaluator、RectEvaluator、PointEvaluator等等。其中Argb是在用于计算Argb之间的值时使用的,RectEvaluator是在用于计算Rect之间的值时使用的,FloatEvaluator是在用于计算Float之间值使用的,IntEvaluator是在用于计算Int之间值时使用的。

TypeEvaluator接口只有一个抽象方法:

public abstract T evaluate (float fraction, T startValue, T endValue);

TypeEvaluator只有一个抽象方法evaluate。evaluate中包含三个形参,其中startValue形参表示动画的结束值,endValue形参表示动画的结束值。由于startValue,endValue以及方法的返回值都是泛型类型,所以我们可以在TypeEvaluator的实现类中为泛型指定任何类型,比如Integer,Double,或者自定义的类型,显然该类型必须和属性动画中的动画属性保持一致。

evaluate方法中有一个形参比较特殊,它就是fraction,fraction形参是float类型。fraction表示单个动画的完成比例(重复动画可以认为是单个动画的多次运行),它的值是[0~1],当fraction = 0时表示单个动画还未开始,fraction = 1表示单个动画的已经完成。当我们使用属性动画类(ValueAnimator或ObjectAnimator类)运行动画时,属性动画类会根据当前动画的运行时间(Elapsed time)和当前动画的运行速率(Interpolator),得出动画的完成比例,在调用我们的TypeEvaluator的实现类时把它传给fraction参数。因此在我们使用fraction参数计算时,无需额外考虑运行速率。

有了这些概念后,我们来自定义一个CircleEvaluator:

Circle.java文件

package com.bean;

public class Circle {
    /**
     * 圆心的横坐标
     */
    private float x;
    /**
     * 圆心的纵坐标
     */
    private float y;
    /**
     * 圆的半径
     */
    private float radius;

    public Circle(float x,float y,float radius)
    {
        this.x=x;
        this.y=y;
        this.radius=radius;
    }
    public float getX() {
        return x;
    }
    public void setX(float x) {
        this.x = x;
    }
    public float getY() {
        return y;
    }
    public void setY(float y) {
        this.y = y;
    }
    public float getRadius() {
        return radius;
    }
    public void setRadius(float radius) {
        this.radius = radius;
    }
}
Circle.java

CircleEvaluator.java 文件

package com.evaluator;

import com.bean.Circle;

import android.animation.TypeEvaluator;

public class CircleEvaluator implements TypeEvaluator<Circle>{
    @Override
    public Circle evaluate(float fraction, Circle startValue, Circle endValue) {
        //圆心的横坐标
        float x=startValue.getX()+fraction*(endValue.getX()-startValue.getX());
        //圆心的纵坐标
        float y=startValue.getY()+fraction*(endValue.getY()-startValue.getY());
        //圆的半径
        float radius=startValue.getRadius()+fraction*(endValue.getRadius()-startValue.getRadius());
        return new Circle(x, y, radius);
    }
}
CircleEvaluator.java

CircleView.java 文件

package com.entry;

import com.bean.Circle;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.view.View;

public class CircleView extends View {
    private Circle circle=null;
    public CircleView(Context context,Circle circle) {
        super(context);
        this.circle=circle;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        Paint paint=new Paint();
        paint.setStyle(Style.STROKE);
        canvas.drawCircle(circle.getX(), circle.getY(), circle.getRadius(),paint);
        super.onDraw(canvas);
    }
    public Circle getCircle() {
        return circle;
    }
    public void setCircle(Circle circle) {
        this.circle = circle;
    }
}
CircleView.java

MainActivity.java 文件

package com.entry;

import com.bean.Circle;
import com.evaluator.CircleEvaluator;

import android.os.Bundle;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.RelativeLayout;

public class MainActivity extends Activity {
    RelativeLayout layout=null;
    Button button=null;
    CircleView circleView=null;
    Circle startCircle=null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startCircle=new Circle(200,200,60);
        circleView=new CircleView(this,startCircle);

        layout=(RelativeLayout)findViewById(R.id.main);
        layout.addView(circleView);

        button=(Button)findViewById(R.id.button1);
        button.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                CircleEvaluator circleEvaluator= new CircleEvaluator();
                Circle endCircle=new Circle(250, 300, 100);
                ObjectAnimator objectAnimator=ObjectAnimator.ofObject(circleView,"circle",circleEvaluator,startCircle,endCircle);
                objectAnimator.setDuration(2000);//两秒内完成
                objectAnimator.addUpdateListener(new AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        circleView.invalidate();
                    }
                });
                objectAnimator.start();
            }
        });
    }
}
MainActivity.java

效果图:

4.6 实现贝塞尔曲线动画

在开始之前,介绍一下Path类,Path类中提供了一个quadTo(float x1, float y1, float x2, float y2)方法,该方法可以用于绘制二阶贝塞尔曲线。

笔者介绍三个贝塞尔曲线的网站:The Bezier GameCanvas Bezier Curve ExampleBezier Curve

贝塞尔曲线的应用非常广泛,栗如:

QQ小红点拖拽效果
360火箭发射
加入购物车动画
一些炫酷的下拉刷新控件
阅读软件的翻书效果
一些平滑的折线图的制作
很多炫酷的动画效果

这里对贝塞尔曲线不做过多的解释,关于贝塞尔曲线读者可以自行度娘。


下面介绍实现贝塞尔曲线动画的思想:
a)自定义估值器(TypeEvaluator),在public abstract T evaluate (float fraction, T startValue, T endValue);方法中利用贝塞尔公式计算出下一个图形的属性的值。
b)对组件动画对象设置新的值,如果是ObjectValuator的话,这一步可以省略,在上面的分析中已经知道ObjectValuator会自动帮我们完成这一步。
c)调用invalidate()重新刷新组件。

接下来我们要利用贝塞尔曲线,实现如下这样的功能:


结构图:

Heart.java 文件

package heart.model;

public class Heart {
    /**
     * 横坐标
     */
    private float x=0;
    /**
     * 纵坐标
     */
    private float y=0;
    /**
     * 颜色值
     */
    private int color=0;
    public Heart(float x,float y,int color){
        this.x=x;
        this.y=y;
        this.color=color;
    }
    public int getColor() {
        return color;
    }
    public void setColor(int color) {
        this.color = color;
    }
    public float getX() {
        return x;
    }
    public void setX(float x) {
        this.x = x;
    }
    public float getY() {
        return y;
    }
    public void setY(float y) {
        this.y = y;
    }
}
Heart.java

HeartView.java 文件

package heart.model;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.view.View;

public class HeartView extends View{
    private Heart heart=null;
    
    public float getAlpha() {
        return super.getAlpha();
    }
    public void setAlpha(float alpha) {
        super.setAlpha(alpha);
    }
    public Heart getHeart() {
        return heart;
    }
    public void setHeart(Heart heart) {
        this.heart = heart;
    }
    
    private final float h=40;
    
    public HeartView(Context context,Heart heart){
        super(context);
        this.heart=heart;
    }
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        
        Paint p=new Paint();
        //设置透明度
        p.setAlpha((int) getAlpha());
        //设置样式
        p.setStyle(Style.FILL);
        //设置画笔颜色
        p.setColor(heart.getColor());
        
        //使用二阶贝塞尔曲线绘制心形图案
        Path path=new Path();
        float x=heart.getX();//获得起始点的横坐标
        float y=heart.getY();//获得起始点的纵坐标
        path.moveTo(x,y);
        path.quadTo(x+h,y-0.268f*h,x,y+h);
        path.moveTo(x,y+h);
        path.quadTo(x-h,y-0.268f*h,x,y);
        
        canvas.drawPath(path,p);
    }
}
HeartView.java

HeartEvaluator.java 文件

package heart.model;

import java.util.Random;

import android.animation.TypeEvaluator;

public class HeartEvaluator implements TypeEvaluator<Heart>{
    private int orientation=1;
    private static Random r=new Random(System.currentTimeMillis());
    
    public HeartEvaluator(){
        orientation=getOrientation();
    }
    /**
     * 利用三阶贝塞尔曲线公式,得出位置。
     */
    @Override
    public Heart evaluate(float t, Heart startValue, Heart endValue) {
        Heart[] points=createPoints(startValue,endValue);
        Heart p0=points[0];//开始点
        Heart p1=points[1];//第一个辅佐点
        Heart p2=points[2];//第二个辅佐点
        Heart p3=points[3];//终点
        //使用三阶贝塞尔曲线算出横坐标
        Double dx= p0.getX()*Math.pow((1-t),3)+
                3*p1.getX()*t*Math.pow((1-t), 2)+
                3*p2.getX()*Math.pow(t, 2)*(1-t)+
                p3.getX()*Math.pow(t, 3);
        float x=Float.parseFloat(dx.toString());
        //使用三阶贝塞尔曲线算出纵坐标
        Double dy= p0.getY()*Math.pow((1-t),3)+
                3*p1.getY()*t*Math.pow((1-t), 2)+
                3*p2.getY()*Math.pow(t, 2)*(1-t)+
                p3.getY()*Math.pow(t, 3);
        float y=Float.parseFloat(dy.toString());
        
        return new Heart(x,y,startValue.getColor());
    }
    /**
     * 根据起始点和终点 算出其余两个辅佐点,
     * 算法自定义
     * @param heart0 开始点
     * @param heart3 终点
     * @return
     */
    public Heart[] createPoints(Heart heart0,Heart heart3){
        float wx=Math.abs(heart0.getX()-heart3.getX());
        Heart heart1=new Heart(heart3.getX(), heart0.getY()-wx, heart0.getColor());
        Heart heart2=new Heart(heart0.getX(),wx,heart3.getColor());
        return new Heart[]{heart0,heart1,heart2,heart3};
    }
    /**
     * 获得飘动的方向
     * @return 一个Int类型的数据,数字为1或是-1。
     */
    private int getOrientation(){
        if(r.nextFloat()>=0.5){
            return 1;
        }
        return -1;
    }
}
HeartEvaluator.java

在这个HeartEvaluator估值器中,我们使用三阶贝塞尔曲线公式,实现单个图形按如下路径移动:

三阶贝塞尔曲线的公式为:

HeartAnimation.java 文件

package heart.model;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;

import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.os.Message;
import android.view.ViewGroup;

public class HeartAnimation {
    private int width;
    private int height;
    private List<HeartView> heartViews=null;
    private static Random random=new Random();
    private ViewGroup parent=null;
    private Context context=null;
    private static final int COLOR_LIMIT=4;
    /**
     * 有参数构造器
     * @param width 宽度
     * @param height 高度
     * @param parent 容器组件
     * @param context 上下文对象
     */
    public HeartAnimation(int width,int height,ViewGroup parent,Context context){
        this.width=width;
        this.height=height;
        this.parent=parent;
        this.context=context;
        heartViews=new ArrayList<HeartView>();
    }
    /**
     * 设置需要显示图形的个数
     * @param count 图形的个数
     * @return 返回一个HeartAnimation类型的数据,当前对象。
     */
    public HeartAnimation setCount(int count){
        if(heartViews!=null)
            heartViews.clear();
        
        for(int i=0;i<count;i++){
            HeartView heartView=new HeartView(context, new Heart(width/2-25, height-100,getColor()));
            heartViews.add(heartView);
            addAnimation(heartView);//添加动画
        }
        return this;
    }
    
    public HeartAnimation addAnimation(final HeartView heartView){
        //创建一个估值器
        HeartEvaluator heartEvaluator=new HeartEvaluator();
        
        //获得起点的图形
        Heart startHeart=heartView.getHeart();
        //获得终点的图形
        Heart endHeart=new Heart(getXPosition(),0,startHeart.getColor());
        
        ValueAnimator animator=ValueAnimator.ofObject(heartEvaluator, startHeart,endHeart);
        ObjectAnimator alphaAnimator=ObjectAnimator.ofFloat(heartView, "alpha", 1,0);
        
        animator.addUpdateListener(new AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获得改变后的值
                heartView.setHeart((Heart)animation.getAnimatedValue());
                //重新设置新值
                heartView.invalidate();
            }
        });
        
        animator.addListener(new AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
            }
            @Override
            public void onAnimationRepeat(Animator animation) {
            }
            @Override
            public void onAnimationEnd(Animator animation) {
                parent.removeView(heartView);//移除
                
                HeartView fHeartView= new HeartView(context, new Heart(width/2-25, height-100,getColor()));
                addAnimation(fHeartView);//重新开始动画
                parent.addView(fHeartView);//将该新图形添加到容器中
            }
            @Override
            public void onAnimationCancel(Animator animation) {
            }
        });
        
        AnimatorSet animatorSet=new AnimatorSet();
        animatorSet.play(animator).with(alphaAnimator);
        
        animatorSet.setDuration(getDuration());
        animatorSet.setStartDelay(getStartDelay());
        //开始
        animatorSet.start();
        return this;
    }
    /**
     * 开始
     */
    public void start(){
        for(HeartView heartView : heartViews){
            parent.addView(heartView);//将心图形对象依次添加到容器中
        }
    }
    /**
     * 终点横坐标的随机数
     * @return 终点的横坐标
     */
    private int getXPosition(){
        return random.nextInt(width);
    }
    /**
     * @return 一个long类型的整数,表示延迟的毫秒数。
     */
    public static long getStartDelay(){
        return (random.nextInt(5)+1)*1000;
    }
    /**
     * @return 一个long类型的数据,表示持续毫秒时间,返回值在[3000,5000)之间;
     */
    public long getDuration(){
        return (random.nextInt(2)+3)*1000;
    }
    /**
     * @return 一个int类型的数据,表示颜色值。
     */
    public int getColor(){
        int colorType= random.nextInt(COLOR_LIMIT);
        int color=0;
        switch (colorType) {
        case 0:
            color=Color.RED;
            break;
        case 1:
            color=Color.BLUE;
            break;
        case 2:
            color=Color.YELLOW;
            break;
        default:
            color=Color.GREEN;
            break;
        }
        return color;
    }
}
HeartAnimation.java

笔者在addAnimation方法中,同时播放alpha和Heart的动画,然后监听addUpdateListener,在有新值后,重新设置新值。

Mainctivity.java 文件

package heart.entry;

import heart.model.HeartAnimation;
import cn.heart.R;
import android.os.Bundle;
import android.widget.RelativeLayout;

public class MainActivity extends BaseActivity {
    RelativeLayout relativeLayout=null;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        ViewShowListen(R.id.layout, 10001);
    }
    @Override
    public void ViewAfterShow(final int width,final int height) {
        relativeLayout=(RelativeLayout)findViewById(R.id.layout);
        
        HeartAnimation heartAnimation=new HeartAnimation(width, height,relativeLayout,MainActivity.this);
        heartAnimation.setCount(20).start();
    }
}
MainActivity.java

5.View、surfaceView和GLSurfaceView的区别

5.1 View、surfaceView和GLSurfaceView的区别

Android游戏当中主要的除了控制类外就是显示类View。SurfaceView是从View基类中派生出来的显示类。android游戏开发中常用的三种视图是:

view、SurfaceView和GLSurfaceView的区别如下:
View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等;必须在UI主线程内更新画面,速度较慢
SurfaceView:基于view视图进行拓展的视图类,更适合2D游戏的开发;是view的子类,类似使用双缓机制,在新的线程中更新画面所以刷新界面速度比view快。
GLSurfaceView:基于SurfaceView视图再次进行拓展的视图类,专用于3D游戏开发的视图;是SurfaceView的子类,openGL专用。

在2D游戏开发中,大致可以分为两种游戏框架,View和SurfaceView。
View和SurfaceView区别:
    View:必须在UI的主线程中更新画面,用于被动更新画面。
    surfaceView:UI线程和子线程中都可以。在一个新启动的线程中重新绘制画面,主动更新画面。

UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。
当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步,涉及到线程同步。

所以基于以上,根据游戏特点,一般分成两类。
1 被动更新画面的。比如棋类,这种用view就好了。因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate。 因为这种情况下,这一次Touch和下一次的Touch需要的时间比较长些,不会产生影响。
2 主动更新。比如一个人在一直跑动。这就需要一个单独的thread不停的重绘人的状态,避免阻塞main UI thread。所以显然view不合适,需要surfaceView来控制

上面的所有的案例的,笔者都是用的View来实现的(这并不是最理想的选择,尤其是最后一个贝塞尔曲线动画),在知道了这一节的知识后,读者可以尝试使用SurfaceView来实现那些2D动画。

5.2 SurfaceView

5.2.1 SurfaceView的使用

1.创建SurfaceView,需要创建一个新的扩展了SurfaceView的类,并实现SurfaceHolder.Callback

2.需要重写的方法
(1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
//在surface的大小发生改变时激发
(2)public void surfaceCreated(SurfaceHolder holder){}
//在创建时激发,一般在这里调用画图的线程。
(3)public void surfaceDestroyed(SurfaceHolder holder) {}
//销毁时激发,一般在这里将画图的线程停止、释放。
整个过程:继承SurfaceView并实现SurfaceHolder.Callback接口 ----> SurfaceView.getHolder()获得SurfaceHolder对象 ---->SurfaceHolder.addCallback(callback)添加回调函数---->SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布----> Canvas绘画 ---->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)结束锁定画图,并提交改变,将图形显示。

3、SurfaceHolder
这里用到了一个类SurfaceHolder,可以把它当成surface的控制器,用来操纵surface。处理它的Canvas上画的效果和动画,控制表面,大小,像素等。
几个需要注意的方法:
(1)、abstract void addCallback(SurfaceHolder.Callback callback);
// 给SurfaceView当前的持有者一个回调对象。
(2)、abstract Canvas lockCanvas();
// 锁定画布,一般在锁定后就可以通过其返回的画布对象Canvas,在其上面画图等操作了。
(3)、abstract Canvas lockCanvas(Rect dirty);
// 锁定画布的某个区域进行画图等..因为画完图后,会调用下面的unlockCanvasAndPost来改变显示内容。
// 相对部分内存要求比较高的游戏来说,可以不用重画dirty外的其它区域的像素,可以提高速度。
(4)、abstract void unlockCanvasAndPost(Canvas canvas);
// 结束锁定画图,并提交改变。

栗子:

public class MainActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
     }
    class MyView extends SurfaceView implements SurfaceHolder.Callback,Runnable{
        boolean mRunning=false;
        SurfaceHolder holder=null;
        Thread td=null;
        
        public MyView(Context context) {
            super(context);
            holder=getHolder();
            holder.addCallback(this);
            setFocusable(true);
            setFocusableInTouchMode(true);
            this.setKeepScreenOn(true);
        }
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            mRunning=true;//允许开始绘制
            td=new Thread(this);
            td.start();//开始线程
        }
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
        }
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            mRunning=false;//停止
        }
        @Override
        public void run() {
            Canvas c = null;
            int count=0;
            while(mRunning){
                try{
                            c = holder.lockCanvas();//锁定画布,一般在锁定后就可以通过其返回的画布对象Canvas,在其上面画图等操作了。
                            c.drawColor(Color.BLACK);//设置画布背景颜色,如何背景是透明的话,那么建议设置成c.drawColor(Color.TRANSPARENT);如果需要在设置画布之前,清楚画布上的所有东西,那么建议设置成c.drawColor(Color.TEANSPARENT,Mode.CLEAR)
                            Paint p = new Paint(); //创建画笔
                            p.setColor(Color.WHITE);
                            Rect r = new Rect(100, 50, 300, 250);
                            c.drawRect(r, p);
                            c.drawText("这是第"+(count++)+"秒", 100, 310, p);
                            Thread.sleep(1000);//睡眠时间为1秒
                }catch(Exception e){
                    e.printStackTrace();
                }finally{
                    if(c!= null)
                        holder.unlockCanvasAndPost(c);//结束锁定画图,并提交改变。
                }
            }
        }
    }
}

5.2.2 SurfaceView的分层

在使用Surface类开发时,常常需要使绘制出来的图案背景色透明,以实现背景图片和绘制出来的图案融为一体,具体操作方法如下:

首先继承surfaceview类的子类(即你写的类)的构造方法中设置背景图片:

setBackgroundResource(R.drawable.background);

再加入下面这两行:

    setZOrderOnTop(true);//使surfaceview放到最顶层
    getHolder().setFormat(PixelFormat.TRANSLUCENT);//使窗口支持透明度

然后在绘制方法(一般为onDraw())中加入:

canvas.drawColor(Color.TRANSPARENT,Mode.CLEAR);//绘制透明色

在Surface开发中,是常常需要进行分层开发的,这时候可能需要在界面上加入好几个的Surface的实现类。

比如:

        RelativeLayout rl=(RelativeLayout)findViewById(R.id.idVal);
        rl.addView(SurfaceSubClass1);
        rl.addView(SurfaceSubClass2);

如果SurfaceSubClass2覆盖了SurfaceSubClass1的图案,那么这显然不是我们希望的,所以应该把SurfaceSubClass2设置为背景透明。

上面讨论是进行两层的开发,那么如果是需要进行三层或是三层以上分层开发,那么应该怎么办呢?

例如:

        RelativeLayout rl=(RelativeLayout)findViewById(R.id.idVal);
        rl.addView(SurfaceSubClass1);//1
        rl.addView(SurfaceSubClass2);//2
        rl.addView(SurfaceSubClass3);//3

上面的代码中,读者知道1,2,3条语句的哪一个SurfaceSubClass最先被加载到RelativeLayout(三个SurfaceSubClass都没有设置setZOrderOnTop(true))中吗?答案是SurfaceSubClass3,注意这里的三个SurfaceSubClass被加载到RelativeLayout中的顺序是3-2-1。知道了这一点后,进行多层开发就简单了,比如上面的三个SurfaceSubClass中,SurfaceSubClass3应该是最底层的图案(不需要设置透明),再上面一层应该是SurfaceSubClass2的图案(需要设置为透明,否则看不到底层图案),最上面一层是SurfaceSubClass1的图案(需要设置为透明),注意:这三个SurfaceSubClass都没有设置setZOrderOnTop(true)。

原文地址:https://www.cnblogs.com/HDK2016/p/7688813.html