一、前期基础知识储备
笔者在第一课相关讲解中,对Bitmap的加载、高效加载和图片压缩进行了详细的解析,感兴趣的读者可以参考下《Bitmap精炼详解第(一)课:Bitmap解析和加载》。通过前面内容的学习,我们已经掌握了将Bitmap高效的加载出来,当然了实际开发中,产品经理是不可能仅仅满足于将图片加载出来,那样应用程序不够吸引人,在市面上的竞争力也没有那么强,所以我们在将Bitmap加载出来之后,要进行进一步的处理,做出用户体验更好,界面更吸引人的效果。
常见的Bitmap的处理包括:圆角图片、圆形图片、图片截取、图片透明度、图片色调。
二、上代码,具体实现
(1)自定义一个view—BitmapEffect
package com.example.administrator.animation_practice;
public class BitmapEffect extends View {
private Bitmap bitmap;
public BitmapEffect(Context context) {
super(context, null);
}
public BitmapEffect(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BitmapEffect(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public BitmapEffect(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(toRoundCorner(bitmap,10) ,0 ,0 ,null);
}
public Bitmap toRoundCorner(Bitmap bitmap, int pixels) {
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bv);
Bitmap roundCornerBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(roundCornerBitmap);
int color = 0xff424242;// int color = 0xff424242;
Paint paint = new Paint();
paint.setColor(color);
// 防止锯齿
paint.setAntiAlias(true);
Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
RectF rectF = new RectF(rect);
float roundPx = pixels;
// 相当于清屏
canvas.drawARGB(0, 0, 0, 0);
// 先画了一个带圆角的矩形
canvas.drawRoundRect(rectF, 30, 30, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 再把原来的bitmap画到现在的bitmap
canvas.drawBitmap(bitmap, rect, rect, paint);
return roundCornerBitmap;
}
}
(2)创建一个新Activity加载自定义的View
package com.example.administrator.animation_practice;
import android.app.Activity;
import android.os.Bundle;
/**
* Created by Administrator on 2018/4/2 0002.
* powered by Cpf.com.
*/
public class BitmapEffectTest extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
BitmapEffect bitmapEffect = new BitmapEffect(this);
setContentView(bitmapEffect);
}
}
(3)改换边框的代码,设置为圆形图片
canvas.drawCircle(500,500,400,mPaint);
运行效果如图:
小结:这里以两个设置图片的边框特效为例,讲解了Bitmap的常用用法。可能初学Bitmap的读者对于这部分内容会有些疑惑:1)Canvas;2)PorterDuffXfermode。
下面笔者来具体讲解一下——我们来看看自定义的toRoundCorner()方法里面的四行关键代码
//第一行代码——获得图片
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bv);
//第二行代码——获得Bitmap对象
Bitmap roundCornerBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
//第三行代码——装载画布
Canvas canvas = new Canvas(roundCornerBitmap);
//第四行代码——绘制Bitmap
canvas.drawBitmap(bitmap, rect, rect, paint);
l 第一行代码就是我们在Bitmap精炼详解第(一)课中学到的Bitmap图片的加载,这里我们使用的是从本地资源文件里加载的方式,比较简单就没有使用采样率,调用这行代码之后,我们加载了这张图片,也即我们获得了这张图片;
l 第二代码是我们要重点理解的代码之一,这段代码调用了Bitmap的标准方法createBitmap()方法,传入刚刚获取到的图片,最后得到的了Bitmap实例(注意这里,第一行代码中我们得到的是这张图片,第二行代码我们得到的是这张图片的实例对象);
l 第三行代码是创建canvas实例的标准方法,canvas是onDraw()方法里的唯一参数,谷歌官方给出的创建canvas实例的标准方法就是Canvas canvas = new Canvas(bitmap),要求传入一个Bitmap对象,而这里传入的Bitmap实例对象就是我们通过第二行代码创建的,创建了canvas对象,传入了bitmap实例,就相当于把bitmap和canvas绑定了,这样后来调用canvas的draw.XXXX()方法,就可以直接在bimap上进行操作了——which is normal in PS,这种方法在PS中非常常见,“在画布上添加一张图片,然后在对这张图片做各种处理”,比如在本例中,就是给这张图片加上一个类似于“边框”的效果;
l 第四行代码就是toRoundCorner()方法的终点站——利用canvas实例对象调用drawBitmap()方法绘制出Bitmap。这里调用的时机是在canvas.drawRoundRect()之后,就是已经在这个Bitmap中画上了圆角矩形或者圆形,并且已经定义了“混合区域”的显示方式,所以最终显示的就是一张加了圆角矩形或者圆形的“边框”的图片;
最后在onDraw()方法体中再次调用canvas.drawBitmap()方法,并且传入toRoundCorner()方法需要的参数,我们就可以画出一个加了“边框”特效的图片。
另,PorterDuffXfermode,这个东西是属于画笔特效的范畴,在开发中常用的作用就是定义2张图片“混合区域”的显示方式,而最常用的就是实现一个圆角矩形或者一个圆形边框的效果。
————————————————————我是分隔线—————————————————————
三、使用Bitmap的其他注意事项
(1)不用的Bitmap及时释放
if (!bmp.isRecycle()) {
bmp.recycle(); //回收图片所占的内存
bitmap = null;
system.gc(); //提醒系统及时回收
}
虽然调用recycle()并不能保证立即释放占用的内存,但是可以加速Bitmap的内存的释放。释放内存以后,就不能再使用该Bitmap对象了,如果再次使用,就会抛出异常。所以一定要保证不再使用的时候释放。比如,如果是在某个Activity中使用Bitmap,就可以在Activity的onStop()或者onDestroy()方法中进行回收。
(2)捕获异常
Bitmap bitmap = null;
try {
// 实例化Bitmap
bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
}
if (bitmap == null) {
return defaultBitmapMap; // 如果实例化失败 返回默认的Bitmap对象
}
因为Bitmap非常耗内存,了避免应用在分配Bitmap内存的时候出现OutOfMemory异常以后Crash掉,需要特别注意实例化Bitmap部分的代码。通常,在实例化Bitmap的代码中,一定要对OutOfMemory异常进行捕获。很多开发者会习惯性的在代码中直接捕获Exception。但是对于OutOfMemoryError来说,这样做是捕获不到的。因为OutOfMemoryError是一种Error,而不是Exception。
(3)缓存通用的Bitmap对象
有时候,可能需要在一个Activity里多次用到同一张图片。比如一个Activity会展示一些用户的头像列表,而如果用户没有设置头像的话,则会显示一个默认头像,而这个头像是位于应用程序本身的资源文件中的。如果有类似上面的场景,就可以对同一Bitmap进行缓存。如果不进行缓存,尽管看到的是同一张图片文件,但是使用BitmapFactory类的方法来实例化出来的Bitmap,是不同的Bitmap对象。缓存可以避免新建多个Bitmap对象,避免内存的浪费。在Android应用开发过程中所说的缓存有两个级别,一个是硬盘缓存,一个是内存缓存。
(4)Android加载大量图片内存溢出解决方案
1)开发者在实际开发过程中尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存,可以通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source;
2)使用BitmapFactory.Options对图片进行压缩(上述第二部分);
3)运用Java软引用,进行图片缓存,将需要经常加载的图片放进缓存里,避免反复加载;
总结:本节内容较多,然后笔者写着写着也出现了疑问,为什么要采用Bitmap,不直接使用ImageView?其实,这个问题挺傻的,因为ImageView加载出来只能是矩形图片,只能是原始矩形图片,而要对图片进行一系列的操作,就必须借用Bitmap,把PNG\JPG的图片转换成Bitmap,然后调用Canvas和Bitmap的一系列方法,对这张图片进行处理。(╯‵□′)╯︵┻━┻为什么UI不直接做好效果给我们呢?因为UI只能做好内置的图片,而真实用户手机里的只有原始未处理的矩形图片,我们开发者要写好代码,然后从网络/本地中获取用户的图片,加载后再对其进行处理,最终显示为一个更加好的界面元素。伟大的开发者!
。୧(๑•◡•๑)૭୧(๑•◡•๑)૭(๑•ㅂ•)و✧(๑•ㅂ•)و✧——开发者就是我们!