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.}