最近在做一个需求,自定义view,实现一个中空的遮罩层,一开始实现的效果如下:
image.png
先看实现:
canvas.save();
//绘制底部遮罩
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK);
paint.setAlpha(125);
Rect mask = new Rect(0, 0, getWidth(), getHeight());
canvas.drawRect(mask, paint);
//绘制中空
int cropT = DisplayUtil.dip2px(getContext(), 50);
int cropL = DisplayUtil.dip2px(getContext(), 32);
int cropR = getWidth() - cropL;
int cropB = getHeight() - DisplayUtil.dip2px(getContext(), 40);
Rect crop = new Rect(cropL, cropT, cropR, cropB);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawRect(crop, paint);
paint.setXfermode(null);
canvas.restore;
乍一看,逻辑没问题,先画一个半透明的遮罩,然后通过PorterDuff.Mode.CLEAR,在中间挖空。
由于之前分析过view如何显示到手机屏幕,所以知道canvas是从viewrootimpl新建分给各个view的
ViewRootImpl->drawSoftware:
canvas = mSurface.lockCanvas(dirty);
Surface->lockCanvas:
public Canvas lockCanvas(Rect inOutDirty)
throws Surface.OutOfResourcesException, IllegalArgumentException {
synchronized (mLock) {
checkNotReleasedLocked();
if (mLockedObject != 0) {
// Ideally, nativeLockCanvas() would throw in this situation and prevent the
// double-lock, but that won't happen if mNativeObject was updated. We can't
// abandon the old mLockedObject because it might still be in use, so instead
// we just refuse to re-lock the Surface.
throw new IllegalArgumentException("Surface was already locked");
}
mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
return mCanvas;
}
}
从上面可以知道,mCanvas是设备窗口的一个句柄.
也是就是说,如果你直接在canvas上面绘制数据相当于在window上面直接绘制,所有效果将对应到当前的window上面,也就是会出现上面的中间黑色的原因,window的背景为黑色.
但是由于canvas相当于ps中图层的意思,遵循PS的绘制原则,是不允许在原图层绘制数据的,所以数据的绘制必须要新建图层绘制,在这里,也要遵循这个原则,所有在draw(canvas)上面绘制数据,必须新建图层
新建图层:
/**
* Convenience for {@link #saveLayer(RectF, Paint)} that takes the four float coordinates of the
* bounds rectangle.
*/
public int saveLayer(float left, float top, float right, float bottom, @Nullable Paint paint) {
return saveLayer(left, top, right, bottom, paint, ALL_SAVE_FLAG);
}
canvas提供的savelayer方法,新建了一个新的图层
还有另外一种类似新建图层的方法,就是创建一个新的bitmap对象,所有绘制操作的都作用于当前的bitmap,
然后将最后的bitmap绘制到view的canvas上面;
Bitmap bg = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas bgCanvas = new Canvas(bg);
canvas.save();
//绘制底部遮罩
Paint paint = new Paint();
paint.setAntiAlias(true);
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK);
paint.setAlpha(125);
Rect mask = new Rect(0, 0, getWidth(), getHeight());
bgCanvas.drawRect(mask, paint);
//绘制中空
int cropT = DisplayUtil.dip2px(getContext(), 50);
int cropL = DisplayUtil.dip2px(getContext(), 32);
int cropR = getWidth() - cropL;
int cropB = getHeight() - DisplayUtil.dip2px(getContext(), 40);
Rect crop = new Rect(cropL, cropT, cropR, cropB);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
bgCanvas.drawRect(crop, paint);
paint.setXfermode(null);
//将最终的图片绘制到系统的canvas
canvas.drawBitmap(bg, 0, 0, new Paint());
canvas.restore();
完.