本文主要对Android开发中图形图像部分的相关内容作个简单的学习总结。
一、概述
二、ImageView详解
三、Bitmap详解
四、Canvas详解
五、SurfaceView简介
六、总结
一、概述
在我们的应用开发中,图形图像处理往往是不可避免要接触的内容。一个完整的App,文字、图像、动画等都是用户交互的重要组成元素。本文主要从ImageView开始,对Android开发中与图形图像处理相关的内容作个简单的总结。
二、ImageView用法与解析
1、ImageView简介
ImageView的用途相信大家都已经十分的熟悉,应用中图标或者图片的展示一般都需要用到这个控件。SDK文档中对它的介绍,大致翻译是ImageView,图像视图,是用来展示一个任意的图像的控件,例如图标。它可以从不同的来源载入图像,控制图像的大小,并提供多种展示选项,比如缩放比例或者着色器等。
2、ImageView用法
首先我们看下ImageView一个最简单的使用。
<ImageView
android:id="@+id/iv_scaletype_test"
android:layout_width="200dp"
android:layout_height="200dp"
android:src="@drawable/img_test" />
当在布局文件里指定ImageView的宽高以及src属性后,一张图片就可以展示在ImageView上了。如果是用Java代码动态控制,那么src属性可以用方法setImageResource来完成。
当然,我们也可以配置很多其它属性。常用的有scaleType,了解它之前我们来看两张对比图。
可以看到,scaleType的不同设置可以控制图像在ImageView中的显示样式。
scaleType的取值共有8种,分别是:
- MATRIX,意思是“矩阵”,即用矩阵来绘制,不缩放
- FIT_XY,不按比例缩放图片,把图片塞满整个ImageView。
- FIT_START,置顶,图片显示在ImageView的左上角start的位置
- FIT_CENTER,居中
- FIT_END,置底,图片显示在ImageView的右下角end的位置
- CENTER,按图片的原来尺寸居中显示,当图片长/宽超过ImageView的长/宽,则截取图片的居中部分显示
- CENTER_CROP,按比例扩大图片的size居中显示,使得图片长(宽)等于或大于ImageView的长(宽)
- CENTER_INSIDE,图片完整居中显示,比例缩小或原来的size使得图片长/宽等于或小于ImageView的长/宽
一图胜千言,请看:
在未设置scaleType时,系统默认保证图片完整、居中显示,并等比缩放,其效果跟android:scaleType=”fitCenter”的效果一致。
在代码里控制图片的缩放样式是用方法setScaleType来设置的。可以看到,用好这个属性,可以明显地改善图片在界面上的显示效果。
在ImageView的使用过程中,还有个tint属性可以设置。tint翻译是着色,它可以用来改变ImageView中内容的颜色,在代码中对应的设置方法是imageview.setColorFilter(Color),我们可以利用这个属性来修改一些图标的显示颜色。
三、Bitmap详解
Bitmap,翻译为位图,它是一个final类,Android系统图像处理中最重要的类之一。Bitmap可以获取图像文件信息,对图像进行剪切、旋转、缩放,压缩等操作,并可以以指定格式保存图像文件。在实际使用过程中,因为Bitmap很占内存,所以需要注意进行压缩,高效加载避免OOM。
Bitmap类比较特别,我们不能通过它的构造方法来实例化,只能通过Bitmap的静态方法或者借助BitmapFactory的静态方法来实例化。类BitmapFactory,它可以让我们从不同的来源,比如文件,流,字节数组等来源中创建Bitmap对象。下面是BitmapFactory的类结构。可以看到,它提供了一系列从不同来源实例化Bitmap的方法。
下面看个简单的例子。
上图中,已经用OkHttp网络请求获取到了字节数组,在Handler中处理消息时,先获取到字节数组,然后利用BitmapFactory的decodeByteArray方法,实例化Bitmap,最后通过ImageView的setImageBitmap方法将Bitmap设置为ImageView的内容。
Bitmap特别耗内存,所以我们在使用中要十分注意优化。常见的措施有,及时调用recycle()进行内存回收,实例化Bitmap时进行图片压缩,以及采取图片缓存等。
下面是在使用BitmapFactory的静态方法实例化Bitmap时进行压缩。
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 4;
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
可以看到上面的代码实例化参数Options时设置inSampleSize为4,这样实例化的Bitmap占用的内存只为原来的1/16。
1.Bitmap的内存占用
上面我们提到,在使用类BitmapFactory加载Bitmap时指定Options可以有效的优化Bitmap的内存占用,那么Bitmap的内存占用具体怎么计算的呢?这里有个公式:
占用内存 = 图片宽 * 图片高 * 单位像素占用的内存
这里简单说明一下,图片的宽和高比较容易理解,单位像素的占用内存一般是由图片加载时的色彩模式决定的,android默认的色彩模式是ARGB_8888,一个像素占用的内存是4个字节。
这里还需要注意的是,图片的来源也会最终影响占用内存。如果我们直接从网络上获取图片,那么图片的占用内存就是上面的公式。但是如果是从drawable目录下加载图片,那么占用内存会因为不同的drawable目录而不同,因为Android系统在加载不同drawable目录下的图片到不用分辨率的机型上时会进行一定的缩放。
2.Bitmap的高效加载
BitmapFactory加载Bitmap时指定Options是一个比较好的习惯,那么Options的inSampleSize指定多少比较合适呢?其实最理想的情况是我们根据需要显示的ImageView的大小计算出Options的inSampleSize。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(res, resId, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
return BitmapFactory.decodeResource(res, resId, options);
}
private static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int halfHeight = height / 2;
final int halfWidth = width / 2;
while ((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *= 2;
}
}
return inSampleSize;
}
3.Bitmap的缓存
项目中如果需要加载图片,那么图片缓存的设计非常重要。一般来说,图片缓存会涉及到内存缓存和磁盘缓存,当需要加载图片时,先看内存中有没有,没有的话再看磁盘缓存中有没有,最后再尝试网络请求获取图片。
内存缓存和磁盘缓存目前比较常用的算法是LRU(Least Recently Used),即近期最少使用算法,内存缓存我们可以借助LruCache这个类来完成,磁盘缓存可以用DiskLruCache,它们共同的原理是指定一个缓存的总大小,当缓存满时,优先淘汰那些近期最少使用的缓存的对象。
四、Canvas解析
说到图形图像,那么肯定离不开要谈Canvas这个类。当我们想要绘制自己的图形时,或者在自定义View中,一般都会用到它。Canvas,直译为画布,SDK文档对它的介绍如下:
The Canvas class holds the "draw" calls. To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls (writing into the bitmap), a drawing primitive (e.g. Rect, Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).
Canvas类包含很多draw的方法,要画某样东西,一般需要4个基本元素:一是Bitmap,用来包含像素。二是Canvas,包含画的方法。三是画的参数,比如宽高等属性。四是Paint,画笔,用来描述绘画的颜色和样式。
同样的,我们来看个简单的例子。
public class MyDrawView extends View {
private Paint mPaint;
public MyDrawView(Context context) {
super(context);
mPaint = new Paint();
mPaint.setColor(Color.RED);// 设置画笔颜色为红色
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPaint.setTextSize(50f);
canvas.drawText("画圆:", 100, 200, mPaint);// 画文本
canvas.drawCircle(300, 200, 80, mPaint);// 小圆
mPaint.setAntiAlias(true);// 设置画笔的锯齿效果
canvas.drawCircle(600, 200, 150, mPaint);// 大圆
canvas.drawText("画线:", 100, 600, mPaint);
canvas.drawLine(600, 400, 1000, 400, mPaint);// 画线
canvas.drawLine(600, 400, 1000, 800, mPaint);// 斜线
}
}
在上面的代码中,我们创建了一个继承自View的子类,构造函数里我们初始化了画笔Paint,设置颜色为红色。在onDraw()方法里,我们使用Canvas内部的几个draw方法绘制不同的图形,drawText是绘制文本,drawCircle是绘制圆...
最后效果如下:
除了绘制各种基本的图形,我们也可以通过drawBitmap来绘制图片。实际开发中,利用Canvas、Paint等类,我们可以创造出很出酷炫的自定义图形。
五、SurfaceView简介
自己之前对SurfaceView这个类不是很熟悉,这次特地查阅了官方文档结合相关资料,这里简单地作个介绍。
View在大部分情况下可以满足我们的绘图需求,但是当需要频繁刷新,或者刷新时数据量比较大时,容易造成画面卡顿,SurfaceView是用来在上述两种情况下替代View的。
下面是官方文档的介绍
Provides a dedicated drawing surface embedded inside of a view hierarchy. You can control the format of this surface and, if you like, its size; the SurfaceView takes care of placing the surface at the correct location on the screen
SurfaceView提供了一个专门用于绘图的surface,它嵌入在View的内部。我们可以控制这个surface的格式和大小,SurfaceView主要负责把surface放置在屏幕上的正确位置。
SurfaceView可以直接从内存或者DMA等硬件接口取得图像数据,因此是个非常重要的绘图容器。
六、总结
在实际开发中,我们可以为显示的图形图像增加很多的特效,这部分内容这里就不详细介绍了。总的来说,我们可以利用ColorMatrix颜色矩阵类来处理图像的色彩效果,Android系统利用矩阵来进行图像的图形变换,当然,我们还可以充分地利用Android中的动画来完成图形图像的动态效果展示。
关于Android中的图形图像部分的学习总结就是这些,当然实践出真知,只有在实际开发中不断摸索不断尝试,才能更好地掌握Android中的这些图形图像处理,创造出更多更酷炫的自定义效果。