之前写了Android 自定义圆角+阴影布局,虽然实现了阴影效果,但阴影需要考虑在布局内,无法实现像CardView那样阴影可以在布局外绘制。这篇为了解决阴影不能绘制在布局外的问题
实现布局外绘制阴影的思路
- 绘制view外部的阴影(其实就是扩大canvas绘制区域,让其超出view)
- 让view外部的阴影显示(父视图通过设置android:clipChildren="false",让view可以超出自身范围进行绘制)
思路实现遇到的关键问题:如何绘制view外部阴影,又不影响view区域内的图像?
解决:
- 通过PorterDuffXfermode设置图像混合模式,裁剪掉阴影绘制区域在view区域内的图像 (实现后发现存在过度绘制问题)
- 使用canvas的clipPath(path,Region.Op),裁剪掉阴影绘制区域在view区域内的图像(虽然存在锯齿,但没有过度绘制问题)
- 将阴影区域绘制放于super.draw(canvas)之前,这样view将绘制在阴影区域之上,这样不用剪切阴影画笔填充区域了,同时可以设置阴影画笔颜色作为背景色,也没有锯齿问题
实现核心代码(第三种方案结合使用canvas的clipPath(path,Region.Op)):
@Override
public void draw(Canvas canvas) {
...
//画阴影
if (shadowElevation > 0) {
Bitmap srcBmp = createShadowBitmap((int) (getWidth()+shadowElevation*2), (int) (getHeight()+shadowElevation*2));
canvas.drawBitmap(srcBmp, shadow_x-shadowElevation, shadow_y-shadowElevation, clipShadowPaint);
}
}
//添加阴影bitmap
private Bitmap createShadowBitmap(int shadowWidth, int shadowHeight) {
Bitmap output = Bitmap.createBitmap(shadowWidth, shadowHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
shadowRect.set(shadowElevation-shadow_x,shadowElevation-shadow_y,shadowWidth-shadowElevation-shadow_x,shadowHeight-shadowElevation-shadow_y);
shadowPaint.setColor(shadowColor);
if (!isInEditMode()) {
shadowPaint.setShadowLayer(shadowElevation, shadow_x, shadow_y, shadowColor);
}
shadowPath.reset();
shadowPath.addRoundRect(shadowRect, radiusShadowArray, Path.Direction.CW);
if (clipShadowPath) {
//保留shadowPath路径以外区域
canvas.clipPath(shadowPath, Region.Op.DIFFERENCE);
}
canvas.drawPath(shadowPath, shadowPaint);
return output;
}
效果图: