有些时候,棱角分明的矩形可能不满足我们的需求。我们希望图片是圆角,显得图片更加圆滑,例如手q的头像是圆形的。虽说我到现在没做过一个像样的项目,但是还是先学习下,厉兵秣马。
这篇博客主要学习自hongyang前辈的教程,同时也增加自己,作为一个新手,的学习过程,使之详细。此次的圆角图片继承自ImageView,这样可以节省onMeasure步骤。
Xfermode和他的儿子们
实现圆角图片,我们需要用到Xfermode这个类。Xfermode有三个子类:
AvoidXfermode , PixelXorXfermode , PorterDuffXfermode
然而,前两个子类已是弃子,在Android官方文档中描述为“Deprecated since API level 16”,因此就不做介绍。
PorterDuffXfermode是画布绘制的合成模式,也就是两张图的相交之后的显示方式。正常绘制时,新的不透明的会覆盖旧的图,而通过Paint.setXfermode()可以设置相交的显示方式。
使用PorterDuffXfermode,需要创建PorterDuffXfermode的对象,例如:
Xfermode xfermode = new PorterDuffXfermode( PorterDuff.Mode.Clear)
这里传进去的PorterDuff.Mode在官方文档上介绍说有18种,常用的为下面的16种:
引用:
- PorterDuff.Mode.CLEAR
所绘制不会提交到画布上。- PorterDuff.Mode.SRC
显示上层绘制图片- PorterDuff.Mode.DST
显示下层绘制图片- PorterDuff.Mode.SRC_OVER
正常绘制显示,上下层绘制叠盖。- PorterDuff.Mode.DST_OVER
上下层都显示。下层居上显示。- PorterDuff.Mode.SRC_IN
取两层绘制交集。显示上层。- PorterDuff.Mode.DST_IN
取两层绘制交集。显示下层。- PorterDuff.Mode.SRC_OUT
取上层绘制非交集部分。- PorterDuff.Mode.DST_OUT
取下层绘制非交集部分。- PorterDuff.Mode.SRC_ATOP
取下层非交集部分与上层交集部分- PorterDuff.Mode.DST_ATOP
取上层非交集部分与下层交集部分- PorterDuff.Mode.XOR
异或:去除两图层交集部分- PorterDuff.Mode.DARKEN
取两图层全部区域,交集部分颜色加深- PorterDuff.Mode.LIGHTEN
取两图层全部,点亮交集部分颜色- PorterDuff.Mode.MULTIPLY
取两图层交集部分叠加后颜色- PorterDuff.Mode.SCREEN
取两图层全部区域,交集部分变为透明色
下面为图说明,先画DST图,设置不同的Mode,再画SRC图:
值得说明的是,我并没有对所有的模式进行测试,而是对DST_IN和SRC_IN进行了测试,其他的我相信都大同小异,测试代码如下:
int sc = canvas.saveLayer(0, 0, width, height, mPaint, Canvas.ALL_SAVE_FLAG);
// 先画DST
canvas.drawBitmap(bm, 0, 0, mPaint);
// 设置Xfermode
mPaint.setXfermode(xfermode);
// 再画SRC
canvas.drawBitmap(bm, 0, 0, mPaint);
// 设置Xfermode为空
mPaint.setXfermode(null);
canvas.restoreToCount(sc);
圆角图片步骤
自定义属性
我们这个圆角图片可以定义图片的圆角度数,因此需要自定义这个属性如些:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="RoundImageView">
<attr name="radius" format="dimension" />
</declare-styleable>
</resources>
在构造器中获得自定义属性
private static final String TAG = "RoundImageView";
private Paint mPaint;
private Xfermode xfermode;
/** 图片缩放的比例 */
private float scale = 1.0f;
/** 圆角半径 */
private float mRadius;
/** 默认的圆角半径 */
private static final int DEFAULT_RADIUS = 10;
public RoundImageView(Context context) {
this(context, null);
}
public RoundImageView(Context context, AttributeSet attr) {
super(context, attr);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
xfermode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);
TypedArray ta = context.getTheme().obtainStyledAttributes(attr, R.styleable.RoundImageView,
0, 0);
mRadius = ta.getDimensionPixelSize(R.styleable.RoundImageView_radius, (int) TypedValue
.applyDimension(TypedValue.COMPLEX_UNIT_DIP, DEFAULT_RADIUS, getResources()
.getDisplayMetrics()));
Log.e(TAG, "mRadius:" + mRadius);
ta.recycle();
}
重写onDraw方法
① 绘制图片我们需要通过canvas.drawBitmap(Bitmap bm)方法,因此,我们第一步需要获得资源图片的Bitmap对象。而父类ImageView只提供getDrawable()方法来获得Drawable对象,因此需要把Drawable转为Bitmap。
② 另一方面,如果图片资源比较小,而我们设置的width和height过大,我们需要根据比例进行缩放。
考虑以上两点后,onDraw的代码就大致确定了:
@Override
protected void onDraw(Canvas canvas) {
Bitmap bm = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Config.ARGB_8888);
Drawable drawable = getDrawable();
Canvas drawableCanvas = new Canvas(bm);
// 计算缩放
int drawablewidth = drawable.getIntrinsicWidth();
int drawableheight = drawable.getIntrinsicHeight();
int width = getMeasuredWidth();
int height = getMeasuredHeight();
scale = Math.min(width * 1.0f / drawablewidth, height * 1.0f / drawableheight);
Log.e(TAG, scale + "");
// 缩放
drawable.setBounds(0, 0, (int) (drawablewidth * scale), (int) (drawableheight * scale));
drawable.draw(drawableCanvas);
// 绘制圆角矩形
// 下面的方法api已经变为21了,所以不能使用
// canvas.drawRoundRect(0, 0, width, height, mRadius, mRadius, mPaint);
int sc = canvas.saveLayer(0, 0, width, height, mPaint, Canvas.ALL_SAVE_FLAG);
// 绘制资源图片
canvas.drawBitmap(bm, 0, 0, mPaint);
mPaint.setXfermode(xfermode);
// 绘制形状产生的图片
canvas.drawBitmap(makeSrc(width, height), 0, 0, mPaint);
mPaint.setXfermode(null);
canvas.restoreToCount(sc);
}
private Bitmap makeSrc(int width, int height) {
Bitmap bm = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bm);
Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);
p.setColor(Color.BLACK);
c.drawRoundRect(new RectF(0, 0, width, height), mRadius, mRadius, p);
return bm;
}
以上代码注意点:
- canvas.drawRoundRect(0, 0, width, height, mRadius, mRadius, mPaint); 只有api21和以上才能使用,因此需要用drawRoundRect(new RectF(0, 0, width, height), mRadius, mRadius, p);来代替。
- saveLayer和restoreToCount 不要忘记了,否则可能会失败,至少我真机测试时会失败。
- 绘制形状时,不直接使用drawRoundRect方法,而是像makeSrc方法里那样产生一张绘制有形状的Bitmap,否则也可能会失败。
效果图
后记
如有问题,可以在评论中追问。
- android官方文档:http://www.androidcommunitydocs.com/reference/android/graphics/Xfermode.html
- android官方例子 sdk-samples-android17-ApiDemo-graphics-Xfermode类
- 鸿洋前辈的教程:http://blog.csdn.net/lmj623565791/article/details/42094215
- 源码地址:http://download.csdn.net/detail/u012933743/8803383
- github上使用Xfermode绘制各种形状的开源项目:https://github.com/MostafaGazar/CustomShapeImageView