前言:
上一篇android自定义view-打造圆形ImageView(一)中介绍了如何用BitmapShader渲染来绘制圆形圆角ImageView,我们今天采用Xfermode来进行处理,因为相比较而言Xfermode更为常见,更为强大。
知识准备:
我们需要对Xfermode有一定的了解,百度Xfermode,你会看见很多有关它的介绍,我这边就来总结一下。
Xfermode有三个子类 :
- AvoidXfermode 指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图)。
- PixelXorXfermode 当覆盖已有的颜色时,应用一个简单的像素异或操作。
- PorterDuffXfermode 这是一个非常强大的转换模式,使用它,可以使用图像合成的16条Porter-Duff规则的任意一条来控制Paint如何与已有的Canvas图像进行交互。
Part1:AvoidXfermode
AvoidXfermode不支持硬件加速,在高于API16的机器上不会适用,如果想测试这个子类
- 可以关闭手机的硬件加速模块。
- 在AndroidManifest.xml中Application节点上设置硬件加速为false。
来看一下构造方法:
复制代码
AvoidXfermode的构造方法也特别简单,一共接收3个参数:第一个参数opColor是一个16进制的带透明度通道的颜色值,如0X12345678。第二个参数tolerance表示容差值,什么是容差值呢?可以理解成一个表示“精确”和“模糊”的概念,下面会解释一下。第三个参数是AvoidXfermode的模式,AvoidXfermode的模式一共有两种:AvoidXfermode.Mode.TARGET和AvoidXfermode.Mode.AVOID。
1public AvoidXfermode(int opColor, int tolerance, Mode mode)
Part2:PixelXorXfermode
PixelXorXfermode是Xfermode下的另外一种图像混排模式,该类特别简单,不过呢,也很不幸的,在API16中已经过时了。我们来做一个简单的了解,先看PixelXorXfermode的构造方法:
复制代码
构造方法很简单,只要传递一个16进制带透明通道的颜色值即可,那么这个参数有什么用呢?我在Google文档中,找到了这样的一个算法:实际上PixelXorXfermode内部是按照“opColor ^ src ^ dst”这个异或算法运算的,得到一个不透明的(alpha = 255)的色彩值,设置到图像中
1public PixelXorXfermode(int opColor)
Part3:PorterDuffXfermode
对于这个经常用的类,我只想用官方的一张图说明即可。
大家一定要好好看这张图,总共有16种模式。我还是贴一下这16种模式的解释吧,以后还可以回顾参考一下。
从上面我们可以看到PorterDuff.Mode为枚举类,一共有16个枚举值:
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
取两图层全部区域,交集部分变为透明色
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
取两图层全部区域,交集部分变为透明色
正文:
知识准备已经全部讲好了,我们今天采用的是DST_IN模式。接下来开始我们的代码部分,先把效果图奉上吧:
Step1:继承ImageView
复制代码
Step2:自定义属性
1public class XfermodeRoundImageView extends ImageView
我这边是在上一篇的基础上写的,所以,自定义的属性是在attrs.xml的基础加了一个属性
复制代码
Step3:构造方法中初始化值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16<?xml version="1.0" encoding="utf-8"?> <resources> <attr name="borderRadius" format="dimension" /> <attr name="imageType"> <enum name="circle" value="0" /> <enum name="round" value="1" /> </attr> <declare-styleable name="RoundImageView"> <attr name="borderRadius" /> <attr name="imageType" /> </declare-styleable> <declare-styleable name="XfermodeRoundImageView"> <attr name="borderRadius" /> <attr name="imageType" /> </declare-styleable> </resources>
复制代码
其实这部分和之前没有多大区别,也就没有好讲的了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30public XfermodeRoundImageView(Context context) { this(context, null); } public XfermodeRoundImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public XfermodeRoundImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // 初始化画笔 mPaint = new Paint(); mPaint.setAntiAlias(true); // 获取自定义属性值 TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.XfermodeRoundImageView, defStyle, 0); int count = array.getIndexCount(); for (int i = 0; i < count; i++) { int attr = array.getIndex(i); switch (attr) { case R.styleable.XfermodeRoundImageView_borderRadius: // 获取圆角大小 mBorderRadius = array.getDimensionPixelSize(R.styleable.XfermodeRoundImageView_borderRadius, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BORDER_RADIUS_DEFAULT, getResources().getDisplayMetrics())); break; case R.styleable.XfermodeRoundImageView_imageType: // 获取ImageView的类型 type = array.getInt(R.styleable.XfermodeRoundImageView_imageType, TYPE_CIRCLE); break; } } // Give back a previously retrieved StyledAttributes, for later re-use. array.recycle(); }
Step4:重写onMeasure方法
复制代码
Step5:重写onDraw方法
1
2
3
4
5
6
7
8
9
10@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 如果是圆形,则强制宽高一致,以最小的值为准 if (type == TYPE_CIRCLE) { int mWidth = Math.min(getMeasuredWidth(), getMeasuredHeight()); setMeasuredDimension(mWidth, mWidth); } }
这部分是最关键的一部分。
复制代码
这边我们使用到了缓存的知识,我们不会在每次onDraw的时候都会话费内存在处理相同的bitmap上,所以我们使用了WeakReference<T>来方便GC。对了,这边总共有四个引用方便GC,很多面试的时候可能会遇到这样的问题,什么是强引用,什么是弱引用,它们的使用时机与区别,等等之类,有兴趣的去深入了解一下。上面还用到了一个私有方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50@Override protected void onDraw(Canvas canvas) { // 从缓存中取出图片 Bitmap bitmap = mWeakBitmap == null ? null : mWeakBitmap.get(); // 如果没有缓存或者被回收了,则重新绘制 if (bitmap == null || bitmap.isRecycled()) { // 获取背景drawable Drawable drawable = getDrawable(); // 如果有背景图则绘制 if (drawable != null) { // 拿到drawable的长度和宽度 int dWidth = drawable.getIntrinsicWidth(); int dHeight = drawable.getIntrinsicHeight(); bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888); // 创建画布 Canvas canvas1 = new Canvas(bitmap); // 设置图片缩放比率 float scale = 1.0f; if (type == TYPE_CIRCLE) { scale = Math.max(getWidth() * 1.0f / dWidth, getHeight() * 1.0f / dHeight); } else { scale = getWidth() * 1.0F / Math.min(dWidth, dHeight); } // 缩放图片 drawable.setBounds(0, 0, (int) (scale * dWidth), (int) (scale * dHeight)); // 绘制DST图片 drawable.draw(canvas1); // 绘制SRC图片 if (mMaskBitmap == null || mMaskBitmap.isRecycled()) { mMaskBitmap = drawType(); } // 重置画笔 mPaint.reset(); // 不采用滤波 mPaint.setFilterBitmap(false); mPaint.setXfermode(xfermode); canvas1.drawBitmap(mMaskBitmap, 0, 0, mPaint); // 绘制处理好的图形 mPaint.setXfermode(null); canvas.drawBitmap(bitmap, 0, 0, mPaint); // drawable.draw(canvas); // 缓存图片 mWeakBitmap = new WeakReference<Bitmap>(bitmap); } } if (bitmap != null) { mPaint.setXfermode(null); canvas.drawBitmap(bitmap, 0.0f, 0.0f, mPaint); } }
复制代码
用来绘制我们的SRC的bitmap的,也就是我们的形状。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20/** * 绘制形状,作为src * * @return */ private Bitmap drawType() { Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); // 创建画笔 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.BLACK); // 如果type为圆形 if (type == TYPE_CIRCLE) { canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2, paint); } else { canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), mBorderRadius, mBorderRadius, paint); } return bitmap; }
最后别忘了,在绘制view的时候手动GC一下。
复制代码
我们来重点讲一下上面的逻辑。首先我们需要获取缓存的处理好的bitmap,如果缓存好了,就绘制:
1
2
3
4
5
6
7
8
9
10
11// 在重绘中进行mask和dst的内存回收 @Override public void invalidate() { mWeakBitmap = null; if (mMaskBitmap != null) { mMaskBitmap.recycle(); mMaskBitmap = null; } super.invalidate(); }
复制代码
如果说没有缓存的话,那我们就进行处理缓存,并绘制处理好的bitmap。那么,第一步,我们需要获取我们的背景图,如果没有背景图则不处理,有的话就进行图片的的缩放,绘制DST图片。缩放的scale和上一篇是一样的,不过我们这边的缩放不是经过matrix了,而是drawable.setBounds()方法,设置边界值就是相当于缩放图片了,然后在canvas上将DST图片绘制到bitmap上。接下来,我们需要绘制SRC形状图片,这里我们设置画笔mode,将SRC图片绘制到DST同一块画布中,这时的bitmap就是我们处理好的图片了。只需要重置画笔,将处理好的图片绘制出来即可。
1
2
3
4if (bitmap != null) { mPaint.setXfermode(null); canvas.drawBitmap(bitmap, 0.0f, 0.0f, mPaint); }
完整代码:
XfermodeRoundImageView.java:
复制代码
activity_main.xml:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158package com.beyole.view; import java.lang.ref.WeakReference; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffXfermode; import android.graphics.RectF; import android.graphics.Xfermode; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; import android.widget.ImageView; import com.beyole.roundimageview.R; public class XfermodeRoundImageView extends ImageView { // ImageView类型 private int type; // 圆形图片 private static final int TYPE_CIRCLE = 0; // 圆角图片 private static final int TYPE_ROUND = 1; // 默认圆角宽度 private static final int BORDER_RADIUS_DEFAULT = 10; // 获取圆角宽度 private int mBorderRadius; // 画笔 private Paint mPaint; // 使用缓存机制来保存处理好的bitmap,便于GC private WeakReference<Bitmap> mWeakBitmap; // 设置Xfermode的模式为DST_IN private Xfermode xfermode = new PorterDuffXfermode(Mode.DST_IN); // 蒙板图层 private Bitmap mMaskBitmap; public XfermodeRoundImageView(Context context) { this(context, null); } public XfermodeRoundImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public XfermodeRoundImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); // 初始化画笔 mPaint = new Paint(); mPaint.setAntiAlias(true); // 获取自定义属性值 TypedArray array = context.getTheme().obtainStyledAttributes(attrs, R.styleable.XfermodeRoundImageView, defStyle, 0); int count = array.getIndexCount(); for (int i = 0; i < count; i++) { int attr = array.getIndex(i); switch (attr) { case R.styleable.XfermodeRoundImageView_borderRadius: // 获取圆角大小 mBorderRadius = array.getDimensionPixelSize(R.styleable.XfermodeRoundImageView_borderRadius, (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, BORDER_RADIUS_DEFAULT, getResources().getDisplayMetrics())); break; case R.styleable.XfermodeRoundImageView_imageType: // 获取ImageView的类型 type = array.getInt(R.styleable.XfermodeRoundImageView_imageType, TYPE_CIRCLE); break; } } // Give back a previously retrieved StyledAttributes, for later re-use. array.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // 如果是圆形,则强制宽高一致,以最小的值为准 if (type == TYPE_CIRCLE) { int mWidth = Math.min(getMeasuredWidth(), getMeasuredHeight()); setMeasuredDimension(mWidth, mWidth); } } @Override protected void onDraw(Canvas canvas) { // 从缓存中取出图片 Bitmap bitmap = mWeakBitmap == null ? null : mWeakBitmap.get(); // 如果没有缓存或者被回收了,则重新绘制 if (bitmap == null || bitmap.isRecycled()) { // 获取背景drawable Drawable drawable = getDrawable(); // 如果有背景图则绘制 if (drawable != null) { // 拿到drawable的长度和宽度 int dWidth = drawable.getIntrinsicWidth(); int dHeight = drawable.getIntrinsicHeight(); bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Config.ARGB_8888); // 创建画布 Canvas canvas1 = new Canvas(bitmap); // 设置图片缩放比率 float scale = 1.0f; if (type == TYPE_CIRCLE) { scale = Math.max(getWidth() * 1.0f / dWidth, getHeight() * 1.0f / dHeight); } else { scale = getWidth() * 1.0F / Math.min(dWidth, dHeight); } // 缩放图片 drawable.setBounds(0, 0, (int) (scale * dWidth), (int) (scale * dHeight)); // 绘制DST图片 drawable.draw(canvas1); // 绘制SRC图片 if (mMaskBitmap == null || mMaskBitmap.isRecycled()) { mMaskBitmap = drawType(); } // 重置画笔 mPaint.reset(); // 不采用滤波 mPaint.setFilterBitmap(false); mPaint.setXfermode(xfermode); canvas1.drawBitmap(mMaskBitmap, 0, 0, mPaint); // 绘制处理好的图形 mPaint.setXfermode(null); canvas.drawBitmap(bitmap, 0, 0, mPaint); // drawable.draw(canvas); // 缓存图片 mWeakBitmap = new WeakReference<Bitmap>(bitmap); } } if (bitmap != null) { mPaint.setXfermode(null); canvas.drawBitmap(bitmap, 0.0f, 0.0f, mPaint); } } /** * 绘制形状,作为src * * @return */ private Bitmap drawType() { Bitmap bitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); // 创建画笔 Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setColor(Color.BLACK); // 如果type为圆形 if (type == TYPE_CIRCLE) { canvas.drawCircle(getWidth() / 2, getWidth() / 2, getWidth() / 2, paint); } else { canvas.drawRoundRect(new RectF(0, 0, getWidth(), getHeight()), mBorderRadius, mBorderRadius, paint); } return bitmap; } // 在重绘中进行mask和dst的内存回收 @Override public void invalidate() { mWeakBitmap = null; if (mMaskBitmap != null) { mMaskBitmap.recycle(); mMaskBitmap = null; } super.invalidate(); } }
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:beyole="http://schemas.android.com/apk/res/com.beyole.roundimageview" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.beyole.view.XfermodeRoundImageView android:layout_width="200dip" android:layout_height="200dip" android:src="@drawable/demo" beyole:borderRadius="20dip" beyole:imageType="round" /> <com.beyole.view.XfermodeRoundImageView android:layout_width="200dip" android:layout_height="200dip" android:src="@drawable/demo" beyole:borderRadius="20dip" beyole:imageType="circle" /> </LinearLayout>
github地址:
https://github.com/xuejiawei/beyole_roundimageview 欢迎fork or star
题外话:
android交流群:279031247(广告勿入)
新浪微博:SmartIceberg
最后
以上就是隐形眼睛最近收集整理的关于android自定义view-打造圆形ImageView(二)的全部内容,更多相关android自定义view-打造圆形ImageView(二)内容请搜索靠谱客的其他文章。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复