下面是Android ApiDemo里的“Xfermodes”实例,效果图。
Xfermode有三个子类,结构如下:<喎�"http://www.2cto.com/kf/ware/vc/" target="_blank" class="keylink">vcD4KPHA+PC9wPgo8cHJlIGNsYXNzPQ=="brush:java;">public classXfermodeextends Objectjava.lang.Object ? android.graphics.XfermodeKnown Direct SubclassesAvoidXfermode, PixelXorXfermode, PorterDuffXfermode
AvoidXfermode 指定了一个颜色和容差,强制Paint避免在它上面绘图(或者只在它上面绘图)。
PixelXorXfermode 当覆盖已有的颜色时,应用一个简单的像素异或操作。
PorterDuffXfermode 这是一个非常强大的转换模式,使用它,可以使用图像合成的16条Porter-Duff规则的任意一条来控制Paint如何与已有的Canvas图像进行交互。
上面图片种显示的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
取两图层全部区域,交集部分变为透明色
了解了上面的知识点后,我们根据上面的知识点先来实现第一种圆角图片制作方式:
原图:
private
ImageView mImg;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImg = (ImageView) findViewById(R.id.img);
//获得imageview中设置的图片
BitmapDrawable drawable = (BitmapDrawable) mImg.getDrawable();
Bitmap bmp = drawable.getBitmap();
//获得图片的宽,并创建结果bitmap
int
width = bmp.getWidth();
Bitmap resultBmp = Bitmap.createBitmap(width, width,
Bitmap.Config.ARGB_8888);
Paint paint =
new
Paint();
Canvas canvas =
new
Canvas(resultBmp);
//画圆
canvas.drawCircle(width /
2
, width /
2
, width /
2
, paint);
paint.setXfermode(
new
PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 选择交集去上层图片
canvas.drawBitmap(bmp,
0
,
0
, paint);
mImg.setImageBitmap(resultBmp);
bmp.recycle();
}
通过运行上面的代码,我们得出的结果如下:
大家看到这是我们需要的结果。可是这样做可能导致OutOfMomery异常。假如图片很大或者你可能并非通过ImageView的getDrawable获得图像,而是直接Decode一张很大的图片加载到内存,你会发现可能会出现异常。我们做一下改变。
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
|
private
static
final
String TAG =
"RoundImage"
;
private
ImageView mImg;
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImg = (ImageView) findViewById(R.id.img);
// 裁剪图片
BitmapFactory.Options options =
new
BitmapFactory.Options();
options.inJustDecodeBounds =
true
;
BitmapFactory
.decodeResource(getResources(), R.drawable.avatar, options);
Log.d(TAG,
"original outwidth: "
+ options.outWidth);
// 此宽度是目标ImageView希望的大小,你可以自定义ImageView,然后获得ImageView的宽度。
int
dstWidth =
150
;
// 我们需要加载的图片可能很大,我们先对原有的图片进行裁剪
int
sampleSize = calculateInSampleSize(options, dstWidth, dstWidth);
options.inSampleSize = sampleSize;
options.inJustDecodeBounds =
false
;
Log.d(TAG,
"sample size: "
+ sampleSize);
Bitmap bmp = BitmapFactory.decodeResource(getResources(),
R.drawable.avatar, options);
// 绘制图片
Bitmap resultBmp = Bitmap.createBitmap(dstWidth, dstWidth,
Bitmap.Config.ARGB_8888);
Paint paint =
new
Paint();
paint.setAntiAlias(
true
);
Canvas canvas =
new
Canvas(resultBmp);
// 画圆
canvas.drawCircle(dstWidth /
2
, dstWidth /
2
, dstWidth /
2
, paint);
// 选择交集去上层图片
paint.setXfermode(
new
PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bmp,
new
Rect(
0
,
0
, bmp.getWidth(), bmp.getWidth()),
new
Rect(
0
,
0
, dstWidth, dstWidth), paint);
mImg.setImageBitmap(resultBmp);
bmp.recycle();
}
private
int
calculateInSampleSize(BitmapFactory.Options options,
int
reqWidth,
int
reqHeight) {
// Raw height and width of image
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
;
// Calculate the largest inSampleSize value that is a power of 2 and
// keeps both
// height and width larger than the requested height and width.
while
((halfHeight / inSampleSize) > reqHeight
&& (halfWidth / inSampleSize) > reqWidth) {
inSampleSize *=
2
;
}
}
return
inSampleSize;
}
|
再来看一下效果:
首先我们需要了解一个类BitmapShader
引用的介绍如下:
public BitmapShader(Bitmap bitmap,Shader.TileMode tileX,Shader.TileMode tileY)
调用这个方法来产生一个画有一个位图的渲染器(Shader)。
bitmap 在渲染器内使用的位图
tileX The tiling mode for x to draw the bitmap in. 在位图上X方向花砖模式
tileY The tiling mode for y to draw the bitmap in. 在位图上Y方向花砖模式
TileMode:(一共有三种)
CLAMP :如果渲染器超出原始边界范围,会复制范围内边缘染色。
REPEAT :横向和纵向的重复渲染器图片,平铺。
MIRROR :横向和纵向的重复渲染器图片,这个和REPEAT 重复方式不一样,他是以镜像方式平铺。
知道这个原理后,我们贴出对应的代码:
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
|
public
class
CircleImageView
extends
ImageView {
private
static
final
String TAG = CircleImageView.
class
.getSimpleName();
private
Paint mBitmapPaint =
new
Paint();
private
int
mRadius;
public
CircleImageView(Context context, AttributeSet attrs,
int
defStyleAttr) {
super
(context, attrs, defStyleAttr);
init();
}
public
CircleImageView(Context context, AttributeSet attrs) {
super
(context, attrs);
init();
}
public
CircleImageView(Context context) {
super
(context);
init();
}
private
void
init() {
BitmapDrawable drawable = (BitmapDrawable) getDrawable();
if
(drawable ==
null
) {
Log.i(TAG,
"drawable: null"
);
return
;
}
Bitmap bmp = drawable.getBitmap();
BitmapShader shader =
new
BitmapShader(bmp, TileMode.CLAMP,
TileMode.CLAMP);
mBitmapPaint.setShader(shader);
mBitmapPaint.setAntiAlias(
true
);
invalidate();
}
@Override
protected
void
onDraw(Canvas canvas) {
if
(getDrawable() ==
null
) {
return
;
}
mRadius = Math.min(getWidth()/
2
, getHeight()/
2
);
canvas.drawCircle(getWidth() /
2
, getHeight() /
2
, mRadius,
mBitmapPaint);
}
}
|
是不是挺简单的
结果我就不显示了,跟上面的一样。上面也是最原始的代码,文章的结尾贴出一份完整优化过的代码共大家参考如下:
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
|
public
class
CircleImageView
extends
ImageView {
private
static
final
ScaleType SCALE_TYPE = ScaleType.CENTER_CROP;
private
static
final
Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
private
static
final
int
COLORDRAWABLE_DIMENSION =
1
;
private
static
final
int
DEFAULT_BORDER_WIDTH =
0
;
private
static
final
int
DEFAULT_BORDER_COLOR = Color.BLACK;
private
final
RectF mDrawableRect =
new
RectF();
private
final
RectF mBorderRect =
new
RectF();
private
final
Matrix mShaderMatrix =
new
Matrix();
private
final
Paint mBitmapPaint =
new
Paint();
private
final
Paint mBorderPaint =
new
Paint();
private
int
mBorderColor = DEFAULT_BORDER_COLOR;
private
int
mBorderWidth = DEFAULT_BORDER_WIDTH;
private
Bitmap mBitmap;
private
BitmapShader mBitmapShader;
private
int
mBitmapWidth;
private
int
mBitmapHeight;
private
float
mDrawableRadius;
private
float
mBorderRadius;
private
boolean
mReady;
private
boolean
mSetupPending;
public
CircleImageView(Context context) {
super
(context);
init();
}
public
CircleImageView(Context context, AttributeSet attrs) {
this
(context, attrs,
0
);
}
public
CircleImageView(Context context, AttributeSet attrs,
int
defStyle) {
super
(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle,
0
);
mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR);
a.recycle();
init();
}
private
void
init() {
super
.setScaleType(SCALE_TYPE);
mReady =
true
;
if
(mSetupPending) {
setup();
mSetupPending =
false
;
}
}
@Override
public
ScaleType getScaleType() {
return
SCALE_TYPE;
}
@Override
public
void
setScaleType(ScaleType scaleType) {
if
(scaleType != SCALE_TYPE) {
throw
new
IllegalArgumentException(String.format(
"ScaleType %s not supported."
, scaleType));
}
}
@Override
protected
void
onDraw(Canvas canvas) {
if
(getDrawable() ==
null
) {
return
;
}
canvas.drawCircle(getWidth() /
2
, getHeight() /
2
, mDrawableRadius, mBitmapPaint);
if
(mBorderWidth !=
0
) {
canvas.drawCircle(getWidth() /
2
, getHeight() /
2
, mBorderRadius, mBorderPaint);
}
}
@Override
protected
void
onSizeChanged(
int
w,
int
h,
int
oldw,
int
oldh) {
super
.onSizeChanged(w, h, oldw, oldh);
setup();
}
public
int
getBorderColor() {
return
mBorderColor;
}
public
void
setBorderColor(
int
borderColor) {
if
(borderColor == mBorderColor) {
return
;
}
mBorderColor = borderColor;
mBorderPaint.setColor(mBorderColor);
invalidate();
}
public
int
getBorderWidth() {
return
mBorderWidth;
}
public
void
setBorderWidth(
int
borderWidth) {
if
(borderWidth == mBorderWidth) {
return
;
}
mBorderWidth = borderWidth;
setup();
}
@Override
public
void
setImageBitmap(Bitmap bm) {
super
.setImageBitmap(bm);
mBitmap = bm;
setup();
}
@Override
public
void
setImageDrawable(Drawable drawable) {
super
.setImageDrawable(drawable);
mBitmap = getBitmapFromDrawable(drawable);
setup();
}
@Override
public
void
setImageResource(
int
resId) {
super
.setImageResource(resId);
mBitmap = getBitmapFromDrawable(getDrawable());
setup();
}
@Override
public
void
setImageURI(Uri uri) {
super
.setImageURI(uri);
mBitmap = getBitmapFromDrawable(getDrawable());
setup();
}
private
Bitmap getBitmapFromDrawable(Drawable drawable) {
if
(drawable ==
null
) {
return
null
;
}
if
(drawable
instanceof
BitmapDrawable) {
return
((BitmapDrawable) drawable).getBitmap();
}
try
{
Bitmap bitmap;
if
(drawable
instanceof
ColorDrawable) {
bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
}
else
{
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
}
Canvas canvas =
new
Canvas(bitmap);
drawable.setBounds(
0
,
0
, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return
bitmap;
}
catch
(OutOfMemoryError e) {
return
null
;
}
}
private
void
setup() {
if
(!mReady) {
mSetupPending =
true
;
return
;
}
if
(mBitmap ==
null
) {
return
;
}
mBitmapShader =
new
BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
mBitmapPaint.setAntiAlias(
true
);
mBitmapPaint.setShader(mBitmapShader);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setAntiAlias(
true
);
mBorderPaint.setColor(mBorderColor);
mBorderPaint.setStrokeWidth(mBorderWidth);
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
mBorderRect.set(
0
,
0
, getWidth(), getHeight());
mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) /
2
, (mBorderRect.width() - mBorderWidth) /
2
);
mDrawableRect.set(mBorderWidth, mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth);
mDrawableRadius = Math.min(mDrawableRect.height() /
2
, mDrawableRect.width() /
2
);
updateShaderMatrix();
invalidate();
}
private
void
updateShaderMatrix() {
float
scale;
float
dx =
0
;
float
dy =
0
;
mShaderMatrix.set(
null
);
if
(mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {
scale = mDrawableRect.height() / (
float
) mBitmapHeight;
dx = (mDrawableRect.width() - mBitmapWidth * scale) *
0
.5f;
}
else
{
scale = mDrawableRect.width() / (
float
) mBitmapWidth;
dy = (mDrawableRect.height() - mBitmapHeight * scale) *
0
.5f;
}
mShaderMatrix.setScale(scale, scale);
mShaderMatrix.postTranslate((
int
) (dx +
0
.5f) + mBorderWidth, (
int
) (dy +
0
.5f) + mBorderWidth);
mBitmapShader.setLocalMatrix(mShaderMatrix);
}
}
|