基础
官方文档
在安卓中,动画的实现原理很简单。就是通过父View不断调整子View的位置,并且不断地绘制子View。从而形成了一个动画。
使用到的api是AnimationDrawable,其实质是一个Drawable(AnimationDrawable继承于Drawable)。
使用步骤:
1,在res/drawable建立xml文件,用于定义要播放的图片和图片显示的时间。如frame_anim.xml:
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="true"> <item android:drawable="@drawable/rocket_thrust1" android:duration="200" /> <item android:drawable="@drawable/rocket_thrust2" android:duration="200" /> <item android:drawable="@drawable/rocket_thrust3" android:duration="200" /> </animation-list>
onShot属性:如果为true,就代表动画不会循环播放;为false代表循环播放。
2,用一个ImageView来显示动画。imageview通过调用setBackgroundResource()来关联相应的动画:
iv = (ImageView) findViewById(R.id.iv_after); iv.setBackgroundResource(R.drawable.frame_anim);3,开始播放动画
@Override public void onWindowFocusChanged(boolean hasFocus) { rocketAnimation = (AnimationDrawable) iv.getBackground(); rocketAnimation.start(); super.onWindowFocusChanged(hasFocus); }说明:重写activity的onWindowFocusChanged方法,并在该方法中开始播放动画。这样一来,界面显示的时候就会开始播放该动画。不能在onCreate()中调用start()的方法,因为iv.getBackground()是一个异步方法,可能没有执行完毕就调用了start()方法,这样就无法播放。
如果无法播放,就需要把帧动画的每一个图片放到res/drawable目录下。
只需要定义开始帧,结束帧以及持续时间,系统会自动计算出应该补入多少帧,并补入相应的图形,从而完成整个动画。也就是说它的实质上仍是帧动画,不过不需要用户设置太多的静态图片。
下面的AlphaAnimation,ScaleAnimation,RotateAnimation,TranslateAnimation及AnimationSet都是继承于Animation。是帧动画的总父类。
getTransformation:获取帧动画执行到目前时的变换信息。并将信息封装到第二个参数中。一般使用如下:
Animation anim = mAnimation; if (anim != null) { anim.getTransformation( AnimationUtils.currentAnimationTimeMillis(), mTransformation); System.out.println("millis = "+mTransformation.getMatrix()); }可以通过Transformation#getMatrix()获取帧动画变换的矩阵,进而使用到Canvas中。
xml文件是定义在res/anim目录下的。在代码中是通过R.anim.name来引用的,在xml中是通过@anim/name来引用。xml的根结点必须是<set>,<alpha>,<scale>,<translate>,<rotate>五个中的一个。
步骤:
1,在xml文件定义好动画,
2,在代码中使用AnimationUtils.loadAnimation()来加载动画,
3,用组件调用startAnimation()来开始动画
android:fillAfter:动画结束后,会保持动画结束后的样子。
android:fillBefore:动画结束后,保持动画开始时的样子。
xml文件定义:
<?xml version="1.0" encoding="utf-8"?> <alpha xmlns:android="http://schemas.android.com/apk/res/android" android:fromAlpha="1" android:toAlpha="0.1" android:duration="1000" android:repeatCount="3" > </alpha>代码中使用动画:
iv = (TextView) findViewById(R.id.iv_after); iv.setBackgroundResource(R.drawable.ic_launcher); findViewById(R.id.iv_before).setOnClickListener(new OnClickListener() { public void onClick(View v) { AlphaAnimation alphaAnimation = (AlphaAnimation) AnimationUtils .loadAnimation(MainActivity.this, R.anim.out); iv.startAnimation(alphaAnimation); } });
旋转,对应的是RotateAnimation。
android:pivotX:旋转的中心点。设置为百分比。如:50%表示以自己的横轴中心;如果是50%p,表示以父类的来计算的。
android:pivotY:同上。
平移。对应的是TranslateAnimation。
android:fromXDelta:从x轴的哪个位置开始移动。注意:开始移动的时候会把view的左上角移动到指定的位置处,然后才开始移动。
android:toXDelta:左上角要移动到的位置。两者的值的设置和上面的pivotY是一样的。
缩放。具体属性和上面的差不多。
动画组合。里面可以放置任意的上面三种动画。对应的是AnimationSet。
set单独具有android:shareInterpolator属性,它指的是:是否共享动画插值器。因为set是一个动画的集合,该属性就是用来指明:这些动画是否共享set中的插值器,默认为true。
上述的五个动画的共同父类是Animation。可以设置setAnimationListener(),在动画结束的时候再执行别的操作。
下面是一个帧动画对应的Drawable(来源于apidemo中graphics下AnimateDrawables——继承于Drawable)。截其draw方法如下:
public void draw(Canvas canvas) { Drawable dr = getProxy();//获取使用动画的drawable对象 if (dr != null) { int sc = canvas.save(); Animation anim = mAnimation; if (anim != null) { anim.getTransformation( AnimationUtils.currentAnimationTimeMillis(), mTransformation); System.out.println("millis = "+mTransformation.getMatrix()); canvas.concat(mTransformation.getMatrix());//将变换的矩阵用于当前的canvas } dr.draw(canvas);//重新绘制,从而形成了动画 canvas.restoreToCount(sc); } }
上面的几个动画中,都有一个属性interpolator。动画就是系统不断地计算,不断地移动;这个移动的过程是需要时间的。因此,在移动的过程中就有快慢之分,而这个属性就是用来指出快慢的分布。比如:越来越快,越来越慢,先快后慢,先慢后快等。常用插值值如下图(来源):
上述的几个插值器也可以通过xml文件来修改其中的一些属性。具体如下(来源):
动画结束后,点击位置不对。
其原因是:在动画的过程中,移动的并不是view的实际位置,而是它的显示位置。也就是说它的layout()方法中的参数是没有变化的,因此,它的父View还是认为它在原来的位置,而不是在我们看到的位置(也就是显示位置)。那么在点击的时候,父View在对touch事件进行分发传递的过程时,就会判断错误,从而不会形成我们想要的结果。
解决办法:在动画结束后,将view的实际位置进行移动,移动到它的显示位置上去。这里有三个方法。而且这些方法都是写在AnimationListener中的onAnimationEnd()方法中的。
其一:
通过view调用setX,setY,setTranslationX与setTranslationY这些方法(前两个和后两个的作用是一样的,只不过传的值不一样)。并把动画结束后view显示位置的坐标传入其中。要注意:这四个方法都是从api11开始的。
其二:
通过view调用layout(),传入的参数也是动画结束后view的显示位置的坐标。要注意:这种方法可以解决一部分点击事件的问题。当整个布局发现变化时(比如弹出软键盘),view会回到它原来的位置(也就是从布局文件中加载进来时它的位置),而不会保持在layout()方法设置的位置。
其三:
通过LayoutParams移动view实际位置。
示例:
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { System.out.println(view.getTop()+"---------------"); } });首先输出的结果是0,并且在调用onAnimationEnd()时,开始输入的结果仍是0(在第一行就进行输出),等到通过params进行设置之后,再输出时,其值已经变成了160。从这可以发现动画并没有移动view在其父view中的实际位置。
示例代码:
public void onAnimationEnd(Animation animation) { view.clearAnimation(); // 通过setX来实现 // view.setX(view.getLeft()); // view.setY(2*h/10); // view.setTranslationX(0); // view.setTranslationY(3.0f*h/10); // 通过layout来实现 // view.layout(view.getLeft(), h / 5, view.getRight(), h/5 + // view.getHeight()); RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams) view .getLayoutParams(); params.topMargin = 2 * h / 10; view.setLayoutParams(params); }