最近一段时间的开发一直都没离开阴影这个词,真就和别人说的那样,设计师有三宝透明、阴影和圆角,阴影这东西说起来就一句话,做起来可真是头疼,特别是开发地图 Markers 时的时候,硬要在 Markers 边上加阴影,形状还不固定,简直把我气死了,后面还是让设计师给图片解决的。

也看了很多博客,自己也试了很多方法,下面总结一下:

阴影背景

阴影背景一般是通过资源文件引进来的,可以直接是图片,一般使用 .9 文件(可拉伸),也可以是 shape 生成的,在需要的地方引入,使用方便。

  • .9 文件
    因为 .9 文件可以在宽高方向拉伸,所以制作成阴影背景还是很合适的,特别是按钮的背景,搭配 selector 一起使用,可以实现有阴影的点击效果 。

优点:1. 上下左右可拉伸,使用起来简单方便。2. Android Studio 可制作。
缺点:1. 需要注意阴影占据大小,内部内容不能覆盖阴影。2. 有限制,只能左右拉伸。

  • shape 阴影背景
    shape 阴影背景一般就是通过 layer-list 实现的,通过多层不同透明度的背景叠加生成阴影效果,用起来还是挺方便,也可以修改背景的参数
<item>
    <layer-list>
        <item>
            <shape>
                <corners android:radius="25dp"/>
                <solid android:color="#E4E4E4"/>
            </shape>
        </item>
        <item android:left="2dp" android:top="2dp"
              android:bottom="2dp" android:right="2dp">
            <shape>
                <corners android:radius="25dp"/>
                <solid android:color="#FFFFFF"/>
            </shape>
        </item>
    </layer-list>
</item>

优点:1. 使用方便,参数可控,易修改。
缺点:1. 需要注意阴影占据大小,内部内容不能覆盖阴影。2. 有限制,只能左右拉伸。

阴影容器

说到阴影实际上想到最多就是 CardView 了,它继承了 FramLayout,能够显示阴影效果,并且有很好的向下兼容性,实际上还是要设置一定的 padding 值才能显示出阴影,是一种用容器来显示阴影的实现方法,但是却不太好控制阴影,设计师估计会不服。。。

下面简单讲讲使用容器实现阴影的办法:

  • elevation 属性
    用了那么久的 cardElevation,你知道其实 view 也有 elevation 属性吗?使用 elevation 属性可以拔高 view 的高度,Google 爸爸就会自动给它加上阴影效果,和 CardView 类似,但是要求安卓版本不低于 v21,不太好办。
android:elevation="100dp"

Attribute elevation is only used in API level 21 and higher (current min is 19)

优点:1. 使用方便,直接修改属性值就可以。
缺点:1. 要求安卓最低版本较高。2. 无法控制阴影效果,只是通过 elevation 值自动生效阴影。

  • CardView
    CardView 其实和上面类似,通过修改 cardElevation 值修改阴影,并自带圆角,大受欢迎。

优点:1. 广为使用,直接修改属性值就可以。 2. 向下兼容性好,不限制安卓版本。
缺点:1. 无法控制阴影效果,只是通过 cardElevation 值自动生效阴影。

  • 自定义阴影容器控件
    有了 CardView 能够实现阴影,可是很多时候真不能满足设计师要求,只能自己实现阴影空间了,我这里就贴点博客和源码吧,写的挺详细,不过内容我们后面要讲,所以不多说了。

//开源库

https://github.com/Devlight/ShadowLayout

//一个自定义阴影效果的博客


优点:1. 可以控制阴影效果。
缺点:1. 使用的人少,自己维护。

文本控件阴影

在 Css 里面都能很容易的控制文本的阴影,我们安卓怎么可能没有呢?下面是两种使用形式

  • XML 实现
<TextView
       …………
        android:shadowRadius="3"
        android:shadowDx="5"
        android:shadowDy="5"
        android:shadowColor="@android:color/darker_gray"/>

这里的属性实际会调用我们后面要讲到的 setShadowLayer 函数,但这几个属性只有 TextVIew 及其派生类才会有,其它类是没有的,TextVIew 的派生类如下:

android shape按钮颜色怎么改 android shape阴影_阴影效果

常用的 TextView 、EditText、Button 都在其中。

  • 代码实现
    代码实现和 XML 类似,下面时函数原型:
//TextView中的设置阴影函数
public void setShadowLayer(float radius, float dx, float dy, int color) ;

具体使用方法:

TextView tv = (TextView)findViewById(R.id.tv);
tv.setShadowLayer(2,5,5, Color.GREEN);

Paint.setShadowLayer实现阴影效果

上面我们讲到了 TextView 之类的控件实际时通过 setShadowLayer 方法实现的,这个方法时 Paint 的方法,我们可以在自定义控件的 onDraw 方法或者 canvas 控件中使用,可以使文字和图片产生阴影效果,下面先看函数原型:

public void setShadowLayer(float radius, float dx, float dy, int color);
//清除ShadowLayer阴影
public void clearShadowLayer()

下面时各参数的意义:

  • float radius:意思是模糊半径,radius越大越模糊,越小越清晰,但是如果radius设置为0,则阴影消失不见。
  • float dx:阴影的横向偏移距离,正值向右偏移,负值向左偏移
  • float dy:阴影的纵向偏移距离,正值向下偏移,负值向上偏移
  • int color:绘制阴影的画笔颜色,即阴影的颜色(对图片阴影无效)

使用案例我直接拿启舰老师的,下面看图和代码:

android shape按钮颜色怎么改 android shape阴影_安卓_02

public class ShadowLayerView extends View {
    private Paint mPaint = new Paint();
    private Bitmap mDogBmp;
    public ShadowLayerView(Context context) {
        super(context);
        init();
    }
 
    public ShadowLayerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
 
    public ShadowLayerView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
 
    private void init(){
        //关闭硬件加速
        setLayerType( LAYER_TYPE_SOFTWARE , null);
        mPaint.setColor(Color.GREEN);
        mPaint.setTextSize(25);
        //阴影
        mPaint.setShadowLayer(1, 10, 10, Color.GRAY);
        mDogBmp = BitmapFactory.decodeResource(getResources(),R.drawable.dog);
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
 
        canvas.drawText("启舰大SB",100,100,mPaint);
 
        canvas.drawCircle(200,200,50,mPaint);
 
        canvas.drawBitmap(mDogBmp,null,new Rect(200,300,200+mDogBmp.getWidth(),300+mDogBmp.getHeight()),mPaint);
    }
}

这里需要注意几点问题:

  1. 绘制阴影的画笔颜色对图片无效

使用setShadowLayer所产生的阴影,对于文字和绘制的图形的阴影都是使用自定义的阴影画笔颜色来画的,而图片的阴影则是直接产生一张相同的图片,仅对阴影图片的边缘进行模糊。

  1. setShadowLayer 只有文字绘制阴影支持硬件加速

这里有一点需要非常注意的是setShadowLayer只有文字绘制阴影支持硬件加速,其它都不支持硬件加速,所以为了方便起见,我们需要在自定义控件中禁用硬件加速。

Paint.setMaskFilter实现阴影效果

这里还是 Paint 的一个方法,它接受一个 MaskFilter 对象,这个对象有两个派生类,分别是 BlurMaskFilter 和 EmbossMaskFilter,其中 BlurMaskFilter 可以实现发光效果,而 EmbossMaskFilter 是用来实现浮雕效果的,这里我们使用 BlurMaskFilter 来实现阴影效果。

需要注意的是,setMaskFilter 是不支持硬件加速的,必须关闭硬件加速才可以。

下面先看函数原型:

public BlurMaskFilter(float radius, Blur style)

其中:

  • float radius:用来定义模糊半径,同样是高斯模糊算法。
  • Blur style:发光样式,有内发光、外发光、和内外发光,分别对应:Blur.INNER (内发光)、Blur.SOLID (外发光)、Blur.NORMAL (内外发光)、Blur.OUTER (仅发光部分可见)。

先讲一下 BlurMaskFilter 的效果吧,还是看启舰老师的例子:

public class BlurMaskFilterView extends View {
    private Paint mPaint;
    public BlurMaskFilterView(Context context) {
        super(context);
        init();
    }
 
    public BlurMaskFilterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }
 
    public BlurMaskFilterView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }
 
    private void init(){
        //关闭硬件加速
        setLayerType(LAYER_TYPE_SOFTWARE,null);
        mPaint = new Paint();
        mPaint.setColor(Color.RED);
        //设置 MaskFilter
        mPaint.setMaskFilter(new BlurMaskFilter(50, Blur.INNER));
    }
 
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
 
        canvas.drawCircle(200,200,100,mPaint);
    }
}

下面时具体的效果图:

  • Blur.INNER——内发光

android shape按钮颜色怎么改 android shape阴影_阴影效果_03

  • Blur.SOLID——外发光

android shape按钮颜色怎么改 android shape阴影_阴影_04

  • Blur.NORMAL——内外发光

android shape按钮颜色怎么改 android shape阴影_安卓_05

  • Blur.OUTER——仅显示发光效果

android shape按钮颜色怎么改 android shape阴影_阴影效果_06

下面开始讲解阴影的实现,这里不使用启舰老师的例子了,因为它那阴影时通过偏移显示的,我是觉得有点怪,下面就拿上一篇博客的滑块阴影简单讲讲:

  1. 在 init 函数中设置画笔的 BlurMaskFilter,使阴影外发光,设置发光颜色为黑色。
// 实例化阴影画笔,抗锯齿、抗抖动
mMaskShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mMaskShadowPaint.setColor(Color.BLACK);
mMaskShadowPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.SOLID));
  1. 使用 Bitmap 的 extractAlpha 方法产生一个只有 Alpha 值的空白图像。
//滑块阴影
mMaskShadowBitmap = mMaskBitmap.extractAlpha();
  1. 使用 drawBitmap 方法通过设置好的画笔先画出只有 Alpha 值的空白图像,再绘制原图像。
canvas.drawBitmap(mMaskShadowBitmap, -mCaptchaX + mDragerOffset, 0, mMaskShadowPaint);
canvas.drawBitmap(mMaskBitmap, -mCaptchaX + mDragerOffset, 0, null);

实现起来很简单,把上面原理搞清楚了后,很 easy,当然我这做法可能不是最好的,有时间可以改进改进。