动手学Android之十三——动起来

有些事情,去做了,要把收获记录下来,记录下来的才是自己真正得到的

         这节我们来学一下android中的动画,这可是做游戏必备的,不过说起做游戏,可能很多人很感兴趣,但是其实真正的游戏大多是用游戏引擎做的,所谓游戏引擎,就是一个专门用来做游戏的框架或者类库,用它可以很方便地实现一些游戏效果。不过我们这里学习的android动画,也可以做一些小游戏了。

         Android主要支持两种动画,渐变动画和逐帧动画。但是之前,我们总是点击一个list的item跳到另一个Activity,然后来演示每一种功能,这次我们换一换,介绍一个新的东西,TabHost。

         我们先来看下tabHost的效果:


         怎么样,还是不错的吧?那这是怎么做到的呢?看代码:

public class MainActivity extends TabActivity {

	private TabHost tabHost = null;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		// TODO Auto-generated method stub
		super.onCreate(savedInstanceState);
		
		tabHost = this.getTabHost();
		TabHost.TabSpec tabSpec = null;
		Intent intent = null;
		Resources resources = getResources();
		
		intent = new Intent();
		intent.setClass(MainActivity.this, FrameAnimActivity.class);
		tabSpec = tabHost.newTabSpec("frame animation")
						 .setIndicator("逐帧动画", resources.getDrawable(R.drawable.icon_frame))
						 .setContent(intent);
		tabHost.addTab(tabSpec);
		
		intent = new Intent();
		intent.setClass(MainActivity.this, TweenAnimActivity.class);
		tabSpec = tabHost.newTabSpec("tween animation")
						 .setIndicator("渐变动画", resources.getDrawable(R.drawable.icon_tween))
						 .setContent(intent);
		tabHost.addTab(tabSpec);
	}
	
}

         首先,我们的Activity继承了TabActivity,然后我们通过getTabHost得到TabHost,然后,我们new了一个tabSpec,设置tag、indicator、drawable和content,最后把他加到tabhost中,这样一个tab就设置好了。

         下面我们先来看一下逐帧动画,其实这个过程我们似曾相识,我们在res下建立anim文件夹,再在anim文件夹下面建立动画文件frame.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/person01" android:duration="300" />
    <item android:drawable="@drawable/person02" android:duration="300" />
    <item android:drawable="@drawable/person03" android:duration="300" />
    <item android:drawable="@drawable/person04" android:duration="300" />

</animation-list>

         这个文件内容很好理解,我们让四幅画逐帧播放,每帧间隔300ms,这里android:oneshot="false"表示循环播放,如果为true则只播放一次。但是这个动画文件怎么引用呢?如果我们像progressBar的indeterminateDrawable这样引用:

<ImageView
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:background="@anim/frame" 
	/>

动画是不会有反应的,因为我们还没有启动动画,我们还需要加上:

animationDrawable = (AnimationDrawable) imageView.getBackground();
animationDrawable.start();

         把这两句话加在适当的位置,一个是用来获取AnimationDrawable的,一个是用来启动动画的。我们来看一下效果:


但是这个效果有个明显的缺点,当我们停止后,再开始,动画并不是接着开始,而是重新开始。我看了下源码,是因为里面的mCurFrame在stop的时候变成-1了,貌似是因为isRunning这个函数要用mCurFrame==-1来判断是否停止。而且AnimationDrawable这个类也没有类似setFrame这种函数,想要在停止后让动画从停止的那一帧继续下去还真是不易。

我个人觉得这个类是android的一个败笔,既然为逐帧动画设计的类,怎么可用性这么差呢?这里吐槽一下,有高手知道原因的也请告诉我,这个AnimationDrawable类的设计出发点?这个类除了使用这种xml加载动画的方法,还有一个方法,用纯代码来加载:

animationSourceDrawable = new AnimationDrawable();
for(int i=1; i<=4; i++){
	String str = String.format("person%02d", i);
	int id = getResources().getIdentifier(str, "drawable", getPackageName());
	animationSourceDrawable.addFrame(getResources().getDrawable(id), 300);
}
animationSourceDrawable.setOneShot(false);

         不过用代码来加载还不如用xml方便,你可能会发现很多坑,所以还是尽量用xml加载吧。

下面我们自己来实现这个所谓的停止后继续功能,其实逐帧动画很简单,无非就是隔一定时间换一张图片,我们完全可以用异步任务来完成这项功能。

这里,主要是我们异步任务在onStop的时候应该干什么?我们之前学习过异步任务,但是似乎没有提到它的停止,其实它的停止没有我们想象中那么简单,一般是用一个标志位boolean来标志线程是否需要停止,然后在doInBackground中判断标志位。熟悉Java多线程的同学一定不陌生。在这里,我们为了简单,让异步任务一开始就执行起来,然后用标志位来判断是否要启动动画就ok了。这里我把异步任务的代码贴出来,其他部分请参考我的实例程序。

public class AnimationAsyncTask extends AsyncTask<Void, Void, Void> {

	private Context context = null;
	private boolean isRunAnimation = false;
	
	public AnimationAsyncTask(Context context) {
		// TODO Auto-generated constructor stub
		this.context = context;
	}
	
	public void setRunAnimation(boolean isRun) {
		isRunAnimation = isRun;
	}
	
	@Override
	protected Void doInBackground(Void... params) {
		// TODO Auto-generated method stub
		while(true) {
			try {
				if(isRunAnimation) {
					publishProgress();
				}
				Thread.sleep(300);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	@Override
	protected void onProgressUpdate(Void... values) {
		// TODO Auto-generated method stub
		super.onProgressUpdate(values);
		((FrameAnimActivity)context).stepAsyncFrame();
	}

}

         但是这么一个功能用异步任务似乎有点大材小用了,其实还有一个更简单的方式,Handler,handler有两种用法,这里我们先来介绍一种。

handler = new Handler();
handlerRunnable = new Runnable() {
	
	public void run() {
		// TODO Auto-generated method stub
		if(isHandlerAnimationRun) {
			stepHandlerFrame();
			handler.postDelayed(this, 300);
		}
	}
};

         我们定义一个Runnable,在里面再调用handler的postDelayed方法,这个方法需要一个Runnable和一个时间,意思是,延迟300ms再调用Runnable中的run方法。在这里,我们把handlerRunnable自身传进去,这样就无限循环了,当然要在isHandlerAnimationRun为true的时候。

startHandlerFrameBtn.setOnClickListener(new OnClickListener() {
			
	public void onClick(View v) {
		// TODO Auto-generated method stub
		isHandlerAnimationRun = true;
		handler.postDelayed(handlerRunnable, 300);
		startHandlerFrameBtn.setVisibility(View.GONE);
		stopHandlerFrameBtn.setVisibility(View.VISIBLE);
	}
});

stopHandlerFrameBtn.setOnClickListener(new OnClickListener() {
	
	public void onClick(View v) {
		// TODO Auto-generated method stub
		isHandlerAnimationRun = false;
		startHandlerFrameBtn.setVisibility(View.VISIBLE);
		stopHandlerFrameBtn.setVisibility(View.GONE);
	}
});

这是我们的按钮绑定监听的代码,这个效果和用异步任务的几乎一样,但是,这种方法的Handler是在同一个线程中调用run方法,也就是说,handlerRunnable的run方法是UI线程在调用,所以我们才可以在里面调用stepHandlerFrame来改变imageView的图片。比起异步任务来,我们少了一个线程的开销,岂不是很爽?关于Handler,以后会有专题,这里我们只要知道一个大概就ok。

         好了,逐帧动画就先讲到这里,下面我们来看看渐变动画。渐变动画大概可以分为alpha渐变(透明度渐变)、scale渐变(大小变化)、translate渐变(位置变化)和rotate渐变(旋转变化)。我们先来看alpha渐变:同样是定义anim资源

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha="1.0"
    android:toAlpha="0.0"
    android:duration="1000"
    />

         这里的资源表示从完全不透明到完全透明,动画时间为1s。我们怎么使用这个资源呢?

Animation anim = AnimationUtils.loadAnimation(TweenAnimActivity.this, R.anim.alpha);
imageView.startAnimation(anim);

         动画可以应用在任何View上面。其他动画我们也来看看吧。Scale:

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXScale="1.0"
    android:toXScale="2.0"
    android:fromYScale="1.0"
    android:toYScale="2.0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:duration="1000"
    />

         fromXScale和toXScale指定了x方向从多大的size到多大的size,Y方向类似,这里说一下pivotX,这是指定缩放的锚点,这里是相对自身,也就是说这个动画已View的中心为锚点进行缩放。如果你对锚点不了解,你可以想象一下Word里面的图形,我们缩放的时候,是不是默认中心有一个小圆圈,这个小圆圈是不会变的,其他的地方都按比例缩放,这个小圆圈就是锚点。网上有些人说可以用0.5,但是我在我手机上试了不行,建议大家用50%,保证适配性。

         再看看Translate:

<translate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="-50%p"
    android:toXDelta="50%p"
    android:fromYDelta="-50%p"
    android:toYDelta="50%p"
    android:duration="3000"
    />

         这里的fromXDelta使用了-50%p,这表示50%是相对于父控件来说的,这个配置的意思是,向左上退父控件长宽的一半,然后移动到右下父控件长宽的一半,注意,这里并不是从父控件的-50%开始移动,而是以View现在的位置算起,往后退父控件的50%这么个长度,大家可以下载我的例子运行看看。

         最后来看看rotate:

<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:duration="1000"
    />

         这样就是转360度咯,但是请注意,我们没有设置锚点,这时,默认锚点是0,0,也就是已左上角为中心旋转,我们如果想以小人为中心旋转怎么办呢?相信你已经想到了:

<rotate
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="360"
    android:pivotX="50%"
    android:pivotY="50%"
    android:duration="1000"
    />	

         说到动画还有一个东西要说一下,就是Interpolator,这个东西是专门来指定动画运动的速度的,怎么说呢?我们在translate上加一个试试:android:interpolator="@android:anim/accelerate_interpolator",我们看看效果,是不是发现移动并不是匀速移动了,而是越来越快呢?这就是accelerate_interpolator的效果,当然还有很多其他的Interpolator,不一一介绍了,还可以自定义Interpolator,以后有需求再介绍吧,毕竟不是重点。

         除了用xml来配置,我们也可以使用纯代码来设置动画,也很简单,我们以alpha为例:

Animation anim = new AlphaAnimation(1.0f, 0.0f);
anim.setDuration(1000);
imageView.startAnimation(anim);

         其他代码类似,大家可以参考我的例子程序,这里不赘述了。

         我们能不能在动画开始和结束时做一些动作呢?答案是可以的,我们可以用到AnimationListener,我们在rotate中加上一个AnimationListener。

anim.setAnimationListener(new AnimationListener() {
	
	public void onAnimationStart(Animation animation) {
		// TODO Auto-generated method stub
		System.out.println("animation start");
	}
	
	public void onAnimationRepeat(Animation animation) {
		// TODO Auto-generated method stub
		System.out.println("animation repeat");
	}
	
	public void onAnimationEnd(Animation animation) {
		// TODO Auto-generated method stub
		System.out.println("animation end");
	}
	
});

         这个Listener很简单,一看就明白,有3个回调,分别是动画开始,动画重复,动画结束,基本上满足了我们的要求。对了,我们怎么让动画重复呢?可以调用anim.setRepeatCount(2);,这样就是重复2次,注意这里总共转3圈哦!那无限重复呢?肯定是用一个特殊宏咯,Animation.INFINITE。

         这个AnimationListener还是非常有用的,比如说你的动画开始时要一个提示,每次重复动画提示也变化,动画结束提示就消失,这些功能都能用AnimationListener来实现,非常方便。
         但是到这里,我们还是有很多需求没有被满足,比如我想一边移动一边变大,怎么办呢?这就要用到set了。

<set xmlns:android="http://schemas.android.com/apk/res/android">
    
    <scale 
        android:fromXScale="1.0"
        android:toXScale="2.0"
        android:fromYScale="1.0"
        android:toYScale="2.0"
        android:duration="1000"
        />
    
    <translate 
        android:fromXDelta="-40%p"
        android:toXDelta="40%p"
        android:fromYDelta="-40%p"
        android:toYDelta="40%p"
        android:duration="1000"
        />

</set>

         这时候我们再来加载动画:

AnimationSet anim = (AnimationSet) AnimationUtils.loadAnimation(TweenAnimActivity.this, R.anim.set);
imageView.startAnimation(anim);

         加载过程还是一样的,我们发现小人在移动的同时变大了。

         用代码同样可以实现这一过程,它是类似这样的代码:

Animation alpha = new AlphaAnimation(1.0f, 0.0f);
alpha.setDuration(1000);
Animation rotate = new RotateAnimation(0.0f, 360.0f);
rotate.setDuration(1000);
AnimationSet set = new AnimationSet(false);
set.addAnimation(alpha);
set.addAnimation(rotate);
imageView.startAnimation(set);

         最后讲一下我们的一个新的布局,大家先看下我的丑陋界面:


         下面的按钮是怎么布局的呢?其实就是TableLayout,这是表格布局,之前没有讲过的,它用起来其实很简单:

<TableLayout
	android:layout_width="match_parent"
	android:layout_height="wrap_content"
	>
	
	<TableRow >
		
		<Button
			android:id="@+id/alphaButton"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:text="@string/alpha" 
			/>
		
		<Button
			android:id="@+id/alphaSourceButton"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:text="@string/alphaSource" 
			/>
		
	</TableRow>
	
	<TableRow >
		
		<Button
			android:id="@+id/scaleButton"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:text="@string/scale" 
			/>
		
		<Button
			android:id="@+id/scaleSourceButton"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:text="@string/scaleSource" 
			/>
		
	</TableRow>
	
	<TableRow >
		
		<Button
			android:id="@+id/translateButton"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:text="@string/translate" 
			/>
		
		<Button
			android:id="@+id/translateSourceButton"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:text="@string/translateSource" 
			/>
		
	</TableRow>
	
	<TableRow >
		
		<Button
			android:id="@+id/rotateButton"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:text="@string/rotate" 
			/>
		
		<Button
			android:id="@+id/rotateSourceButton"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:text="@string/rotateSource" 
			/>
		
	</TableRow>
	
	<TableRow >
		
		<Button
			android:id="@+id/setButton"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:text="@string/set" 
			/>
		
		<Button
			android:id="@+id/setSourceButton"
			android:layout_width="match_parent"
			android:layout_height="wrap_content"
			android:layout_weight="1"
			android:text="@string/setSource" 
			/>
		
	</TableRow>
	
</TableLayout>

         后面的高深用法以后用到再讲好啦!

总结一下:

1、  TabHost的基本用法

2、  AnimationDrawable的用法,个人觉得不好用

3、  逐帧动画的替代方法,用异步任务或者Handler

4、  四种Tween动画的xml配置

5、  Interpolator

6、  利用代码实现四种Tween动画

7、  AnimationListener

8、  AnimationSet

9、  TableLayout的基本用法

这一节知识点很多,讲得较简单,希望大家好好研究下例子程序,例子程序在:http://download.csdn.net/detail/yeluoxiang/7473345,欢迎大家下载。


         噢,在测试的时候发现一个bug,我们用异步任务来实现Frame Animation的时候,线程并没有关闭,如果我们在doInBackground中输出:System.out.println(Thread.currentThread().getId()+ ":running");,我们打开程序,然后按返回键退出,再打开,多弄几次,你看看输出:


好几个线程在后面跑,这样一来,过不了多久系统资源就被大量浪费了。所以我们在Activity destroy的时候,要关闭异步任务。

代码做了调整,加上了isRunning变量,标识线程是否正在运行。增加了stop方法,具体如下:

public class AnimationAsyncTask extends AsyncTask<Void, Void, Void> {

	private Context context = null;
	private boolean isRunAnimation = false;
	private boolean isRunning = false;
	
	public AnimationAsyncTask(Context context) {
		// TODO Auto-generated constructor stub
		this.context = context;
		isRunning = true;
	}
	
	public void setRunAnimation(boolean isRun) {
		isRunAnimation = isRun;
	}
	
	@Override
	protected Void doInBackground(Void... params) {
		// TODO Auto-generated method stub
		while(isRunning) {
			try {
				if(isRunAnimation) {
					publishProgress();
				}
				Thread.sleep(300);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getId() + ":running");
		}
		return null;
	}
	
	@Override
	protected void onProgressUpdate(Void... values) {
		// TODO Auto-generated method stub
		super.onProgressUpdate(values);
		((FrameAnimActivity)context).stepAsyncFrame();
	}
	
	public void stop() {
		isRunning = false;
	}

}

         然后在FrameAnimActivity中加入:

@Override
protected void onDestroy() {
	// TODO Auto-generated method stub
	animationAsyncTask.stop();
	super.onDestroy();
}

         请大家在代码中自己调整。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值