OPhone平台提供了一套完整的动画框架,使得开发者可以用它来开发各种动画效果,本文将向读者阐述OPhone平台的动画框架是如何实现的。任何一个框架都有其优势和局限性,只有明白了其实现原理,开发者才能知道哪些功能可以利用框架来实现,哪些功能须用其他途径实现。 (作者:鲁威)5 s+ _6 x5 v* g( I9 ^; g
OPhone动画框架原理0 C7 ~! U) G* y; C& o1 S
现有的OPhone动画框架是建立在View的级别上的,在View类中有一个接口startAnimation来使动画开始,startAnimation函数会将一个Animation类别的参数传给View,这个Animation是用来指定我们使用的是哪种动画,现有的动画有平移,缩放,旋转以及alpha变换等。如果需要更复杂的效果,我们还可以将这些动画组合起来,这些在下面会讨论到。; x+ t0 L0 @2 g: Y
要了解OPhone动画是如何画出来的,我们首先要了解OPhone的View是如何组织在一起,以及他们是如何画自己的内容的。OPhone的View层次结构见图1:
5 H( z/ D' Z4 d8 P8 B b5 m
当一个ChildView要重画时,它会调用其成员函数invalidate()这个函数将通知其ParentView这个ChildView要重画,这个过程一直向上遍历到ViewRoot,当ViewRoot收到这个通知后就会调用DecorView的dispatchDraw()这个函数,dispatchDraw会调用其每一个ChildView的draw()函数,View的draw()函数就是它具体画它的内容的地方,它会调用onDraw函数,对用户来说只需要重载View::onDraw()就可以了。View::onDraw()有一个画布参数Canvas,画布顾名思义就是画东西的地方,OPhone会为每一个View设置好画布,View就可以调用Canvas的方法,比如:drawText, drawBitmap, drawPath等等去画内容。每一个ChildView的画布是由其ParentView设置的,ParentView根据ChildView在其内部的布局来调整Canvas,其中画布的属性之一就是定义和ChildView相关的坐标系,默认是横轴为X轴,从左至右,值逐渐增大,竖轴为Y轴,从上至下,值逐渐增大,见图2:
0 l0 d* h; ^2 y C- @/ Q+ S
OPhone动画就是通过ParentView来调整ChildView的画布坐标系来实现的,下面以平移动画来做示例,见图3,假设在动画开始时ChildView在ParentView中的初始位置在(100,200)处,这时ParentView会根据这个坐标来设置ChildView的画布,在ParentView的dispatchDraw中它发现ChildView有一个平移动画,而且当前的平移位置是(100, 200),于是它通过调用画布的函数traslate(100, 200)来告诉ChildView在这个位置开始画,这就是动画的第一帧。如果ParentView发现ChildView有动画,就会不断的调用invalidate()这个函数,这样就会导致自己会不断的重画,就会不断的调用dispatchDraw这个函数,这样就产生了动画的后续帧,当再次进入dispatchDraw时,ParentView根据平移动画产生出第二帧的平移位置(500, 200),然后继续执行上述操作,然后产生第三帧,第四帧,直到动画播完。具体算法描述如下:
! S5 j: n/ N, ?6 k5 D( }
view plaincopy to clipboardprint? 9 ]# m4 F' K m$ v/ p
( R, l% w5 i& ^* J. G+ A/ i/ }
- dispatchDraw()
- {
- ....
- Animation a = ChildView.getAnimation()
- Transformation tm = a.getTransformation();
- Use tm to set ChildView's Canvas;
- Invalidate();
- ....
- }
dispatchDraw() { .... Animation a = ChildView.getAnimation() Transformation tm = a.getTransformation(); Use tm to set ChildView's Canvas; Invalidate(); .... } 6 P" /$ Z* e6 J7 G1 O$ m5 z0 _
( z9 E- w B( k& ]& z4 /
/ Z* q8 O8 c* A$ r' c' F; C2 d
以上是以平移动画为例子来说明动画的产生过程,这其中又涉及到两个重要的类型,Animation 和 Transformation,这两个类是实现动画的主要的类,Animation中主要定义了动画的一些属性比如开始时间,持续时间,是否重复播放等等,这个类主要有两个重要的函数:getTransformation 和 applyTransformation,在getTransformation中Animation会根据动画的属性来产生一系列的差值点,然后将这些差值点传给applyTransformation,这个函数将根据这些点来生成不同的Transformation,Transformation中包含一个矩阵和alpha值,矩阵是用来做平移,旋转和缩放动画的,而alpha值是用来做alpha动画的,以上面的平移矩阵为例子,当调用dispatchDraw时会调用getTransformation来得到当前的Transformation,这个Transformation中的矩阵如下:
5 O- /$ ^# [+ U8 y4 _4 y$ P u. A1 N8 c8 d6 R
所以具体的动画只需要重载applyTransformation这个函数即可,类层次图如下:% Q0 B5 M0 Y: z" w( Q
& u! m% b4 ?6 }% R$ @6 x
用户可以定义自己的动画类,只需要继承Animation类,然后重载applyTransformation这个函数。对动画来说其行为主要靠差值点来决定的,比如,我们想开始动画是逐渐加快的或者逐渐变慢的,或者先快后慢的,或者是匀速的,这些功能的实现主要是靠差值函数来实现的,OPhone提供了一个Interpolator的基类,你要实现什么样的速度可以重载其函数getInterpolation,在Animation的getTransformation中生成差值点时,会用到这个函数。
以上就是OPhone的动画框架的原理,了解了原理对我们的开发来说就可以清晰的把握动画的每一帧是怎样生成的,这样便于开发和调试。
动画示例
在这个例子中,将要实现一个绕Y轴旋转的动画,这样可以看到3D透视投影的效果,代码如下:
# m* }3 t2 R2 O- i
view plaincopy to clipboardprint? * E$ L0 K0 q8 @
8 o2 z0 r7 j+ V% Q3 @3 [
- public# N4 _1 ^, O) o' J& b, /0 a
class Rotate3dAnimation extends Animation {
private) `" o. T' h; G- M5 f% t/ ]
final
float mFromDegrees;- - c& J: _5 w. g; |6 f! H! @
private
final3 e: _0 W' /0 |
float mToDegrees;
private
final
float mCenterX;- ! v0 c* X! @, n6 ?, K' `
private4 d! C/ T6 E/ M& V! y
final6 A V1 r9 z6 P
float mCenterY; - 9 G/ ~, d1 w; s
private
final
float mDepthZ;
private
final) u1 U7 F, e* n0 ~/ M
boolean mReverse;
private Camera mCamera;- & C& s) C5 x0 j0 [
- 8 |# K; E p8 G* R! R+ r3 ]7 d; M
/** - * Creates a new 3D rotation on the Y axis. The rotation is defined by its
- * start angle and its end angle. Both angles are in degrees. The rotation
- * is performed around a center point on the 2D space, definied by a pair
- * of X and Y coordinates, called centerX and centerY. When the animation
- * starts, a translation on the Z axis (depth) is performed. The length
- * of the translation can be specified, as well as whether the translation
- * should be reversed in time.
- *
- * @param fromDegrees the start angle of the 3D rotation
- * @param toDegrees the end angle of the 3D rotation
- * @param centerX the X center of the 3D rotation
- * @param centerY the Y center of the 3D rotation
- * @param reverse true if the translation should be reversed, false otherwise
- */) s- _. B; j$ Z" m1 c6 E
public Rotate3dAnimation(float fromDegrees, float toDegrees,
float centerX, float centerY, float depthZ, boolean reverse) {- mFromDegrees = fromDegrees;
- mToDegrees = toDegrees;
- mCenterX = centerX;
- mCenterY = centerY;
- mDepthZ = depthZ;
- mReverse = reverse;
- }
@Override/ t. c2 M5 V0 `; z- # U& s9 K5 `7 a- ]6 v2 P( A
public0 S+ f& w7 w0 Z6 |/ r6 m
void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);- mCamera = new Camera();
- }
- r5 ^$ }% y0 s4 b, Z
@Override; f2 g" R% |- Y9 I- 6 H5 E1 _* /# ~! s: P, I* U
protected
void applyTransformation(float interpolatedTime, Transformation t) {
final
float fromDegrees = mFromDegrees;
float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);- - n8 } F( C, p$ x/ d+ Q, D
- 0 O6 t* n; O& Y1 ?" |: p
final3 ^5 c. f% l( d2 k
float centerX = mCenterX;
final/ V; x' b6 U% L4 p9 o: J) A; e4 K
float centerY = mCenterY;
final Camera camera = mCamera;- 8 G9 H2 A. v* {
final Matrix matrix = t.getMatrix();- camera.save();
- , O% g. O( v- v7 e( ]" ~
if (mReverse) { - camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime);
- } else {
- camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime));
- }
- camera.rotateY(degrees);
- camera.getMatrix(matrix);
- camera.restore();
- matrix.preTranslate(-centerX, -centerY);
- matrix.postTranslate(centerX, centerY);
- }
- }
public class Rotate3dAnimation extends Animation { private final float mFromDegrees; private final float mToDegrees; private final float mCenterX; private final float mCenterY; private final float mDepthZ; private final boolean mReverse; private Camera mCamera; /** * Creates a new 3D rotation on the Y axis. The rotation is defined by its * start angle and its end angle. Both angles are in degrees. The rotation * is performed around a center point on the 2D space, definied by a pair * of X and Y coordinates, called centerX and centerY. When the animation * starts, a translation on the Z axis (depth) is performed. The length * of the translation can be specified, as well as whether the translation * should be reversed in time. * * @param fromDegrees the start angle of the 3D rotation * @param toDegrees the end angle of the 3D rotation * @param centerX the X center of the 3D rotation * @param centerY the Y center of the 3D rotation * @param reverse true if the translation should be reversed, false otherwise */ public Rotate3dAnimation(float fromDegrees, float toDegrees, float centerX, float centerY, float depthZ, boolean reverse) { mFromDegrees = fromDegrees; mToDegrees = toDegrees; mCenterX = centerX; mCenterY = centerY; mDepthZ = depthZ; mReverse = reverse; } @Override public void initialize(int width, int height, int parentWidth, int parentHeight) { super.initialize(width, height, parentWidth, parentHeight); mCamera = new Camera(); } @Override protected void applyTransformation(float interpolatedTime, Transformation t) { final float fromDegrees = mFromDegrees; float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime); final float centerX = mCenterX; final float centerY = mCenterY; final Camera camera = mCamera; final Matrix matrix = t.getMatrix(); camera.save(); if (mReverse) { camera.translate(0.0f, 0.0f, mDepthZ * interpolatedTime); } else { camera.translate(0.0f, 0.0f, mDepthZ * (1.0f - interpolatedTime)); } camera.rotateY(degrees); camera.getMatrix(matrix); camera.restore(); matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); } } 在这个例子中我们重载了applyTransformation函数,interpolatedTime就是getTransformation函数传下来的差值点,在这里做了一个线性插值算法来生成中间角度:float degrees = fromDegrees + ((mToDegrees - fromDegrees) * interpolatedTime);
Camera类是用来实现绕Y轴旋转后透视投影的,我们只需要其返回的Matrix值,这个值会赋给Transformation中的矩阵成员,当ParentView去为ChildView设置画布时,就会用它来设置坐标系,这样ChildView画出来的效果就是一个绕Y轴旋转同时带有透视投影的效果。利用这个效果便可以作出像 iPhone的CoverFlow这样比较酷的动画。
结束语 ( J, t& ~' o* R
本文介绍了OPhone动画框架的基本原理,可以帮助开发者深入理解OPhone的动画是如何实现的,从而能够充分利用现有框架来作出够眩,够酷的动画效果。