首先,这是基于api 21的elevation
来实现的阴影,21以下的机型暂不讨论
阴影是绘制在控件外部,所以,首先要保证该控件的直属parent有足够的空间让其绘制阴影。(如果是非直属的,则parent的clipChildren和clipPadding需要设置为false)
看完本篇文章,你将会:
1.知道如何控制elevation
来实现自己的阴影
2.知道如何控制elevation
的阴影的alpha(感觉这个是重点,当初使用cardView就是UI认为太重,无法调节alpha而最终自己用shadow绘制
3.控制阴影的同时,也具有圆角效果
首先,我们需要两个属性,一个是圆角大小,一个是alpha的值,直接定义style为
<declare-styleable name="ShadowLayout">
<attr name="shadow_radius" format="dimension" />
<attr name="shadow_alpha" format="float" />
</declare-styleable>
然后,简单实现下圆角代码。(为何这样实现,不再复述)
public class ShadowLayout extends FrameLayout {
private RectF mRectF;
private Path mPath;
private Paint mPaint;
private float mRadius;
private float mAlpha;
public ShadowLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ShadowLayout, 0, 0);
mRadius = a.getDimensionPixelSize(R.styleable.ShadowLayout_shadow_radius, 0);
mAlpha = a.getFloat(R.styleable.ShadowLayout_shadow_alpha, 1f);
if (mAlpha < 0) {
mAlpha = 0;
} else if (mAlpha > 1f) {
mAlpha = 1f;
}
a.recycle();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT));
mRectF = new RectF();
mPath = new Path();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mRectF.set(0, 0, getWidth(), getHeight());
mPath.addRect(mRectF, Path.Direction.CCW);
mPath.addRoundRect(mRectF, mRadius, mRadius, Path.Direction.CW);
}
@Override
protected void dispatchDraw(Canvas canvas) {
final int layerId = canvas.saveLayer(mRectF, null, Canvas.ALL_SAVE_FLAG);
super.dispatchDraw(canvas);
canvas.drawPath(mPath, mPaint);
canvas.restoreToCount(layerId);
}
}
然后,布局文件如下
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<dora.felix.shadowlayoutdemo.ShadowLayout
android:layout_width="150dp"
android:layout_height="150dp"
android:elevation="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:shadow_alpha="0.5"
app:shadow_radius="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#00ffff"
android:gravity="center"
android:text="Hello World!" />
</dora.felix.shadowlayoutdemo.ShadowLayout>
</android.support.constraint.ConstraintLayout>
看下效果如下:
嗯嗯,圆角很ok,但是,阴影哪去了?
别急,网上说了,要设置背景,才能显示,于是,在初始化完毕,直接,设置个背景就行了
setBackground(new ColorDrawable(Color.WHITE));
再看下效果
嗯,阴影效果是有了,但是,背景就没有圆角了,不过,这难不倒我们,自己定义个drawable不就好了。
public class RoundDrawable extends Drawable {
private Paint mPaint;
private float mRadius;
private float mAlpha;
private RectF mRectF;
public RoundDrawable(float radius, float alpha) {
mRadius = radius;
mAlpha = alpha;
mRectF = new RectF();
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mPaint.setColor(Color.WHITE);
}
@Override
public void setColorFilter(@Nullable ColorFilter colorFilter) {
mPaint.setColorFilter(colorFilter);
}
@Override
public int getOpacity() {
return PixelFormat.TRANSLUCENT;
}
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
}
@Override
public void setBounds(@NonNull Rect bounds) {
super.setBounds(bounds);
mRectF.set(bounds);
}
@Override
public void draw(@NonNull Canvas canvas) {
canvas.drawRoundRect(mRectF, mRadius, mRadius, mPaint);
}
}
当然,为了保证自己的drawable不会被动到,直接重写android.view.View#setBackground
,代码如下:
@Override
public void setBackground(Drawable background) {
if (background instanceof RoundDrawable) {
super.setBackground(background);
} else {
super.setBackground(new RoundDrawable(mRadius, mAlpha));
}
}
然后,继续运行下效果
嗯嗯,又有圆角了。咦,不对,阴影咋又没了?接下来的这个,就是我花的大部分时间,一个一个的去尝试,最后也是功夫不负有心人,被我尝试出来了的,这个方法就是
android.graphics.drawable.Drawable#getOutline
附上代码如下:
@Override
public void getOutline(@NonNull Outline outline) {
super.getOutline(outline);
outline.setRoundRect(getBounds(), mRadius);
outline.setAlpha(mAlpha);
}
第一个设置,可以得到阴影效果,alpha设置就可以直接设置阴影的alpha了,直接看效果图
再来张alpha为0.15的效果(为啥是0.15,就是因为当时我们的UI要求很淡,但是又不能没有)
嗯嗯,效果很淡,很ok了(觉得没有阴影效果的,自己对比下第一图)
最后说下,做这个的最主要的原因,其实,也就是研究下cardView的实现原理,至少目前最低还不会是21,21以下的无法兼容,自然也无法用,也只能用cardView。看下cardView的实现,也会发现,所谓的阴影大小,其实就是调用了
android.view.View#setElevation
,而控制有阴影且圆角,也是类似RoundDrawable的方法,但是,看了cardView内部的方法后,就会发现他的android.graphics.drawable.Drawable#getOutline
没有设置alpha,就是类似outline.setAlpha(mAlpha);
这样的语句,这也就是CardView无法设置阴影的alpha的原因了。可能google认为,阴影设置很大的时候,