Android 圆形/圆角图片的方法
目前网上有很多圆角图片的实例,Github上也有一些成熟的项目。之前做项目,为了稳定高效都是选用Github上的项目直接用。但这种结束也是Android开发必备技能 ,所以今天就来简单研究一下该技术,分享给大家。
预备知识:
Xfermode介绍:
下面是Android ApiDemo里的“Xfermodes”实例,效果图。
Xfermode有三个子类,结构如下:
view sourceprint?
1.public
class
2.Xfermode
3.extends
Object
4.java.lang.Object
5.? android.graphics.Xfermode
6.Known Direct Subclasses
7.AvoidXfermode, 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
取两图层全部区域,交集部分变为透明色
了解了上面的知识点后,我们根据上面的知识点先来实现第一种圆角图片制作方式:
原图:
先看这一段代码
view sourceprint?
01.private
ImageView mImg;
-
03.@Override
04.protected
void onCreate(Bundle savedInstanceState) {
05.super.onCreate(savedInstanceState);
06.setContentView(R.layout.activity_main);
07.mImg = (ImageView) findViewById(R.id.img); -
09.//获得imageview中设置的图片
10.BitmapDrawable drawable = (BitmapDrawable) mImg.getDrawable();
11.Bitmap bmp = drawable.getBitmap();
12.//获得图片的宽,并创建结果bitmap
13.int
width = bmp.getWidth();
14.Bitmap resultBmp = Bitmap.createBitmap(width, width,
15.Bitmap.Config.ARGB_8888);
16.Paint paint =
new Paint();
17.Canvas canvas =
new Canvas(resultBmp);
18.//画圆
19.canvas.drawCircle(width /
2, width / 2, width /
2, paint);
20.paint.setXfermode(new
PorterDuffXfermode(PorterDuff.Mode.SRC_IN));// 选择交集去上层图片
21.canvas.drawBitmap(bmp,
0, 0, paint);
22.mImg.setImageBitmap(resultBmp);
23.bmp.recycle(); -
25.}
通过运行上面的代码,我们得出的结果如下:
大家看到这是我们需要的结果。可是这样做可能导致OutOfMomery异常。假如图片很大或者你可能并非通过ImageView的getDrawable获得图像,而是直接Decode一张很大的图片加载到内存,你会发现可能会出现异常。我们做一下改变。
view sourceprint?
01.private
static final
String TAG = "RoundImage";
02.private
ImageView mImg;
-
04.@Override
05.protected
void onCreate(Bundle savedInstanceState) {
06.super.onCreate(savedInstanceState);
07.setContentView(R.layout.activity_main);
08.mImg = (ImageView) findViewById(R.id.img);
09.// 裁剪图片
10.BitmapFactory.Options options =
new BitmapFactory.Options();
11.options.inJustDecodeBounds =
true;
12.BitmapFactory
13..decodeResource(getResources(), R.drawable.avatar, options);
14.Log.d(TAG,
"original outwidth: " + options.outWidth);
15.// 此宽度是目标ImageView希望的大小,你可以自定义ImageView,然后获得ImageView的宽度。
16.int
dstWidth = 150;
17.// 我们需要加载的图片可能很大,我们先对原有的图片进行裁剪
18.int
sampleSize = calculateInSampleSize(options, dstWidth, dstWidth);
19.options.inSampleSize = sampleSize;
20.options.inJustDecodeBounds =
false;
21.Log.d(TAG,
"sample size: " + sampleSize);
22.Bitmap bmp = BitmapFactory.decodeResource(getResources(),
23.R.drawable.avatar, options); -
25.// 绘制图片
26.Bitmap resultBmp = Bitmap.createBitmap(dstWidth, dstWidth,
27.Bitmap.Config.ARGB_8888);
28.Paint paint =
new Paint();
29.paint.setAntiAlias(true);
30.Canvas canvas =
new Canvas(resultBmp);
31.// 画圆
32.canvas.drawCircle(dstWidth /
2, dstWidth / 2, dstWidth /
2, paint);
33.// 选择交集去上层图片
34.paint.setXfermode(new
PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
35.canvas.drawBitmap(bmp,
new Rect(0,
0, bmp.getWidth(), bmp.getWidth()),
36.new
Rect(0,
0, dstWidth, dstWidth), paint);
37.mImg.setImageBitmap(resultBmp);
38.bmp.recycle();
39.} -
41.private
int calculateInSampleSize(BitmapFactory.Options options,
42.int
reqWidth, int
reqHeight) {
43.// Raw height and width of image
44.final
int height = options.outHeight;
45.final
int width = options.outWidth;
46.int
inSampleSize = 1; -
48.if
(height > reqHeight || width > reqWidth) { -
50.final
int halfHeight = height /
2;
51.final
int halfWidth = width / 2; -
53.// Calculate the largest inSampleSize value that is a power of 2 and
54.// keeps both
55.// height and width larger than the requested height and width.
56.while
((halfHeight / inSampleSize) > reqHeight
57.&& (halfWidth / inSampleSize) > reqWidth) {
58.inSampleSize *=
2;
59.}
60.}
61.return
inSampleSize;
62.}
再来看一下效果:
上面提供了一种方式,更多细节,需要你自己去优化,下面介绍第二种绘制圆角图片的方式。
首先我们需要了解一个类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 重复方式不一样,他是以镜像方式平铺。
知道这个原理后,我们贴出对应的代码:
view sourceprint?
01.public
class CircleImageView extends
ImageView {
-
03.private
static final
String TAG = CircleImageView.class.getSimpleName();
04.private
Paint mBitmapPaint = new
Paint();
05.private
int mRadius; -
07.public
CircleImageView(Context context, AttributeSet attrs, int
defStyleAttr) {
08.super(context, attrs, defStyleAttr);
09.init();
10.} -
12.public
CircleImageView(Context context, AttributeSet attrs) {
13.super(context, attrs);
14.init();
15.} -
17.public
CircleImageView(Context context) {
18.super(context);
19.init();
20.} -
22.private
void init() {
23.BitmapDrawable drawable = (BitmapDrawable) getDrawable();
24.if
(drawable == null) {
25.Log.i(TAG,
"drawable: null");
26.return;
27.}
28.Bitmap bmp = drawable.getBitmap();
29.BitmapShader shader =
new BitmapShader(bmp, TileMode.CLAMP,
30.TileMode.CLAMP);
31.mBitmapPaint.setShader(shader);
32.mBitmapPaint.setAntiAlias(true);
33.invalidate();
34.} -
36.@Override
37.protected
void onDraw(Canvas canvas) {
38.if
(getDrawable() == null) {
39.return;
40.}
41.mRadius = Math.min(getWidth()/2, getHeight()/2);
42.canvas.drawCircle(getWidth() /
2, getHeight() / 2, mRadius,
43.mBitmapPaint);
44.} -
46.}
是不是挺简单的
结果我就不显示了,跟上面的一样。上面也是最原始的代码,文章的结尾贴出一份完整优化过的代码共大家参考如下:
view sourceprint?
001.public
class CircleImageView extends
ImageView {
-
003.private
static final
ScaleType SCALE_TYPE = ScaleType.CENTER_CROP; -
005.private
static final
Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888;
006.private
static final
int COLORDRAWABLE_DIMENSION = 1; -
008.private
static final
int DEFAULT_BORDER_WIDTH = 0;
009.private
static final
int DEFAULT_BORDER_COLOR = Color.BLACK; -
011.private
final RectF mDrawableRect =
new RectF();
012.private
final RectF mBorderRect =
new RectF(); -
014.private
final Matrix mShaderMatrix =
new Matrix();
015.private
final Paint mBitmapPaint =
new Paint();
016.private
final Paint mBorderPaint =
new Paint(); -
018.private
int mBorderColor = DEFAULT_BORDER_COLOR;
019.private
int mBorderWidth = DEFAULT_BORDER_WIDTH; -
021.private
Bitmap mBitmap;
022.private
BitmapShader mBitmapShader;
023.private
int mBitmapWidth;
024.private
int mBitmapHeight; -
026.private
float mDrawableRadius;
027.private
float mBorderRadius; -
029.private
boolean mReady;
030.private
boolean mSetupPending; -
032.public
CircleImageView(Context context) {
033.super(context); -
035.init();
036.} -
038.public
CircleImageView(Context context, AttributeSet attrs) {
039.this(context, attrs,
0);
040.} -
042.public
CircleImageView(Context context, AttributeSet attrs, int
defStyle) {
043.super(context, attrs, defStyle); -
045.TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView, defStyle,
0); -
047.mBorderWidth = a.getDimensionPixelSize(R.styleable.CircleImageView_border_width, DEFAULT_BORDER_WIDTH);
048.mBorderColor = a.getColor(R.styleable.CircleImageView_border_color, DEFAULT_BORDER_COLOR); -
050.a.recycle();
-
052.init();
053.} -
055.private
void init() {
056.super.setScaleType(SCALE_TYPE);
057.mReady =
true; -
059.if
(mSetupPending) {
060.setup();
061.mSetupPending =
false;
062.}
063.} -
065.@Override
066.public
ScaleType getScaleType() {
067.return
SCALE_TYPE;
068.} -
070.@Override
071.public
void setScaleType(ScaleType scaleType) {
072.if
(scaleType != SCALE_TYPE) {
073.throw
new IllegalArgumentException(String.format("ScaleType %s not supported.", scaleType));
074.}
075.} -
077.@Override
078.protected
void onDraw(Canvas canvas) {
079.if
(getDrawable() == null) {
080.return;
081.} -
083.canvas.drawCircle(getWidth() /
2, getHeight() / 2, mDrawableRadius, mBitmapPaint);
084.if
(mBorderWidth != 0) {
085.canvas.drawCircle(getWidth() /
2, getHeight() / 2, mBorderRadius, mBorderPaint);
086.}
087.} -
089.@Override
090.protected
void onSizeChanged(int
w, int
h, int oldw, int
oldh) {
091.super.onSizeChanged(w, h, oldw, oldh);
092.setup();
093.} -
095.public
int getBorderColor() {
096.return
mBorderColor;
097.} -
099.public
void setBorderColor(int
borderColor) {
100.if
(borderColor == mBorderColor) {
101.return;
102.} -
104.mBorderColor = borderColor;
105.mBorderPaint.setColor(mBorderColor);
106.invalidate();
107.} -
109.public
int getBorderWidth() {
110.return
mBorderWidth;
111.} -
113.public
void setBorderWidth(int
borderWidth) {
114.if
(borderWidth == mBorderWidth) {
115.return;
116.} -
118.mBorderWidth = borderWidth;
119.setup();
120.} -
122.@Override
123.public
void setImageBitmap(Bitmap bm) {
124.super.setImageBitmap(bm);
125.mBitmap = bm;
126.setup();
127.} -
129.@Override
130.public
void setImageDrawable(Drawable drawable) {
131.super.setImageDrawable(drawable);
132.mBitmap = getBitmapFromDrawable(drawable);
133.setup();
134.} -
136.@Override
137.public
void setImageResource(int
resId) {
138.super.setImageResource(resId);
139.mBitmap = getBitmapFromDrawable(getDrawable());
140.setup();
141.} -
143.@Override
144.public
void setImageURI(Uri uri) {
145.super.setImageURI(uri);
146.mBitmap = getBitmapFromDrawable(getDrawable());
147.setup();
148.} -
150.private
Bitmap getBitmapFromDrawable(Drawable drawable) {
151.if
(drawable == null) {
152.return
null;
153.} -
155.if
(drawable instanceof
BitmapDrawable) {
156.return
((BitmapDrawable) drawable).getBitmap();
157.} -
159.try
{
160.Bitmap bitmap; -
162.if
(drawable instanceof
ColorDrawable) {
163.bitmap = Bitmap.createBitmap(COLORDRAWABLE_DIMENSION, COLORDRAWABLE_DIMENSION, BITMAP_CONFIG);
164.}
else {
165.bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), BITMAP_CONFIG);
166.} -
168.Canvas canvas =
new Canvas(bitmap);
169.drawable.setBounds(0,
0, canvas.getWidth(), canvas.getHeight());
170.drawable.draw(canvas);
171.return
bitmap;
172.}
catch (OutOfMemoryError e) {
173.return
null;
174.}
175.} -
177.private
void setup() {
178.if
(!mReady) {
179.mSetupPending =
true;
180.return;
181.} -
183.if
(mBitmap == null) {
184.return;
185.} -
187.mBitmapShader =
new BitmapShader(mBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); -
189.mBitmapPaint.setAntiAlias(true);
190.mBitmapPaint.setShader(mBitmapShader); -
192.mBorderPaint.setStyle(Paint.Style.STROKE);
193.mBorderPaint.setAntiAlias(true);
194.mBorderPaint.setColor(mBorderColor);
195.mBorderPaint.setStrokeWidth(mBorderWidth); -
197.mBitmapHeight = mBitmap.getHeight();
198.mBitmapWidth = mBitmap.getWidth(); -
200.mBorderRect.set(0,
0, getWidth(), getHeight());
201.mBorderRadius = Math.min((mBorderRect.height() - mBorderWidth) /
2, (mBorderRect.width() - mBorderWidth) /
2); -
203.mDrawableRect.set(mBorderWidth, mBorderWidth, mBorderRect.width() - mBorderWidth, mBorderRect.height() - mBorderWidth);
204.mDrawableRadius = Math.min(mDrawableRect.height() /
2, mDrawableRect.width() /
2); -
206.updateShaderMatrix();
207.invalidate();
208.} -
210.private
void updateShaderMatrix() {
211.float
scale;
212.float
dx = 0;
213.float
dy = 0; -
215.mShaderMatrix.set(null);
-
217.if
(mBitmapWidth mDrawableRect.height() > mDrawableRect.width() mBitmapHeight) {
218.scale = mDrawableRect.height() / (float) mBitmapHeight;
219.dx = (mDrawableRect.width() - mBitmapWidth scale)
0.5f;
220.}
else {
221.scale = mDrawableRect.width() / (float) mBitmapWidth;
222.dy = (mDrawableRect.height() - mBitmapHeight scale)
0.5f;
223.} -
225.mShaderMatrix.setScale(scale, scale);
226.mShaderMatrix.postTranslate((int) (dx +
0.5f) + mBorderWidth, (int) (dy +
0.5f) + mBorderWidth); -
228.mBitmapShader.setLocalMatrix(mShaderMatrix);
229.} -
231.}