Android图形动画
一、动画基础 |
|---|
本质
每帧绘制不同的内容。
基本过程
开始动画后,调用View的invalidate触发重绘。重绘后检查动画是否停止,若未停止则继续调用invalidate触发下一帧(下一次重绘),直到动画结束。
重绘时View的draw方法会被调用,根据动画的进行绘制不同的内容,如某个被绘制元素的大小变化、角度旋转、透明度变化等,这样即会产生动画。
动画的推进过程一般都会有一个变化量,这个变量会被用到draw方法内元素的绘制。一般的变量都是时间,也可以是手指移动、传感器等任何其他的变量。
Android中的动画支持
Animation:早期实现的让View整体做动画的类。能让View做Matrix(移动、缩放、旋转、3D旋转)和Alpha(透明)的动画。
Animator:有硬件加速后为做动画实现的类。能方便的让View整体做动画;也可以只产生随时间变化的变量,用来在onDraw里做绘图级的动画。比Animation灵活很多。
AnimationDrawable:图片逐帧动画。主要用来播放提前制作好的动画。
在哪个级别做动画
让整个View做动画(比如整个View平移、旋转等)很简单方便,一般调用几行代码就行。我把它称作View级的动画。
在View的draw/onDraw里通过Canvas来绘制时做动画更灵活,更精细,能力更强大。我把它称作绘图级的动画。(View级的动画本质上也是这么做的,只是Android系统帮我们做了大部分工作)
绘图级的动画
这篇文章主要讲绘图级的动画。
下面来一段绘图级动画的典型实现:
class MyView extends View { void startAnimator() { ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f); animator.start(); invalidate(); } protected void onDraw(Canvas canvas) { if (animator.isRunning()) { float ratio = (Float)animator.getAnimatedValue(); canvas.rotate(ratio*360); canvas.drawBitmap(bitmap, 0, 0, null); invalidate(); } ... }} |
有了不断变化的ratio变量,绘图级动画就可以大展身手了。
绘图级动画的强大能力来自绘图API的强大能力,下面主要讲绘图API。
二、绘图API |
|---|
Matrix
Canvas.[translate,scale,rotate,skew]方法
Matrix.set/pre/post[translate,scale,rotate,skew]方法
平移、缩放、旋转、斜切。
从使用API的角度来看,我们通过调用Canvas.translate等方法,可以使后续在此Canvas上绘制操作的绘制区域变化,如translate(5,0),则后续所有绘制操作的绘制区域都会向右移动5个像素。
原理:Canvas里有一个Matrix,Canvas上的这几个调用都会最终调用到Matrix.pre*。这个Matrix保存整个变换过程。当有Canvas.draw时,要绘制的点都会经过Matrix.mapPoints方法做一个映射。于是产生我们期望的变换效果。(事实上映射的时候只需要映射关键点,其他的是插值来的)
关于Matrix的更多信息
set/pre/post的区别:set是设置,冲掉以前的数据。pre是前乘,post是后乘,根本上讲就是生效顺序不同。具体表现效果可在网上搜索资料。
setPolyToPoly:与mapPoints方法相反,mapPoints是通过矩阵把原始点映射为目标点。setPolyToPoly是输入原始点和映射后的目标点,计算出这个矩阵。
Camera:有透视效果的3D旋转。Camera是一个生成Matrix的工具类。可用来生成有透视效果的3D旋转。
Canvas.draw*方法
Canvas.draw-Point/s
Canvas.draw-Line/s
Canvas.draw-Rect,RoundRect,Circle,Oval,Arc,Path
Canvas.draw-Text
Canvas.draw-Bitmap,BitmapMesh
Canvas.draw-Color,Paint
这些方法都表示绘制一个区域。绘制的区域中究竟填充什么颜色,由Paint决定。
Color,Paint,Bitmap,BitmapMesh这几个则除了指定绘制区域外,还指定了填充内容。
Path功能比较强大,可自行组织成任何形状,还可以用贝塞尔曲线。
这些方法基本上都很好理解,从名字上即可看出其功能。这里重点提一下drawBitmapMesh。
drawBitmapMesh是输入一个网格模型,绘制出的图片内容将依据这个网格来扭曲。可以想像成把图片画在一块有弹性的布上,当我们把布的某些区域扯动的时候,会形成画面扭曲效果。
示例:假设有个30x30大小的图片,我们建立这样的网格输入:
0,0, 15,0, 30,0,
0,15, 15,15, 30,15,
0,30, 15,30, 30,30
则图片会原样输出,没有任何扭曲。
如果我们建立这样的网格输入:
0,0, 15,12, 30,0,
0,15, 15,15, 30,15,
0,30, 15,30, 30,30
Alpha通道
每个Color里可以有四个通道ARGB,其中RGB是红绿蓝,A即Alpha通道,它通常的作用是用来作为此颜色的透明度。
因为我们的显示屏是没法透明的,因此最终显示在屏幕上的颜色里可以认为没有Alpha通道。Alpha通道主要在两个图像混合的时候生效。
默认情况下,当一个颜色绘制到Canvas上时的混合模式是这样计算的:(RGB通道) 最终颜色 = 绘制的颜色 + (1 - 绘制颜色的透明度) × Canvas上的原有颜色。
注意:
1.这里我们一般把每个通道的取值从0到255映射到0到1的浮点数表示。
2.这里等式右边的“绘制的颜色"、“Canvas上的原有颜色”都是经过预乘了自己的Alpha通道的值。如绘制颜色:0x88ffffff,那么参与运算时的每个颜色通道的值不是1.0,而是(1.0 * 0.53125 = 0.53125)。
使用这种方式的混合,就会造成后绘制的内容以半透明的方式叠在上面的视觉效果。
其实还可以有不同的混合模式供我们选择,用Paint.setXfermode,指定不同的PorterDuff.Mode。
下表是各个PorterDuff模式的混合计算公式:(D指原本在Canvas上的内容dst,S指绘制输入的内容src,a指alpha通道,c指RGB各个通道)
| ADD | Saturate(S + D) |
| CLEAR | [0, 0] |
| DARKEN | [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + min(Sc, Dc)] |
| DST | [Da, Dc] |
| DST_ATOP | [Sa, Sa * Dc + Sc * (1 - Da)] |
| DST_IN | [Sa * Da, Sa * Dc] |
| DST_OUT | [Da * (1 - Sa), Dc * (1 - Sa)] |
| DST_OVER | [Sa + (1 - Sa)*Da, Rc = Dc + (1 - Da)*Sc] |
| LIGHTEN | [Sa + Da - Sa*Da, Sc*(1 - Da) + Dc*(1 - Sa) + max(Sc, Dc)] |
| MULTIPLY | [Sa * Da, Sc * Dc] |
| SCREEN | [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] |
| SRC | [Sa, Sc] |
| SRC_ATOP | [Da, Sc * Da + (1 - Sa) * Dc] |
| SRC_IN | [Sa * Da, Sc * Da] |
| SRC_OUT | [Sa * (1 - Da), Sc * (1 - Da)] |
| SRC_OVER | [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] |
| XOR | [Sa + Da - 2 * Sa * Da, Sc * (1 - Da) + (1 - Sa) * Dc] |
可以发现,我们之前的默认混合模式其实就是SRC_OVER。
通过选择其他的PorterDuff模式,我们可以达到一些特殊的效果:
使用DST_OVER的话,相当于后绘制的内容作为背景在底下。
使用DST_IN/DST_OUT的话,可以裁剪Canvas里的内容,或用一张带alpha的图片mask指定哪些区域显示/不显示。
通过选择SRC_ATOP可以只在Canvas上有内容(不透明)的地方绘制。
用一张示例图来查看使用不同模式时的混合效果(src表示输入的图,dst表示原Canvas上的内容):

填充颜色
之前说过Canvas.draw*指定了绘制的区域。而区域里的填充颜色是由Paint来指定的。
Paint.setColor指定纯色。
Paint.setShader可指定:BitmapShader, LinearGradient, RadialGradient, SweepGradient, ComposeShader。
BitmapShader:图片填充。
LinearGradient, RadialGradient, SweepGradient:渐变填充。
ComposeShader:叠加前面的某两种。可选择PorterDuff混合模式。
如果既调用了setColor,又调用了setShader,则setShader生效。如果同时用setColor或setAlpha设置了透明度,则透明度也会生效。(会和Shader的透明度叠加)
如果使用drawBitmap输入一个只有alpha的图片(可用Bitmap.extractAlpha方法获得),则会以alpha图片为mask,绘制出shader/color的颜色。
ColorFilter
通过ColorFilter可以对一次绘制的所有像素做一个通用处理。
Paint.setColorFilter: LightingColorFilter, PorterDuffColorFilter, ColorMatrixColorFilter。
这可以整体上改变这一次draw的内容,比如让颜色更暗、更亮等。
这里重点介绍下ColorMatrixColorFilter。
ColorMatrix是4x5矩阵,定义其每个元素如下:
{ a, b, c, d, e,
f, g, h, i, j,
k, l, m, n, o,
p, q, r, s, t }
则ColorMatrix的最终运算方式如下:
R' = a*R + b*G + c*B + d*A + e;
G' = f*R + g*G + h*B + i*A + j;
B' = k*R + l*G + m*B + n*A + o;
A' = p*R + q*G + r*B + s*A + t;
可在这里方便的试验flash在线版。
绘图API架构
整个绘制流水线大概如下:(我们能定义的部分用蓝色表示)

考虑动画实现的时候一般从两个角度来思考:
宏观角度:有几个变化量,分别是什么。动画从开始到结束的流程。
微观角度:从某一帧上去想,在变化量为某个数值时的图像,该怎么绘制。
把这两者分开去想,就会比较清晰。
PPT里有示例,可以参照DEMO来熟悉:
Android图形动画的更多相关文章
- OpenGL—Android 开机动画源码分析一
.1 Android开机动画实现方式目前实现Android开机动画的方式主要是逐帧动画和OpenGL动画. ?逐帧动画 逐帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的 ...
- Android属性动画
这几天看郭神的博客 Android属性动画完全解析(上),初识属性动画的基本用法之后,我自己突然想实现一种动画功能,就是我们在携程网.阿里旅行等等手机APP端买火车票的时候,看到有选择城市,那么就有出 ...
- android 自定义动画
android自定义动画注意是继承Animation,重写里面的initialize和applyTransformation,在initialize方法做一些初始化的工作,在applyTransfor ...
- 【转】android 属性动画之 ObjectAnimator
原文网址:http://blog.csdn.net/feiduclear_up/article/details/39255083 前面一篇博客讲解了 android 简单动画之 animtion,这里 ...
- Android属性动画之ValueAnimation
ValueAnimation是ObjectAnimation类的父类,经过前几天的介绍,相信大家对ObjectAnimation有了 一定的认识,今天就为大家最后介绍一下ValueAnimation, ...
- Android属性动画之ObjectAnimator
相信对于Android初学者,对于Android中的动画效果一定很感兴趣,今天为大家总结一下刚刚学到的属性动画案例. 首先和一般的Android应用一样,我们先建一个工程,为了方便,我们的布局文件中就 ...
- 79.Android之动画基础
转载:http://a.codekk.com/detail/Android/lightSky/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8 ...
- Android属性动画完全解析(下)
转载:http://blog.csdn.net/guolin_blog/article/details/44171115 大家好,欢迎继续回到Android属性动画完全解析.在上一篇文章当中我们学习了 ...
- Android属性动画完全解析(上),初识属性动画的基本用法
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/43536355 在手机上去实现一些动画效果算是件比较炫酷的事情,因此Android系 ...
随机推荐
- JQuery获得内容 - text()、html() 以及 val()
获得text()和html() <!DOCTYPE html><html><head><script src="/jquery/jquery-1.1 ...
- MySQL数据库服务器整体规划(go)
我们在搭建MySQL数据库服务器的开始阶段就合理的规划,可以避免以后的很多问题的产生,大大节省我们的时间和精力,在一定幅度上降低成本.当然,这会涉及很多方面.比如机器的选型.业务评估和系统规划等. 所 ...
- ProtocolBuffers (二) android与PC,C#与Java 利用protobuf 进行无障碍通讯【Socket】
protobuf 是什么? Protocol buffers是一种编码方法构造的一种有效而可扩展的格式的数据. 谷歌使用其内部几乎RPC协议和文件格式的所有协议缓冲区. 参考文档 http://c ...
- bzoj4918: 回文数对
Description 给定区间[L,R],请统计有多少对整数A,B(L<=A,B<=R)满足A xor B的值在二进制表示下,去掉所有前导0后是回文串 Input 第一行包含一个正整数T ...
- 实验二:C基本数据类型及运算
2.1 建议使用double型 #include<stdio.h> int main(){ double x,y,z,s,p,a; scanf("%lf%lf%lf" ...
- 学习笔记之FluentAssertions
dotnet/src/MoqSample at master · haotang923/dotnet · GitHub https://github.com/htanghtang/dotnet/tre ...
- Oracle 同一个字段的两值进行加减计算
如 病人ID 入院日期 出院日期 00001 2016-09-01 2016-09-10 00001 2016-09-15 ...
- sqoop产生背景及概述
sqoop产生背景 多数是用Hadoop技术处理大数据业务的企业有大量的数据存储在传统的关系型数据库(RDBMS)中:由于缺乏工具的支持.对Hadoop和传统数据库系统中的数据进行相互传输是一件十分困 ...
- CSS宽度高度的百分比取值基于谁
width=num% , height=num% 基于以下几点 1. 若元素不存在定位: 则基于直接父元素的宽高度 2. 若元素存在定位 且 定位为 relative, 则也基于直接父元素的宽高度 3 ...
- relocation error: /usr/lib64/libc.so.6: symbol _dl_starting_up, version GLIBC_PRIVATE not defined in file ld-linux-x86-64.so.2 with link time reference 问题解决
在建立一个错误的软连接到ld-linux-x86-64.so.2时,悲剧就这么发生了.此时大部分命令都不能使用,SSH当然也不能登录了.这个时候一定不要退出终端. 有人说那就把软连接复原吧,可是ln也 ...