本文共 7196 字,大约阅读时间需要 23 分钟。
近期又在啃《Android开发艺术探索》这本书,最近看到了第六章节—Android中的Drawable。我写博客的风格不喜欢一味的介绍理论知识,更喜欢从实战的角度去学习,在敲代码的过程中去补充理论知识,根据实际情况做出分析,最后实现想要的效果。本文就从制作圆形头像的角度,来学习Android中的Drawable的那些事。
一.准备工作
Drawable有很多种,表示的是一种可以在Canvas上进行绘制的抽象的概念,它的种类有很多种,最常见的颜色和图片都可以是一个Drawable。它是所有Drawable对象的基类,每个具体的Drawable都是它的子类。比如下文提到的BitmapDrawable。BitmapDrawable是一种最简单的Drawable,它表示的就是一张图片,在实际开发中应用也是最广泛的,我们可以直接引用原始图片即可获取。
回到需求上,我们要制作一个圆形头像,首先需要一张图片,最终以圆形的方式展现出来。
这是我在网上找的一张图片:
制作圆形图片,最理想的效果是原始图片能够是正方形的,这样图片裁剪成圆形以后才会显得对称。而提供给我们的原始图片很有可能不是正方形的,比如上图,所以我们首先要加工这张图片,使成为正方形图片。
布局文件:
布局文件中,上面是我们的原始图片,下面是我们处理以后的图片,看看点击事件做了什么:
btn.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Bitmap bitmap = ((BitmapDrawable) myCircleImageView.getDrawable()).getBitmap(); Matrix matrix = new Matrix(); float size = Math.min(bitmap.getWidth(), bitmap.getHeight()); //x缩放比例 float x = size / bitmap.getWidth(); //y缩放比例 float y = size / bitmap.getHeight(); matrix.setScale(x, y); Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); img.setImageBitmap(newBitmap); } });
首先获取当前ImageView的Bitmap,关于ImageView转换为Bitmap,大家可以参考这篇博客:
然后构造一个矩阵Matrix,计算并设置好缩放比例,然后调用
createBitmap(Bitmap source, int x, int y, int width, int height,
Matrix m, boolean filter)
方法生成新的Bitmap。看看这个方法具体干了什么:
这是官方文档解释,source代表原始图片的Bitmap;x,y代表X,Y方向上的起始位置;width,height代表X,Y方向需要处理的宽度与高度;m代表图片处理的矩阵;,filter参数为true表示进行滤波处理,有助于改善新图像质量,flase代表不做过滤处理。
看看处理后的效果:
OK,图片的前期准备工作已经做好了,现在开始制作圆形头像了。
二.XferMode方法制作圆形图片
Xfermode有三个子类 : AvoidXfermode ,PixelXorXfermode,PorterDuffXfermode,其中前两个类在API 16被遗弃了 。PorterDuffXfermode类主要用于计算图形合成时的图像过渡模式 ,一共有16条规则。然后调用 paint.setXfermode(XferMode)方法设置图像的过渡模式,这样就可以完成一些复杂的效果。先看看有哪些模式:每种模式代表的含义如下:
1.PorterDuff.Mode.CLEAR 所绘制不会提交到画布上。 2.PorterDuff.Mode.SRC 显示上层绘制图片 3.PorterDuff.Mode.DST 显示下层绘制图片 4.PorterDuff.Mode.SRC_OVER 正常绘制显示,上下层绘制叠盖。 5.PorterDuff.Mode.DST_OVER 上下层都显示。下层居上显示。 6.PorterDuff.Mode.SRC_IN 取两层绘制交集。显示上层。 7.PorterDuff.Mode.DST_IN 取两层绘制交集。显示下层。 8.PorterDuff.Mode.SRC_OUT 取上层绘制非交集部分。 9.PorterDuff.Mode.DST_OUT 取下层绘制非交集部分。 10.PorterDuff.Mode.SRC_ATOP 取下层非交集部分与上层交集部分 11.PorterDuff.Mode.DST_ATOP 取上层非交集部分与下层交集部分 12.PorterDuff.Mode.XOR 异或:去除两图层交集部分 13.PorterDuff.Mode.DARKEN 取两图层全部区域,交集部分颜色加深 14.PorterDuff.Mode.LIGHTEN 取两图层全部,点亮交集部分颜色 15.PorterDuff.Mode.MULTIPLY 取两图层交集部分叠加后颜色 16.PorterDuff.Mode.SCREEN 取两图层全部区域,交集部分变为透明色大家也可以参考这篇文章:
这里我们结合圆形图像的例子看看怎么使用:
//缩放以后的bitmap Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); int width = newBitmap.getWidth(); int height = newBitmap.getHeight(); //圆形bitmap Bitmap circleBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(circleBitmap); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.BLACK); canvas.drawCircle(width / 2, height / 2, width / 2, paint); PorterDuffXfermode porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); paint.setXfermode(porterDuffXfermode); canvas.drawBitmap(newBitmap, 0, 0, paint); circleImg.setImageBitmap(circleBitmap);
得到缩放以后的Bitmap以后,创建一个Canvas对象,首先绘制了一个黑色实心圆,然后设置Xfermode为PorterDuff.Mode.SRC_IN,最后绘制缩放以后的Bitmap。根据这种模式的定义:取两层绘制交集并显示上层,可以得到我们的圆形头像。最后实现的效果如下:
三.BitmapShader方法制作圆形图片
BitmapShader是Shader的子类,可以通过Paint.setShader(Shader shader)进行设置,BitmapShader的构造方法如下:
mBitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);
参数1:bitmap
参数2,参数3:TileMode; TileMode的取值有三种: CLAMP 拉伸 拉伸的是图片最后的那一个像素;横向的最后一个横行像素,不断的重复,纵项的那一列像素,不断的重复 REPEAT 重复 就是横向、纵向不断重复这个bitmap MIRROR 镜像 横向不断翻转重复,纵向不断翻转重复关于BitmapShader可参考:
这里我们结合圆形图像的例子看看怎么使用:
//缩放以后的bitmap Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); int width = newBitmap.getWidth(); int height = newBitmap.getHeight(); //圆形bitmap Bitmap circleBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(circleBitmap); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.BLACK); /**第一种方式**/// canvas.drawCircle(width / 2, height / 2, width / 2, paint);// PorterDuffXfermode porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);// paint.setXfermode(porterDuffXfermode);// canvas.drawBitmap(newBitmap, 0, 0, paint); /**第二种方式**/ BitmapShader bitmapShader = new BitmapShader(newBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); paint.setShader(bitmapShader); canvas.drawCircle(width / 2, height / 2, width / 2, paint); circleImg.setImageBitmap(circleBitmap);
得到缩放以后的Bitmap以后,创建一个Canvas对象,初始化BitmapShader,画笔设置Shader,最后在canvas里面进行画圆就行了。效果与第一种方式实现的一样,就不重复贴图了。
四.ClipPath方法制作圆形图片
使用clipPath的方法进行切割,来实现圆角图片,具体可参考:
这里我们结合圆形图像的例子看看怎么使用:
//缩放以后的bitmap Bitmap newBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false); int width = newBitmap.getWidth(); int height = newBitmap.getHeight(); //圆形bitmap Bitmap circleBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(circleBitmap); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(Color.BLACK); /**第一种方式**/// canvas.drawCircle(width / 2, height / 2, width / 2, paint);// PorterDuffXfermode porterDuffXfermode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);// paint.setXfermode(porterDuffXfermode);// canvas.drawBitmap(newBitmap, 0, 0, paint); /**第二种方式**/// BitmapShader bitmapShader = new BitmapShader(newBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);// paint.setShader(bitmapShader);// canvas.drawCircle(width / 2, height / 2, width / 2, paint);// circleImg.setImageBitmap(circleBitmap); /**第三种方式**/ Path path = new Path(); //按照顺时针方向添加一个圆 path.addCircle(width / 2, height / 2, width / 2, Path.Direction.CW); canvas.save(); //设置为在圆形区域内绘制 canvas.clipPath(path); canvas.drawBitmap(newBitmap, 0, 0, paint); canvas.restore(); circleImg.setImageBitmap(circleBitmap);
得到缩放以后的Bitmap以后,创建一个Canvas对象,设置为在圆形区域内绘制,最后在canvas里面绘制Bitmap。效果与第一种方式实现的一样,就不重复贴图了。
当然,要实现其他形状图片,其实很简单,每种方法稍微改变一下就行,只要先绘制出不同的形状,原理还是和这个一样。
OK,源码已同步上传至github,欢迎star,fork,提issues,一起交流进步!