第七章 图形与图像处理
Android的图形处理基础
Bitmap和BitmapFactory
继承View在Android中绘图
掌握Canvas、Paint、Path等绘图API
双缓存机制
使用Matrix对图像进行几何变换
通过drawBitpamMesh方法扭曲图像
使用不同的Shader类渲染图形
逐帧动画
补间动画
属性动画
开发自定义补间动画
SurfaceView的绘图机制
继承SurfaceView开发动画
1、Bitmap和BitmapFactory
Android应用中的图片不仅包括*.png,*.jpg,*.gif等各种格式的位图,还包括使用XML资源文件定义的各种Drawable对象。
Bitmap提供了一些静态方法来创建新的Bitmap对象:
createBitmap(Bitmap source,int x,int y,int width,int height)
createScaledBitmap(Bitmap src,int desWidth,int dstHeight,boolean filter)
createBitmap(int width,int height,Bitmap.Config config)
createBitmap(Bitmap source,int x,int y,int width,int height,Matrix m,boolean filter)
BitmapFactory是一个工具类,它提供了大量的方法从不同的数据源来解析、创建Bitmap对象:
decodeByteArray(byte[] data,int offset,int length)
decodeFile(String pathName)
decodeFileDescriptor(FileDescriptor fd):用于从FileDescriptor对应的文件中解析、创建Bitmap对象
decodeResource(Resource res,int id)
decodeStream(InputStream is)
Android为Bitmap提供了两个方法判断它是否已回收
boolean isRecyled()
void recycle()
2、绘图
Canvas
Canvas代表依附于View的画布
一些画各种图形的方法
Paint
Canvas上的画笔,主要用来设置绘制风格,包括画笔颜色,画笔笔触粗细、填充风格等,
Path
Path代表任意多条直线连接而成的任意图形,当Canvas根据Path绘制时,它可以绘制出任意的图形
public void moveTo (float x, float y):设置path的开始坐标
public void lineTo (float x, float y):在设定的点和原先的点之间画一条线,如果之前没有调用moveTo(),那么原先的点默认为(0,0)
public void close ():关闭当前画的轮廓,如果当前的点不是刚开始的点,则自动在这两点之间画一天线。
之后就可以调用Canvas的drawPath(path,paint)方法沿着路径绘制图形。实际上Android还为路径绘制提供了PathEffect来定义绘制效果。
PathEffect:
ComposePathEffect
CornerPathEffect
DashPathEffect
DiscretePathEffect
PathDashPathEffect
SumPathEffect
通过paint.setPathEffect(PathEffect pe);给画笔设置效果
画跟随Path的字体:Canvas.drawTextOnPath(DRAW_STR,path,int ,int,paint)
通知View重绘可以调用invalidate(UI线程中),在非UI线程中需要调用postInvalidate();
所谓的双缓冲技术:当程序需要在指定的View上进行绘制时,程序并不直接绘制到该View组件上,而是先绘制到内存中的一个Bitmap图片(这就是缓冲区)上,等到内存中的Bitmap绘制好后,再一次性地将Bitmap绘制到View组件上。具体实现就是,在构造方法中初始化缓冲需要的Canvas和Paint,监听触摸事件,绘制Bitmap,缓冲Bitmap绘制完成后调用invalidate,将Bitmap绘制到组件的Canvas上。
3、图形特效处理
使用Matrix控制变换
Matrix是Android提供的一个矩阵工具类,它本身并不能对图形或组件进行变换,但它可以与其他API结合来控制图形、组件的变换。
使用步骤:
获取Matrix对象,该Matrix对象即可新创建,也可直接获取其他对象内封装的Matrix
调用Matrix的方法进行平移、旋转、缩放、倾斜等(setTranslate(),setSkew(),setRotate(),setScale())
将程序对Matrix所做的变换应用到指定的图形或组件(Canvas.drawBitmap(Bitmap bitmap,Matrix matrix,Paint paint))
使用drawBitmapMesh扭曲图像
public void drawBitmapMesh (Bitmap bitmap, int meshWidth, int meshHeight, float[] verts, int vertOffset, int[] colors, int colorOffset, Paint paint)
bitmap:指定需要扭曲的源位图。
meshWidth:该参数控制在横向上把该源位图划分为多少格
meshHeight:该参数控制在纵向上把该源位图划分为多少格
verts:该参数是一个长度为(meshWidth + 1) * (meshHeight + 1) * 2 的数组,它记录了扭曲后的位图各“顶点“的位置,虽然它是一个一维数组,但实际上它记录的数据是形如(x0,y0)、(x1,y1)、(x2,y2)、...、(xn,yn)格式的数据,这些数组元素控制对Bitmap位图的扭曲效果。
vertOffset:控制verts数组中从第几个数组元素开始才对bitmap进行扭曲(忽略verOffset之前数据的扭曲效果)
使用Shader填充图形
通过Paint.setShader(Shader s)方法,给画笔设置渲染效果——Android不仅可以使用颜色填充图形,也可以使用Shader对象指定的渲染效果来填充图形。Shader本身是一个抽象类,它提供了如下实现类:
BitmapShader:使用位图平铺的渲染效果。
LinearGradient:使用线性渐变来填充图形。
RadialGradient:使用圆形渐变来填充图形。
SweepGradient:使用角度渐变来填充图形。
ComposeShader:使用组合渲染效果来填充图形。
4、动画
帧(Frame)动画
AnimationDrawable
使用
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item
android:drawable="@mipmap/ic_launcher"
android:duration="30"/>
<item
android:drawable="@mipmap/ic_launcher"
android:duration="30"/>
<item
android:drawable="@mipmap/ic_launcher"
android:duration="30"/>
</animation-list>
android:oneshot:控制该动画是否循环播放,true表示不会循环播放,否则将会循环播放
也可以使用Java代码定义所有关键帧,需要先创建AnimationDrawable对象,通过addFrame()向该动画添加关键帧。
// Load the ImageView that will host the animation and
// set its background to our AnimationDrawable XML resource.
ImageView img = (ImageView)findViewById(R.id.spinning_wheel_image);
img.setBackgroundResource(R.drawable.spin_animation);
// Get the background, which has been compiled to an AnimationDrawable object.
AnimationDrawable frameAnimation = (AnimationDrawable) img.getBackground();
// Start the animation (looped playback by default).
frameAnimation.start();
AnimationDrawable提供了两个方法控制动画的播放
start();
stop();
补间(Tween)动画
补间动画就是指开发者只需指定动画开始、结束等”关键帧“,而动画变化的”中间帧“由系统计算并补齐。
Tween动画和Interpolator
AlphaAnimation:
ScaleAnimation:
TranslateAnimation:
RotateAnimation:
一旦为补间动画指定了三个必要信息,Android就会根据动画的开始帧、结束帧、动画持续时间计算出需要在中间"补入"多少帧,并计算所有补入帧的图形。当用户浏览补间动画时,他眼中看到的依然是"帧动画"。
为了控制在动画期间需要动态"补入"多少帧,具体在动画运行的哪些时刻补入帧,需要借助于Interpolator。
Interpolator:差值器。根据特定算法计算出整个动画所需要动态插入帧的密度。简单来说就是控制动画的变化速度,使基本的动画效果(Alpha,Translate,Scale,Rotate)能以匀速变换、加速、减速、抛物线速度等各种速度变化。
Interpalator是一个接口,它定义了所有Interpolator需要实现的方法:float getInterpolation(float input),开发者完全通过实现Interpolator来控制动画的变化速度。
LinearInterpolator:动画匀速改变
AccelerateInterpolator:动画开始的地方改变速度较慢,然后开始加速
AccelerateDecelerateInterpolator:在动画开始、结束的地方改变速度较慢,在中间时候加速。
CycleInterpolator:动画循环播放特定的次数,变化速度按正弦曲线改变
DecelerateInterpolator:在动画开始的房速度较快,然后开始一减速
这些属性可以再资源文件中定义:android:interpolator属性
android定义了默认支持的Interpolator:@android:anim/linear_interpolator ...
自定义补间动画
自定义补间动画,需要基础Animation,重写抽象基类的appleTransformation(float interpolatedTime,Transformation t)方法,
InterpolatedTime:代表了动画的时间进行比。不管动画实际的持续事件如何,当动画播放时,该参数总是自动从0变化到1.
Transformation:代表了补间动画在不同时刻对图形或组件的变化程度。
属性动画
Animator:它提供创建属性的动画的基类;
ValueAnimator(继承Animator):
ObjectAnimator(继承自ValueAnimator):
AnimatorSet(继承Animator):
除此之外,属性动画还需要利用一个Evaluation(计算器),该工具类控制属性动画如何计算属性值。
IntEvaluator:
FloatEvaluator:
ArgbEvaluator:
TypeEvaluator:
ValueAnimator va = ValueAnimator.ofFloat(0f,1f);
va.setDuration(1000);
va.start()
如果希望使用ValueAnimator创建动画,还需要注册一个监听器:AnimatorUpdateListener,该监听器负责更新对象的属性值,
ObjectAnimator oa = ObjectAnimator.ofFloat(foo,"alpha",0f,1f);
oa.setDuration(1000);
oa.start();
ObjectAnimator继承了ValueAnimator,因此它可以直接将ValueAnimator在动画过程中计算出来的值应用到指定的对象的制定的属性上,即不用注册监听器了。
使用ObjectAnimator要注意的:
要为该对象对应的属性提供setter方法,如上例中的foo对象提供setAlpha(float value);
调用ObjectAnimator的ofInt()、ofFloat()、ofObject()工厂方法时,如果value... 参数只提供了一个值(本来需要提供开始值和结束值),那么该值会被认为是结束值,该对象应该为该属性提供一个getter方法,该getter方法的返回值被作为开始值。
如果动画对象的View,为了能显示动画效果,可能还需要在onAnimationUpdate()事件监听方法中调用View.invalidate()方法来刷新屏幕的显示。
使用SurfaceView实现动画
View绘图机制存在如下缺陷:
View缺乏双缓存机制。
当程序需要更新View上的图片时,程序必须重绘View上显示的整张图片。
新线程无法直接更新View组件
特别是在游戏相关方面,一般使用SurfaceView替代View
SurfaceView的绘图机制
SurfaceView一般会与SurfaceHolder结合使用,SurfaceView用于向与之关联的SurfaceView上绘图,调用SurfaceView的getHolder()方法即可获取SurfaceView关联的SurfaceHolder。
SurfaceHolder提供如下两个方法获取Canvas对象:
Canvas lockCanvas():锁定整个SurfaceView对象,获取该SurfaceView上的Canvas
Canvas lockCanvas(Rect dirty):锁定SurfaceView上Rect划分的区域,获取该SurfaceView上的Canvas
当同一个SurfaceView调用上面两个方法是,两个方法返回的是同一个Canvas对象。但当程序调用第二个方法获取指定区域的Canvas时,SurfaceView将只对Rect所“圈”出来的区域进行更新,通过这种方式可以提高画面的更新速度。
当通过LockCanvas()获取指定SurfaceView上的Canvas之后,接下来程序就可以调用Canvas进行绘图了,Canvas绘图完成后通过如下方法来释放绘图、提交所绘制的图形:
unlockCanvasAndPost(canvas)
Note:当调用SurfaceHolder的unlockCanvasAndPost(canvas)方法后,该方法之前所绘制的图形还处于缓冲区内,下一次lockCanvas()方法锁定的区域可能会“遮挡”它
SurfaceHolder.Callback
SurfaceView 与普通View一个重要区别:View的绘图必须在UI线程中进行,当SurfaceView就不会,因为SurfaceView的绘图是由SurfaceHolder完成的。