效果圖
為了更友善的實作UI效果圖裡的圓角+陰影,就自定義了布局RoundShadowLayout
邏輯思路
- 設定布局的圓角,将子view超出圓角的區域裁剪掉
- 設定陰影,不改變子view的大小,将布局大小擴充到可以容納陰影,并調整子view的位置
裁剪實作方案
- 使用canvas.clipXXX()方法裁剪畫圖區域(存在鋸齒,不使用)
- 使用paint的Xfermode進行處理,獲得需要效果(使用)
- 使用ViewOutLineProvider(需要21以上,不能單獨設定某個圓角,不使用)
陰影實作方案
-
使用paint.setShadowLayer()方法
主要實作代碼
//設定布局大小
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 測量子view的最大寬高
int width = 0;
int height = 0;
for (int i=0;i<getChildCount();i++) {
View child = getChildAt(i);
//測量子View的寬高,measureChild最終調用child.measure(w,h)
final ViewGroup.LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec-(int) (shadowRadius)*2, getPaddingLeft() + getPaddingRight(), lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec-(int) (shadowRadius)*2, getPaddingTop() + getPaddingBottom(), lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
MarginLayoutParams mlp = (MarginLayoutParams) child.getLayoutParams();
int childWidth = child.getMeasuredWidth() + mlp.leftMargin + mlp.rightMargin;
int childHeight = child.getMeasuredHeight() + mlp.topMargin + mlp.bottomMargin;
width = Math.max(width, childWidth);
height = Math.max(height, childHeight);
}
//如果使用陰影,則寬高加上陰影
setMeasuredDimension(
width + getPaddingLeft() + getPaddingRight() + (int) (shadowRadius)*2,
height + getPaddingTop() + getPaddingBottom() + (int) (shadowRadius)*2
);
}
//添加陰影bitmap,最後将bitmap設定為布局的background
private Bitmap createShadowBitmap(int shadowWidth, int shadowHeight, float shadowRadius,
float dx, float dy, int shadowColor) {
Bitmap output = Bitmap.createBitmap(shadowWidth, shadowHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
shadowRect.set(shadowRadius,shadowRadius,shadowWidth - shadowRadius,shadowHeight - shadowRadius);
if (dy > 0) {
shadowRect.top += dy;
shadowRect.bottom -= dy;
} else if (dy < 0) {
shadowRect.top += Math.abs(dy);
shadowRect.bottom -= Math.abs(dy);
}
if (dx > 0) {
shadowRect.left += dx;
shadowRect.right -= dx;
} else if (dx < 0) {
shadowRect.left += Math.abs(dx);
shadowRect.right -= Math.abs(dx);
}
shadowPaint.setAntiAlias(true);
shadowPaint.setStyle(Paint.Style.FILL);
shadowPaint.setColor(shadowColor);
if (!isInEditMode()) {
shadowPaint.setShadowLayer(shadowRadius, dx, dy, shadowColor);
}
shadowPath.reset();
shadowPath.addRoundRect(shadowRect, radiusArray, Path.Direction.CW);
canvas.drawPath(shadowPath, shadowPaint);
return output;
}
//裁剪圓角區域
private void clipRound(Canvas canvas) {
roundPath.reset();
roundPath.addRoundRect(roundRect, radiusArray, Path.Direction.CW);
//畫筆設定
roundPaint.setColor(Color.WHITE);
roundPaint.setAntiAlias(true);
roundPaint.setStyle(Paint.Style.FILL);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
roundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawPath(roundPath, roundPaint);
} else {
roundPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
final Path path = new Path();
path.addRect(0, 0, getWidth(), getHeight(), Path.Direction.CW);
path.op(roundPath, Path.Op.DIFFERENCE);
canvas.drawPath(path, roundPaint);
}
}
源代碼https://github.com/wudengwei/RoundShadowLayout
參考 rclayout、shadow-layout