近期又在啃《Android开发艺术探索》这本书,最近看到了第六章节—Android中的Drawable。我写博客的风格不喜欢一味的介绍理论知识,更喜欢从实战的角度去学习,在敲代码的过程中去补充理论知识,根据实际情况做出分析,最后实现想要的效果。本文就从制作圆形头像的角度,来学习Android中的Drawable的那些事。
一.准备工作
Drawable有很多种,表示的是一种可以在Canvas上进行绘制的抽象的概念,它的种类有很多种,最常见的颜色和图片都可以是一个Drawable。它是所有Drawable对象的基类,每个具体的Drawable都是它的子类。比如下文提到的BitmapDrawable。
BitmapDrawable是一种最简单的Drawable,它表示的就是一张图片,在实际开发中应用也是最广泛的,我们可以直接引用原始图片即可获取。
回到需求上,我们要制作一个圆形头像,首先需要一张图片,最终以圆形的方式展现出来。
这是我在网上找的一张图片:
制作圆形图片,最理想的效果是原始图片能够是正方形的,这样图片裁剪成圆形以后才会显得对称。而提供给我们的原始图片很有可能不是正方形的,比如上图,所以我们首先要加工这张图片,使成为正方形图片。
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_third"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="org.tyk.android.artstudy.ThirdActivity">
<ImageView
android:id="@+id/my_circle_img"
android:layout_width="100dp"
android:layout_height="150dp"
android:layout_marginTop="10dp"
android:src="@drawable/k" />
<ImageView
android:id="@+id/second_img"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_marginTop="10dp" />
<Button
android:id="@+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="缩放" />
</LinearLayout>
布局文件中,上面是我们的原始图片,下面是我们处理以后的图片,看看点击事件做了什么:
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可参考:
自定义控件其实很简单1/3
这里我们结合圆形图像的例子看看怎么使用:
//缩放以后的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,一起交流进步!