最近在学习自定义view的时候看见了几个方法,一个是clipxxx(),一个是setShader(),一时有点懵,学习了一下,感觉还是很简单的,和xfermode有异曲同工之妙,所以写了个demo,来看看最基本的方法
为了表现最基本的用法,不考虑图片的适配缩放,不考虑内存泄漏等情况,给最直观的用法
private void drawRoundImageByPath() {
mCanvas.save();
mPath.addCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, 90, Path.Direction.CCW);
//由于我们只是需要将画布裁剪成圆形,无需考虑两种图形相交情况,所以OP参数任意
mCanvas.clipPath(mPath, Region.Op.REVERSE_DIFFERENCE);
mCanvas.restore();
}
mCanvas是onDraw函数里默认的canvas,关于clipxxx(),个人理解
剪裁画布,因为我们需要的是一个圆形,所以只需要将path设置为圆形然后剪裁即可,有两点需要注意的是,clipxxx()剪裁的是画布,所以我们需要什么形状,只需要将画布剪裁成相应形状然后画view即可。也就是说先剪裁,后画图,当然你也可以先画图后剪裁,但是这样不符合惯性思维
更多属性以及用法请参考 http://blog.csdn.net/wzlyd1/article/details/72436585
//shader实现圆角图片,记得屏蔽super.onDraw(canvas)
private void drawRoundImageByShader() {
if (mSharder == null) {
mSharder = new BitmapShader(getBitmapBeforeDraw(), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
}
mPaint.setShader(mSharder);
mCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, 90, mPaint);
}
主要的知识点
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 重复方式不一样,他是以镜像方式平铺。
private void drawRoundImageByXfermode() {
int saveLayerId = mCanvas.saveLayer(0, 0, mCanvas.getWidth(), mCanvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
//先画DST
mCanvas.drawBitmap(getBitmapBeforeDraw(), 0, 0, mPaint);
//画SRC
Bitmap bitmap = Bitmap.createBitmap(mCanvas.getWidth(), mCanvas.getHeight(), Bitmap.Config.ARGB_8888);
Canvas ca = new Canvas(bitmap);
ca.drawCircle(mCanvas.getWidth() / 2, mCanvas.getHeight() / 2, 90, mPaint);
//相交取下半部分
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mCanvas.drawBitmap(bitmap, 0, 0, mPaint);
mPaint.setXfermode(null);
mCanvas.restoreToCount(saveLayerId);
}
主要知识点在于 PorterDuff.Mode的几种模式,以及你要分清楚什么是DST 什么是SRC,这点很重要,更多可以参考博客 http://blog.csdn.net/wzlyd1/article/details/71597599
package view;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.Shader;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.widget.ImageView;
/**
* 简单起见,固定宽高为200dp
*/
public class RoundImageView extends ImageView {
private Paint mPaint;
private Paint mDSTPaint;
private BitmapShader mSharder;
private Path mPath;
private Canvas mCanvas;
private PorterDuffXfermode mMode = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);
public RoundImageView(Context context) {
this(context, null);
}
public RoundImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public RoundImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setAntiAlias(true);
mDSTPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mDSTPaint.setAntiAlias(true);
mPath = new Path();
setLayerType(LAYER_TYPE_SOFTWARE, mPaint);//clipPath 不支持硬件加速
}
@Override
protected void onDraw(Canvas canvas) {
mCanvas = canvas;
// drawRoundImageByXfermode();
// drawRoundImageByShader();
// drawRoundImageByPath();
}
//通过路径来实现圆角,注意super.onDraw(canvas)调用位置
private void drawRoundImageByPath() {
mCanvas.save();
mPath.addCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, 90, Path.Direction.CCW);
//由于我们只是需要将画布裁剪成圆形,无需考虑两种图形相交情况,所以OP参数任意
mCanvas.clipPath(mPath, Region.Op.INTERSECT);
mCanvas.restore();
}
//shader实现圆角图片,记得屏蔽super.onDraw(canvas)
private void drawRoundImageByShader() {
if (mSharder == null) {
mSharder = new BitmapShader(getBitmapBeforeDraw(), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
}
mPaint.setShader(mSharder);
mCanvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, 90, mPaint);
}
private void drawRoundImageByXfermode() {
int saveLayerId = mCanvas.saveLayer(0, 0, mCanvas.getWidth(), mCanvas.getHeight(), null, Canvas.ALL_SAVE_FLAG);
//先画DST
mCanvas.drawBitmap(getBitmapBeforeDraw(), 0, 0, mPaint);
//画SRC
Bitmap bitmap = Bitmap.createBitmap(mCanvas.getWidth(), mCanvas.getHeight(), Bitmap.Config.ARGB_8888);
Canvas ca = new Canvas(bitmap);
ca.drawCircle(mCanvas.getWidth() / 2, mCanvas.getHeight() / 2, 90, mPaint);
//相交取下半部分
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
mCanvas.drawBitmap(bitmap, 0, 0, mPaint);
mPaint.setXfermode(null);
mCanvas.restoreToCount(saveLayerId);
}
public Bitmap getBitmapBeforeDraw() {
BitmapDrawable b = (BitmapDrawable) this.getDrawable();
return Bitmap.createScaledBitmap(b.getBitmap(), getMeasuredWidth(), getMeasuredHeight(), false);
}
}
第一种方式最简单,但是剪切出来的图形有锯齿,图越大越明显,且设置抗锯齿没用~
第二种方式是Fresco 源码的设置方式,可将图形渲染为你指定的图形。
第三种方式比较复杂,尤其是要搞清楚哪个是DST ,哪个是SRC,相较于第二种,我觉得最大的不同点在于,第二种是将一种图渲染为一种指定的形状
第三种是两种图形的交汇来达到不同的效果