动画!
http://www.cnblogs.com/angeldevil/archive/2011/12/02/2271096.html
http://blog.csdn.net/jdsjlzx/article/details/7528588
What is Animation?
Animation
extends Object
implements Cloneable
Abstraction for an Animation that can be applied to Views, Surfaces, or other objects.
AlphaAnimation
extends Animation
An animation that controls the alpha level of an object. Useful for fading things in and out. This animation ends up changing the alpha property of aTransformation
AlphaAnimation(Context context, AttributeSet attrs)
Constructor used when an AlphaAnimation is loaded from a resource.
AlphaAnimation(float fromAlpha, float toAlpha)
Constructor to use when building an AlphaAnimation from code
AnimationSet
extends Animation
Represents a group of Animations that should be played together. The transformation of each individual animation are composed together into a single transform. If AnimationSet sets any properties that its children also set (for example, duration or fillBefore), the values of AnimationSet override the child values.
在代码中实现动画效果的方法:
ImageView imageView = (ImageView) findViewById(R.id.imageView1); AnimationSet animationSet = new AnimationSet(true); AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1); alphaAnimation.setDuration(1000); alphaAnimation.setStartOffset(10000); animationSet.addAnimation(alphaAnimation); //animationSet.setStartOffset(10000); animationSet.setFillBefore(false); animationSet.setFillAfter(true); imageView.startAnimation(animationSet);
在XML文件中实现动画效果的方法:
① 在res目录下创建一个anim文件夹,在里面添加一个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" android:fillAfter="true" android:fillBefore="false"> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:startOffset="1000" android:duration="1000" /> </set>
② 在Activity中使用AnimationUtils获取Animation并进行设置:
Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.alpha); imageView.startAnimation(animation);
ScaleAnimation
extends Animation
An animation that controls the scale of an object. You can specify the point to use for the center of scaling.
ScaleAnimation(Context context, AttributeSet attrs)
Constructor used when a ScaleAnimation is loaded from a resource.
ScaleAnimation(float fromX, float toX, float fromY, float toY)
Constructor to use when building a ScaleAnimation from code
ScaleAnimation(float fromX, float toX, float fromY, float toY, float pivotX, float pivotY)
Constructor to use when building a ScaleAnimation from code
ScaleAnimation(float fromX, float toX, float fromY, float toY, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)
Constructor to use when building a ScaleAnimation from code
在代码中实现动画效果:
ImageView imageView = (ImageView) findViewById(R.id.imageView1); AnimationSet animationSet = new AnimationSet(true); ScaleAnimation scaleAnimation = new ScaleAnimation(1, 0.5f, 1, 0.5f, Animation.RELATIVE_TO_SELF, 1f, Animation.RELATIVE_TO_SELF, 1f); animationSet.addAnimation(scaleAnimation); animationSet.setDuration(1000); imageView.startAnimation(animationSet);
在XML文件中实现动画效果的方法:
① 在res的anim文件夹下,创建一个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"> <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="2000" /> </set>
② 在Activity中使用AnimationUtils获取Animation并进行设置:
Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.scale); imageView.startAnimation(animation);
RotateAnimation
extends Animation
An animation that controls the rotation of an object. This rotation takes place int the X-Y plane. You can specify the point to use for the center of the rotation, where (0,0) is the top left point. If not specified, (0,0) is the default rotation point.
RotateAnimation(Context context, AttributeSet attrs)
Constructor used when a RotateAnimation is loaded from a resource.
RotateAnimation(float fromDegrees, float toDegrees)
Constructor to use when building a RotateAnimation from code.
RotateAnimation(float fromDegrees, float toDegrees, float pivotX, float pivotY)
Constructor to use when building a RotateAnimation from code
RotateAnimation(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)
Constructor to use when building a RotateAnimation from code
在代码中实现动画效果:
ImageView imageView = (ImageView) findViewById(R.id.imageView1); AnimationSet animationSet = new AnimationSet(true); RotateAnimation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_PARENT, 0.5f, Animation.RELATIVE_TO_PARENT, 0.5f); rotateAnimation.setDuration(1000); animationSet.addAnimation(rotateAnimation); imageView.startAnimation(animationSet);
在XML文件中实现动画效果的方法:
① 在res的anim文件夹下,创建一个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"> <rotate android:fromDegrees="0" android:toDegrees="+360" android:pivotX="50%" android:pivotY="50%" android:duration="1000" /> </set>
② 在Activity中使用AnimationUtils获取Animation并进行设置:
Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.rotate); imageView.startAnimation(animation);
TranslateAnimation
extends Animation
An animation that controls the position of an object.
TranslateAnimation(Context context, AttributeSet attrs)
Constructor used when a TranslateAnimation is loaded from a resource.
TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta)
Constructor to use when building a TranslateAnimation from code
TranslateAnimation(int fromXType, float fromXValue, int toXType, float toXValue, int fromYType, float fromYValue, int toYType, float toYValue)
Constructor to use when building a TranslateAnimation from code
在代码中实现动画效果:
ImageView imageView = (ImageView) findViewById(R.id.imageView1); AnimationSet animationSet = new AnimationSet(true); TranslateAnimation translateAnimation = new TranslateAnimation( Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 1.0f, Animation.RELATIVE_TO_SELF, 0f, Animation.RELATIVE_TO_SELF, 1.0f); translateAnimation.setDuration(1000); animationSet.addAnimation(translateAnimation); imageView.startAnimation(animationSet);
在XML文件中实现动画效果的方法:
① 在res的anim文件夹下,创建一个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"> <translate android:fromXDelta="0%p" android:toXDelta="100%p" android:fromYDelta="0%p" android:toYDelta="100%p" android:duration="1000" /> </set>
其中100%p表示相对于父空间的位置
② 在Activity中使用AnimationUtils获取Animation并进行设置:
Animation animation = (Animation) AnimationUtils.loadAnimation(MainActivity.this, R.anim.translate); imageView.startAnimation(animation);
AnimationSet animationSet = new AnimationSet(false); AlphaAnimation alpha = new AlphaAnimation(1.0f, 0.0f); ScaleAnimation scale = new ScaleAnimation(1, 0.5f, 1, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); animationSet.addAnimation(alpha); animationSet.addAnimation(scale); animationSet.setDuration(2000); animationSet.setStartOffset(1000); animationSet.setFillAfter(true); imageView.startAnimation(animationSet);
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" android:shareInterpolator="true" android:fillAfter="true"> <alpha android:fromAlpha="1.0" android:toAlpha="0.0" android:startOffset="1000" android:fillAfter="true" android:duration="2000" /> <scale android:fromXScale="1.0" android:toXScale="0.5" android:fromYScale="1.0" android:toYScale="0.5" android:pivotX="50%" android:pivotY="50%" android:startOffset="1000" android:duration="2000" /> </set>
Activity中的代码:
Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.alpha); imageView.startAnimation(animation);
什么是Interpolator
Interpolator
extends Object
AccelerateDecelerateInterpolator:
AccelerateDecelerateInterpolator
extends Object
implements Interpolator
An interpolator where the rate of change starts and ends slowly but accelerates through the middle.
AccelerateInterpolater:
AccelerateInterpolator
extends Object
implements Interpolator
An interpolator where the rate of change starts out slowly and and then accelerates.
CycleInterpolator:
CycleInterpolator
extends Object
implements Interpolator
Repeats the animation for a specified number of cycles. The rate of change follows a sinusoidal pattern.
DecelerateInterpolator:
DecelerateInterpolator
extends Object
implements Interpolator
An interpolator where the rate of change starts out quickly and and then decelerates.
LinearInterpolator:
LinearInterpolator
extends Object
implements Interpolator
An interpolator where the rate of change is constant.
XML文件定义在set标签里或每个动画标签
set标签中定义:
<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:shareInterpolator="true" android:fillAfter="true">
每个动画标签中定义:
<set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator" android:shareInterpolator="false" android:fillAfter="true"> <alpha android:interpolator="@android:anim/accelerate_interpolator" android:fromAlpha="1.0" android:toAlpha="0.0" android:startOffset="1000" android:fillAfter="true" android:duration="2000" /> <scale android:interpolator="@android:anim/accelerate_decelerate_interpolator" android:fromXScale="1.0" android:toXScale="0.5" android:fromYScale="1.0" android:toYScale="0.5" android:pivotX="50%" android:pivotY="50%" android:startOffset="1000" android:duration="2000" /> </set>
在代码中设置:
AnimationSet animationSet = new AnimationSet(true); animationSet.setInterpolator(new AccelerateInterpolator());
或者分别为每个动画设置:
AnimationSet animationSet = new AnimationSet(false); AlphaAnimation alpha = new AlphaAnimation(1.0f, 0.0f); alpha.setInterpolator(new AccelerateInterpolator()); ScaleAnimation scale = new ScaleAnimation(1, 0.5f, 1, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); scale.setInterpolator(new AccelerateDecelerateInterpolator());
① 准备4张图片run1.png,run2.png,run3.png,run4.png分别放到res的三个drawable文件夹中
② 在res的drawable-ldpi目录下创建一个anim_run.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/run1" android:duration="100" /> <item android:drawable="@drawable/run2" android:duration="100" /> <item android:drawable="@drawable/run3" android:duration="100" /> <item android:drawable="@drawable/run4" android:duration="100" /> </animation-list>
③ 在Activity中使用xml文件设置ImageView控件imageView的背景源,并获取AnimationDrawable进行显示动画:
imageView.setBackgroundResource(R.drawable.anim_run); AnimationDrawable animationDrawable = (AnimationDrawable)imageView.getBackground(); animationDrawable.start();
什么是LayoutAnimationController?
LayoutAnimationController
extends Object
A layout animation controller is used to animated a layout's, or a view group's, children. Each child uses the same animation but for every one of them, the animation starts at a different time. A layout animation controller is used by ViewGroup to compute the delay by which each child's animation start must be offset. The delay is computed by using characteristics of each child, like its index in the view group. This standard implementation computes the delay by multiplying a fixed amount of miliseconds by the index of the child in its parent view group. Subclasses are supposed to override getDelayForView(android.view.View) to implement a different way of computing the delay. For instance, aGridLayoutAnimationController will compute the delay based on the column and row indices of the child in its parent view group. Information used to compute the animation delay of each child are stored in an instance of LayoutAnimationController.AnimationParameters, itself stored in theViewGroup.LayoutParams of the view.
在使用LayoutAnimationController控制ListView控件的样式效果的方法:
① 在res的anim文件夹中创建一个list_anim.xml文件用于控制ListView控件的动画:
<?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"> <scale android:fromXScale="0.0" android:toXScale="1.0" android:fromYScale="0.0" android:toYScale="1.0" android:pivotX="50%" android:pivotY="50%" android:duration="1000" /> </set>
② 创建一个布局文件item.xml用于设置ListView的item的样式:
<?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/user_name" android:layout_width="180dip" android:layout_height="30dip" android:textSize="10pt" android:singleLine="true" /> <TextView android:id="@+id/user_id" android:layout_width="fill_parent" android:layout_height="fill_parent" android:textSize="10pt" android:singleLine="true"/> </LinearLayout>
③ 在主Activity的布局文件main.xml中添加一个ListView
<ListView android:id="@id/android:list" android:layout_width="fill_parent" android:layout_height="wrap_content" android:scrollbars="vertical" android:layoutAnimation="@anim/anim_layout" />
④ 创建一个MainActivity继承ListActivity,并在onCreate方法中添加如下代码:
ListView listView = getListView(); List<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>(); HashMap<String, String> hm1 = new HashMap<String, String>(); hm1.put("user_name", "arthinking"); hm1.put("user_id", "001"); HashMap<String, String> hm2 = new HashMap<String, String>(); hm2.put("user_name", "Jason"); hm2.put("user_id", "002"); list.add(hm1); list.add(hm2); SimpleAdapter simpleAdapter = new SimpleAdapter(this, list, R.layout.item, new String[] { "user_name", "user_id" }, new int[] { R.id.user_name, R.id.user_id }); listView.setAdapter(simpleAdapter); //通过Animation获取LayoutAnimationController对ListView进行设置 Animation animation = (Animation)AnimationUtils.loadAnimation(MainActivity.this, R.anim.list_anim); LayoutAnimationController lac = new LayoutAnimationController(animation); lac.setOrder(LayoutAnimationController.ORDER_NORMAL); lac.setDelay(0.5f); listView.setLayoutAnimation(lac);
这样,运行程序,显示的ListView就会按照xml文件中预置的动画效果显示了。
也可以通过xml文件进行设置动画:
① 在以上步骤的基础之上,在res/anim文件夹下创建一个anim_layout.xml文件:
<?xml version="1.0" encoding="utf-8"?> <layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" android:delay="1" android:animationOrder="normal" android:animation="@anim/list_anim" />
② main在布局文件的的ListView添加如下属性:
android:layoutAnimation="@anim/anim_layout"
这样就在把MainActivity的onCreate()方法中的
//通过Animation获取LayoutAnimationController对ListView进行设置
注释后的代码删除了,直接使用xml进行动画的控制。
Animation.AnimationListener
android.view.animation.Animation.AnimationListener
An animation listener receives notifications from an animation. Notifications indicate animation related events, such as the end or the repetition of the animation.
包含以下的三个方法:
Notifies the end of the animation.
onAnimationRepeat(Animation animation)
Notifies the repetition of the animation.
onAnimationStart(Animation animation)
Notifies the start of the animation.
① 可以为一个Button添加一个事件:
button.setOnClickListener(new TestAnimationListener());
② 接下来是编写这个TestAnimationListener类,继承AnimationListener,并覆盖里面的三个方法:
//这里获取控件组,R.id.layoutId为main.xml的整体布局标签的id属性值 ViewGroup viewGroup = (ViewGroup)findViewById(R.id.layoutId); private class RemoveAnimationListener implements AnimationListener{ //该方法在淡出效果执行结束之后被调用 @Override public void onAnimationEnd(Animation animation) { //假设这里要在动画执行完之后删除一个TextView viewGroup.removeView(textView); } @Override public void onAnimationRepeat(Animation animation) { System.out.println("onAnimationRepeat"); } @Override public void onAnimationStart(Animation animation) { System.out.println("onAnimationStart"); } }
③ 同样的,在动画效果中添加控件可以按照如下实现
ScaleAnimation scale = new ScaleAnimation(1, 0.5f, 1, 0.5f, scale.setDuration(1000); scale.setStartOffset(100); TextView textView = new TextView(MainActivity.this); textView.setText("add"); viewGroup.addView(textView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); textView.startAnimation(scale);
Android 动画框架详解,第 2 部分
Android launcher 的平滑和立体翻页效果
我们这里把 Android launcher 程序的 Workspace 相关的代码抽取出来,以一个比较简单的代码来展示 launcher 程序是如何实现多页以及不同页面之间的切换效果。本示例代码在 SDK 2.1 中运行,设置的是 WVGA 的屏幕大小。
首先我们来看一下程序运行的效果来一些感性的认识。
图 1:平滑移动效果
图 2:立体翻页效果
窗口页面的布局
接着我们来看一下程序 UI(即 View 和 ViewGroup)的布局,Activity 的 ContentView 是 layout 中的 main.xml。它的内容如下:
清单 1.
其中 FlatWorkspace 的基类是 Workspace,它继承自 ViewGroup,是一个容器类,其中包含三个子 View,子 View 是 ImageView。三个 ImageView 就是三个页面。这三个 ImageView 的创建是在 WorkspaceActivity 的 onCreate 函数中调用 Workspace 的 initScreens 函数完成的,代码如下:
清单 2
ViewGroup.LayoutParams p = new iewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,ViewGroup.LayoutParams.FILL_PARENT); for (int i = 0; i < 3; i++) { this.addView(new ImageView(this.getContext()), i, p); } ((ImageView)this.getChildAt(0)).setImageResource(R.drawable.image_search); ((ImageView)this.getChildAt(1)).setImageResource(R.drawable.image_system); ((ImageView)this.getChildAt(2)).setImageResource(R.drawable.image_top);
图 3:Workspace 和页面布局图
为了让三个页面达到上图的窗口布局,我们对 Workspace 的 onMeasure 和 onLayout 函数进行了重载,重点在 onLayout 代码中。onLayout 函数调用 layoutScreens 函数完成布局,FlatWorkspace 中的 layoutScreens 实现如下:
清单 3
protected void layoutScreens() { int childLeft = 0; final int count = getChildCount(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != View.GONE) { final int childWidth = child.getMeasuredWidth(); child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight()); childLeft += childWidth; } } }
上面 child.layout 部分的代码把三个页面分别布局到了 X 和 Y 坐标系中的((0,0)-(ScreenWidth,ScreenHeight))和((ScreenWidth,0)-(2*ScreenWidth,ScreenHeight))以及((2*ScreenWidth,0)-(3*ScreenWidth,ScreenHeight))三个矩形区域中,这里用矩形区域的左上角顶点坐标和右下角的顶点坐标来表示矩阵。
至此我们已经完成了整个窗口页面的布局,窗口页面的布局大小是实际可视屏幕宽度的三倍,所以要显示所有页面需要让页面滚动。
回页首
页面的平滑移动的实现
下面来看用户 touch move 的时候程序如何让页面进行滑动,并且绘制他们。
页面的滑动可以调用 View 的 scrollBy 或 ScrollTo 函数,在 Workspace 的 onTouchEvent 函数中取得用户的手指移动的距离,然后调用 scrollBy(它的参数就是 X 和 Y 轴上需要移动的距离)来让 Workspace 这个 View(也是 ViewGroup)移动用户手指移动的距离,当然 View 移动之前得判断一下用户手指移动的距离和速度是否足够才进行移动,以此减少用户的误操作。这部分代码简单就不进行深入分析了,请大家自己看看代码。
当 Workspace 这个 View 调用 scrollBy 进行 View 的滚动时,必然导致这个 View 无效,从而被系统重新绘制,所以它的 dispatchDraw 函数会被调用来进行子 View(ImageView)的绘制,它本身没有什么东西要绘制,所以就不用关心 Workspace 的 onDraw 函数了。dispatchDraw 函数会调用 drawScreens(canvas) 来对子 View 进行绘制。我们来看一下 FlatWorkspace 的实现:
清单 4
protected void drawScreens(Canvas canvas) { final long drawingTime = getDrawingTime(); final int count = getChildCount(); for (int i = 0; i < count; i++) { drawChild(canvas, getChildAt(i), drawingTime); } }
这里的 canvas 宽高就是屏幕可视范围的大小(如 HVGA 屏幕的 320 × 480 大小),而三个子 ImageView 的布局要超出屏幕的范围,不在屏幕可视范围之内的部分是不会被绘制的。这个绘制三个子 ImageView 的函数很重要,是制作立方体翻页等特效的关键地方,FlatWorkspace 实现的是平滑滑动效果,所以我们直接绘制三个子 ImageView。如果要实现立方体的效果,在绘制三个子 ImageView 的时候就要让它们被绘制的时候有立体感,这个在 android 中我们可以通过上文提到的 Camera 类沿 Y 轴旋转一定的角度实现。
程序让用户进行 touch move 操作的目的是让用户选择一个页面,如果按照上面的实现,当用户最后抬起手指时,页面切换不会很彻底,而是象图 1 一样停留在两个页面之间。所以当用户抬起手指时程序需判断一下移动到下一个完整的页面还有多大距离,然后让 Workspace 这个 View 再移动这个距离一遍完整的切换到下一页。在这个移动的过程中,为了给用户一个平滑的感觉,不能一下就移动这个距离,而是需要给一定的时间间隔,在这个时间段里逐渐的移动到位,所以这里我们使用 Scroller 类的方法实现逐渐的移动。具体过程是在 Workspace 的 onTouchEvent 函数中检测到用户 touch up(抬起手指)时进行应该调整到哪个页面的判断,然后调用 snapToScreen(targetScreen) 跳转到需要目的页面,然后它调用 scrollToScreen(screen) 让 Workspace 这个 View 进行需要的滚动,这个函数在 FlatWorkspace 中的实现如下:
清单 5
public void scrollToScreen(int screen) { final int newX = screen * getWidth(); final int deltaX = newX - getScrollX(); Log.e("FlatWorkspace","scrollToScreen call mScroller.startScroll"); mScroller.startScroll(getScrollX(), getScrollY(), deltaX, getScrollY(), Math.abs(deltaX) * 2); invalidate(); }
这里的重点是 mScroler.startScroll 部分的代码,它让 Workspace view 在时间段 Math.abs(deltaX) * 2 里移动下一个目标页面可视化需要移动的距离 deltaX(及目的页面的坐标减去目前已经移动的距离),大家请好好看一下这个 deltaX 的计算,这里不细说了。这个 mScroller.startScroll 并不会导致 Workspace 立即进行移动,它只会导致当前 View 无效,从而重新绘制,在 Workspace 被它的父亲 View 调用绘制的时候,它的 computeScroll 函数会被调用,所以会在这个函数中让 Workspace 调用 scrollTo 函数进行实际的移动。代码如下:
清单 6
public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //postInvalidate(); } else if (mNextScreen != INVALID_SCREEN) { mCurrentScreen = mNextScreen; mNextScreen = INVALID_SCREEN; } }
至此,我们对 Workspace 的整个运行机制和平滑移动的效果是如何实现的已经介绍完成了。下面我们来具体谈谈立体翻页效果是如何实现的。
回页首
立体翻页效果的实现
通过前面的分析可知,立体翻页效果可以在平滑翻页效果的基础上通过改写三个子 ImageView 的绘制来完成。同时可知,翻页时用户操作过程分为三步:放下手指触摸屏幕,移动手指,抬起手指。手指触摸屏幕表示页面之间的滑动要开始了;移动手指的时候页面应该跟着用户手指的移动距离进行对应距离的移动,同时系统会根据页面的移动位置对 Workspace 里面的三个子 View(即页面)进行绘制;抬起手指的时候判断应该移动到哪个页面,还需要移动多少距离,然后平滑的移动需要的距离来跳转到目的页面上。
为了显示立体效果,对每个子 ImageView 的绘制时得想办法让它沿 Y 轴旋转一定的角度,前面已经提到 android 通过 Camera 这个类提供了这个功能,不需要使用 opengl ES 的东西,当然如果要做出更好的 3D 效果,我们就需要 opengl ES 的强大功能了。既然要旋转一定的角度,那这个角度怎么计算呢?我们把这个角度和用户手指移动的距离关联起来。因为这个立方体只会沿着 Y 轴旋转,我们只看这三个面的立方体的顶部就够了,它的顶部沿着 Y 轴的往其箭头指示的方向看是一个等边三角形,每个面相对于手机屏幕的沿着 Y 轴旋转的角度的计算方法如下图所示:
图 4:初始屏幕位置示意图
下图为屏幕 1 沿 Y 轴旋转 45 读后其他两个屏幕需要沿 Y 轴旋转的角度。
图 5:旋转 45 度后屏幕位置示意图
这个变换的部分请看代码 CubeWorkspace 中函数 drawScreen 的代码,如下:
清单 7
protected void drawScreen(Canvas canvas, int screen, long drawingTime) { final int width = getWidth(); final int scrollWidth = screen * width; final int scrollX = this.getScrollX(); if(scrollWidth > scrollX + width || scrollWidth + width < scrollX) { return; } final View child = getChildAt(screen); final int faceIndex = screen; final float faceDegree = currentDegree - faceIndex * preFaceDegree; if(faceDegree > 90 faceDegree < -90) { return; } final float centerX = (scrollWidth < scrollX)?scrollWidth + width:scrollWidth; final float centerY = getHeight()/2; final Camera camera = mCamera; final Matrix matrix = mMatrix; canvas.save(); camera.save(); camera.rotateY(-faceDegree); camera.getMatrix(matrix); camera.restore(); matrix.preTranslate(-centerX, -centerY); matrix.postTranslate(centerX, centerY); canvas.concat(matrix); drawChild(canvas, child, drawingTime); child.setBackgroundColor(Color.TRANSPARENT); canvas.restore(); }
上面函数中的 currentDegree 变量是变化的,不是一个固定的值,改变这个变量值的方法比较隐蔽,在 AngelBaseWorkspace 的 scrollTo 函数中。AngelBaseWorkspace 中的 scrollTo 函数把 View 类中的函数重载了,这个函数会被 View 中的 scrollBy 函数调用,所以每次 touch 屏幕并且 move 的时候 AngelBaseWorkspace 中的 scrollTo 函数会被调用(onTouchEvent 调用 scrollBy,scrollBy 调用 scrollTo),它会根据用户 touch move 移动的距离来更改当前页面的角度,即变量 currentDegree 的值。具体请看如下代码:
清单 8
public void scrollTo(int x, int y) { if (getScrollX() != x || getScrollY() != y) { int oldX = getScrollX(); int oldY = getScrollY(); super.scrollTo(x, y); //x is the touch action X direction move distance currentDegree = x * degreeOffset; onScrollChanged(x, y, oldX, oldY); invalidate(); } }
这个立方体特效部分的代码介绍到这里。