隨着這幾年UI風格的不斷升級,陰影已經成了不少APP設計中的不可或缺的元素。但Android在這方面卻沒有比較好的實現方式。java
這裏有總結的一篇關於Android陰影效果的文章,比較全面,值得一看。聊聊 Material Design 裏,陰影的那些事兒!git
上面這篇文章對Android中各版本的陰影實現都進行了說明,這裏就再也不細說了。雖然提供的方式不少,可是卻有很大的侷限性,具體表如今如下兩方面:github
雖然也能夠按照做者提供的方式,使用Fab或CardView實現陰影的原理來實現,但相對比較麻煩,這裏咱們提供一個簡單的實現方案。canvas
爲View添加陰影,其實就是爲View提供一個有陰影的背景而已,因此有2中實現方式:segmentfault
第一種明顯不合理,咱們不可能重寫每一個須要設置陰影的View的onDraw(),因此這裏選擇自定義Drwable(經過設置Paint的ShadowLayer)來實現。須要注意的是:這種方式實現的陰影,其目標View須要關閉硬件加速。ide
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
複製代碼
源碼地址:ShadowDrawableui
public class ShadowDrawable extends Drawable {
private Paint mPaint;
private int mShadowRadius; // 陰影圓角
private int mShape; // 背景形狀
private int mShapeRadius; // 背景圓角
private int mOffsetX; // 陰影的水平偏移量
private int mOffsetY; // 陰影的垂直偏移量
private int mBgColor[]; // 背景顏色
private RectF mRect;
public final static int SHAPE_ROUND = 1; // 表示圓角矩形
public final static int SHAPE_CIRCLE = 2; // 表示圓
private ShadowDrawable(int shape, int[] bgColor, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) {
this.mShape = shape;
this.mBgColor = bgColor;
this.mShapeRadius = shapeRadius;
this.mShadowRadius = shadowRadius;
this.mOffsetX = offsetX;
this.mOffsetY = offsetY;
mPaint = new Paint();
mPaint.setColor(Color.TRANSPARENT);
mPaint.setAntiAlias(true);
mPaint.setShadowLayer(shadowRadius, offsetX, offsetY, shadowColor);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
}
@Override
public void setBounds(int left, int top, int right, int bottom) {
super.setBounds(left, top, right, bottom);
mRect = new RectF(left + mShadowRadius - mOffsetX, top + mShadowRadius - mOffsetY, right - mShadowRadius - mOffsetX,
bottom - mShadowRadius - mOffsetY);
}
@Override
public void draw(@NonNull Canvas canvas) {
if (mShape == SHAPE_ROUND) {
canvas.drawRoundRect(mRect, mShapeRadius, mShapeRadius, mPaint);
Paint newPaint = new Paint();
if (mBgColor != null) {
if (mBgColor.length == 1) {
newPaint.setColor(mBgColor[0]);
} else {
newPaint.setShader(new LinearGradient(mRect.left, mRect.height() / 2, mRect.right, mRect.height() / 2, mBgColor,
null, Shader.TileMode.CLAMP));
}
}
newPaint.setAntiAlias(true);
canvas.drawRoundRect(mRect, mShapeRadius, mShapeRadius, newPaint);
} else {
canvas.drawCircle(mRect.centerX(), mRect.centerY(), Math.min(mRect.width(), mRect.height())/ 2, mPaint);
}
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
}
複製代碼
因爲提供的屬性比較多,爲了便於使用,提供了Builder的鏈式建立方式,同時提供了經常使用的幾個static方法,設置陰影只需一行代碼便可,具體查看ShadowDrawable.java。this
public static void setShadowDrawable(View view, int shapeRadius, int shadowColor, int shadowRadius, int offsetX, int offsetY) {
ShadowDrawable drawable = new ShadowDrawable.Builder()
.setShapeRadius(shapeRadius)
.setShadowColor(shadowColor)
.setShadowRadius(shadowRadius)
.setOffsetX(offsetX)
.setOffsetY(offsetY)
.builder();
view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
ViewCompat.setBackground(view, drawable);
}
複製代碼
因爲Android系統並無提供完美的解決方案,即使使用5.0以上提供的elevation或translationZ屬性,對於大面積的陰影,看起來也比較生硬, 因此在Android開發中,對於陰影的處理,仍是應該分狀況來對待,大面積的陰影強烈推薦使用9Patch圖來解決,對於小控件的陰影,可以使用以上的方案。spa