【回复“1024”,送你一个特别推送】
原文作者:lijiankun24
原文地址:https://github.com/lijiankun24/ShadowLayout
特别声明:本文为lijiankun24原创并授权发布,未经原作者允许请勿转载,转载请联系原作者
在开发过程中常会遇见带阴影效果的控件,通过 SDK 提供的 CardView
和 android:elevation
可以实现,也可以通过 .9
图实现。但是使用这两种方法会有一些弊端,比如:不可以控制阴影颜色,如果使用 .9
图片过多,会增加 APK
安装文件的体积。针对以上问题,自己写了一个为控件添加阴影的库 —- ShadowLayout
。接下来就 ShadowLayout
展开本文,本文主要分为以下两个部分:
关于 ShadowLayout
的使用;
关于 ShadowLayout
的原理。
先来看一张使用 ShadowLayout
库实现的各种阴影的效果图,如下图所示:
如上图所示,通过使用 ShadowLayout
可以控制阴影的颜色、范围、显示边界(上下左右四个边界)、x 轴和 y 轴的偏移量。
Gradle:
compile 'com.lijiankun24:shadowlayout:1.0.0'
Maven:
<dependency>
<groupId>com.lijiankun24</groupId>
<artifactId>shadowlayout</artifactId>
<version>1.0.0</version>
<type>pom</type>
</dependency>
在 xml 中添加如下布局文件:
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:contentDescription="@null"
android:src="@mipmap/ic_launcher"/>
上面 xml 布局文件实现的效果如下图所示:
如上面 xml 中代码显示的那样,总共有 5 个自定义属性,其含义分别如下:
app:shadowColor="#66000000"
控制阴影的颜色,注意:颜色必须带有透明度的值
app:shadowDx="0dp"
控制阴影 x 轴的偏移量
app:shadowDy="3dp"
控制阴影 y 轴的偏移量
app:shadowRadius="10dp"
控制阴影的范围
app:shadowSide="all|left|right|top|bottom"
控制阴影显示的边界,共有五个值
ShadowLayout
的原理其实非常简单,大概可以分为以下几步: 1. 通过自定义属性获取阴影的相关属性,包括:阴影颜色、阴影范围大小、阴影显示边界、阴影 x 轴和 y 轴的偏移量; 2. 在 onLayout()
方法中获取到阴影应该显示的范围,并设置此 ShadowLayout
的 Padding
值以给阴影的显示留出空间; 3. 在 onDraw()
方法中使用 Canvas
和 Paint
的方法绘制阴影。
绘制阴影最重要的两个方法: *
Paint.setShadowLayer(float radius, float dx, float dy, int shadowColor)
设置阴影的大小、颜色、x 轴和 y 轴的偏移量 *canvas.drawRect(RectF rect, Paint paint)
设置阴影显示的位置
在 ShadowLayout
库中只有一个文件 —- ShadowLayout.java
,ShadowLayout
是 RelativeLayout
的子类,其源码如下所示(有较为详细的注释):
/**
* ShadowLayout.java
*
* Created by lijiankun on 17/8/11.
*/
public class ShadowLayout extends RelativeLayout {
public static final int ALL = 0x1111;
public static final int LEFT = 0x0001;
public static final int TOP = 0x0010;
public static final int RIGHT = 0x0100;
public static final int BOTTOM = 0x1000;
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private RectF mRectF = new RectF();
/**
* 阴影的颜色
*/
private int mShadowColor = Color.TRANSPARENT;
/**
* 阴影的大小范围
*/
private float mShadowRadius = 0;
/**
* 阴影 x 轴的偏移量
*/
private float mShadowDx = 0;
/**
* 阴影 y 轴的偏移量
*/
private float mShadowDy = 0;
/**
* 阴影显示的边界
*/
private int mShadowSide = ALL;
public ShadowLayout(Context context) {
this(context, null);
}
public ShadowLayout(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public ShadowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(attrs);
}
/**
* 获取绘制阴影的位置,并为 ShadowLayout 设置 Padding 以为显示阴影留出空间
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
float effect = mShadowRadius + dip2px(5);
float rectLeft = 0;
float rectTop = 0;
float rectRight = this.getWidth();
float rectBottom = this.getHeight();
int paddingLeft = 0;
int paddingTop = 0;
int paddingRight = 0;
int paddingBottom = 0;
if (((mShadowSide & LEFT) == LEFT)) {
rectLeft = effect;
paddingLeft = (int) effect;
}
if (((mShadowSide & TOP) == TOP)) {
rectTop = effect;
paddingTop = (int) effect;
}
if (((mShadowSide & RIGHT) == RIGHT)) {
rectRight = this.getWidth() - effect;
paddingRight = (int) effect;
}
if (((mShadowSide & BOTTOM) == BOTTOM)) {
rectBottom = this.getHeight() - effect;
paddingBottom = (int) effect;
}
if (mShadowDy != 0.0f) {
rectBottom = rectBottom - mShadowDy;
paddingBottom = paddingBottom + (int) mShadowDy;
}
if (mShadowDx != 0.0f) {
rectRight = rectRight - mShadowDx;
paddingRight = paddingRight + (int) mShadowDx;
}
mRectF.left = rectLeft;
mRectF.top = rectTop;
mRectF.right = rectRight;
mRectF.bottom = rectBottom;
this.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
}
/**
* 真正绘制阴影的方法
*/
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawRect(mRectF, mPaint);
}
/**
* 读取设置的阴影的属性
*
* @param attrs 从其中获取设置的值
*/
private void init(AttributeSet attrs) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null); // 关闭硬件加速
this.setWillNotDraw(false); // 调用此方法后,才会执行 onDraw(Canvas) 方法
TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.ShadowLayout);
if (typedArray != null) {
mShadowColor = typedArray.getColor(R.styleable.ShadowLayout_shadowColor,
ContextCompat.getColor(getContext(), android.R.color.black));
mShadowRadius = typedArray.getDimension(R.styleable.ShadowLayout_shadowRadius, dip2px(0));
mShadowDx = typedArray.getDimension(R.styleable.ShadowLayout_shadowDx, dip2px(0));
mShadowDy = typedArray.getDimension(R.styleable.ShadowLayout_shadowDy, dip2px(0));
mShadowSide = typedArray.getInt(R.styleable.ShadowLayout_shadowSide, ALL);
typedArray.recycle();
}
mPaint.setAntiAlias(true);
mPaint.setColor(Color.TRANSPARENT);
mPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor);
}
/**
* dip2px dp 值转 px 值
*
* @param dpValue dp 值
* @return px 值
*/
private float dip2px(float dpValue) {
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
float scale = dm.density;
return (dpValue * scale + 0.5F);
}}
至此,关于 ShadowLayout
库的使用方法和原理至此全部介绍完毕,库在 GitHub 上 ShadowLayout,欢迎 star 和 fork,也欢迎通过下面二维码下载 APK 体验,如果有什么问题欢迎指出。我的工作邮箱:[email protected]
参考资料:
Android 视图高度和阴影的那点事儿 – 亦枫
ShadowViewHelper – 天天_byconan