android 动画原理二
简介:
这是由两部分组成的 Android 动画框架详解的第二部分实例篇。在阅读本篇之前,建议您首先阅读本系列的第一部分 Android 动画框架详解之原理篇。原理篇详细介绍了 Android 动画框架的实现原理,同时介绍了一个绕 Y 轴旋转的动画示例。本篇是在原理篇的基础上介绍一个较复杂的 Android launcher 的平滑和立体翻页效果动画的实现。
Android launcher 的平滑和立体翻页效果
我们这里把 Android launcher 程序的 Workspace 相关的代码抽取出来,以一个比较简单的代码来展示 launcher 程序是如何实现多页以及不同页面之间的切换效果。本示例代码在 SDK 2.1 中运行,设置的是 WVGA 的屏幕大小。
首先我们来看一下程序运行的效果来一些感性的认识。
图 1:平滑移动效果
图 2:立体翻页效果
回页首
窗口页面的布局
接着我们来看一下程序 UI(即 View 和 ViewGroup)的布局,Activity 的 ContentView 是 layout 中的 main.xml。它的内容如下:
清单 1.
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android=http://schemas.android.com/apk/res/android
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <com.easyandroid.workspace.FlatWorkspace android:id="@+id/workspace"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- </com.easyanrodid.workspace.FlatWorkspace>
- </LinearLayout>
其中 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();
- }
- }
这个立方体特效部分的代码介绍到这里。
结束语
本文介绍了 Android launcher 的平滑和立体翻页效果实现,可以帮助开发者深入理解 Android 的动画框架原理,从而能够充分利用 android 现有框架来做出够眩、够酷的动画效果。
参考资料
学习
- 关于 Android 开发请参考专题 Android 开发从入门到精通。
- 访问 developerWorks Open source 专区获得丰富的 how-to 信息、工具和项目更新以及最受欢迎的文章和教程,帮助您用开放源码技术进行开发,并将它们与 IBM 产品结合使用。
- 随时关注 developerWorks 技术活动和网络广播。
讨论
- 参与论坛讨论。
- 欢迎加入 My developerWorks 中文社区。
作者简介
朱韦伟 , IBM 中国系统与科技开发中心 HPC 部门的一名软件工程师,熟悉嵌入式开发。
李浩 , 软件工程师 , 北京爱格码科技有限公司,从事 Android 平台上的驱动以及应用程序开发。
本文转自 http://www.ibm.com/developerworks/cn/opensource/os-cn-android-anmt2/index.html?ca=drs-
android 动画原理二相关推荐
- Android动画原理
一.前言 Android动画包含三种:补间动画(Tween Animation),帧动画(Frame Animation),属性动画 (Property Animation).其中属性动画是从Andr ...
- 【转】浅析Android动画(二),属性动画高级实例探究
2019独角兽企业重金招聘Python工程师标准>>> ObjectAnimator实现属性动画 为了写好Android动画这几篇博客,在动笔之前我是下过很大决心的,我对自己的要求是 ...
- Android动画框架(二)----属性动画
转载请注明出处:http://blog.csdn.net/fishle123/article/details/50705928 Android提供三种形式动画:视图动画,帧动画,属性动画.其中属性动画 ...
- android 补间动画有停顿,Android动画原理分析(一)----补间动画
1.基本特点 补间动画(Tween动画),是android最早的动画框架,从Android1.0开始就有. 功能:可以实现移动.旋转.缩放.渐变四种效果以及这四种效果的组合形式. 实现形式:xml和代 ...
- 打造简易NineoldAndroids动画库,深入理解Android动画原理
简介 NineoldAndroids是Github上一个著名的动画库,简单来说,NineOldAndroids是一个向下兼容的动画库,主要是使低于API 11的系统也能够使用View的属性动画. 网上 ...
- android动画原理,最详细的解释小白也能听懂,值得收藏!
面试如作战,我们看战争影视剧的时候,经常看到这些剧作往往主要聚焦于作战过程.战场战略,对战前准备给的篇幅往往很少.实际上,战前准备也是关键的一环,没有充足的粮草.车马.兵器的准备.别说赢得战争,投入战 ...
- Android动画原理分析
最近在Android上做了一些动画效果,网上查了一些资料,有各种各样的使用方式,于是乘热打铁,想具体分析一下动画是如何实现的,Animation, Animator都有哪些区别等等. 首先说Anima ...
- Android 动画原理
简介 Android 平台提供了三类动画,1.Tween动画,就是对场景里的对象不断的进行图像变化来产生动画效果(旋转.平移.放缩和渐变):2. Frame动画,即顺序的播放事先做好的图像,与gif图 ...
- Android 动画(二)
以下演示如何通过代码创建动画 1>activity_main.xml布局文件的代码如下: <RelativeLayout xmlns:android="http://schema ...
最新文章
- javascript jquery 获取select选中的值
- Ember 3.9 发布,3.8 升级为 LTS
- 关于网络投票的反思2018-11-11
- jquery解析java对象数组_Javascript / jQuery初学者:将对象推送到数组
- C语言三目运算符 - C语言零基础入门教程
- centos7本地安装mysql_centos7安装mysql
- 专业书籍阅读-Earth System Science Data Resources
- 存储过程中SELECT INTO的使用
- intouch的报警怎么发到邮件上
- 黑群晖vmm专业版_教你群晖用自带的VMM虚拟机安装精简版win10系统教程
- 2021年茶艺师(初级)考试及茶艺师(初级)新版试题
- 微信读书产品体验报告
- 乐视网正式聘用刘延峰担任公司总经理 任期三年
- Redis-6.2.* 版本配置文件redis.conf详解
- c语言eallow,求大神指导C语言框图设计!!!
- 合工大路强java第四次作业第2题
- 多电脑切换器的原理和功能介绍
- 二、RPA机器人开发基础
- Linux UDP下C语言实现TFTP协议客户端
- Magento 过滤导航插件Mana