简单好用的阴影库 ShadowLayout

在开发过程中常会遇见带阴影效果的控件,通过 SDK 提供的 CardViewandroid:elevation可以实现,也可以通过 .9 图实现。但是使用这两种方法会有一些弊端,比如:不可以控制阴影颜色,如果使用 .9 图片过多,会增加 APK 安装文件的体积。针对以上问题,自己写了一个为控件添加阴影的库 —- ShadowLayout。接下来就 ShadowLayout 展开本文,本文主要分为以下两个部分:

  1. 关于 ShadowLayout 的使用;
  2. 关于 ShadowLayout 的原理。 <!– more –>

关于 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 中添加如下布局文件:

    <com.lijiankun24.shadowlayout.ShadowLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="30dp"
        app:shadowColor="#66000000"
        app:shadowDx="0dp"
        app:shadowDy="3dp"
        app:shadowRadius="10dp"
        app:shadowSide="all">        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/white"
            android:contentDescription="@null"
            android:src="@mipmap/ic_launcher"/>
    </com.lijiankun24.shadowlayout.ShadowLayout>

上面 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 的原理

ShadowLayout 的原理其实非常简单,大概可以分为以下几步: 1. 通过自定义属性获取阴影的相关属性,包括:阴影颜色、阴影范围大小、阴影显示边界、阴影 x 轴和 y 轴的偏移量; 2. 在 onLayout()方法中获取到阴影应该显示的范围,并设置此 ShadowLayoutPadding 值以给阴影的显示留出空间; 3. 在 onDraw() 方法中使用 CanvasPaint 的方法绘制阴影。

绘制阴影最重要的两个方法: * Paint.setShadowLayer(float radius, float dx, float dy, int shadowColor)设置阴影的大小、颜色、x 轴和 y 轴的偏移量 * canvas.drawRect(RectF rect, Paint paint) 设置阴影显示的位置

ShadowLayout 库中只有一个文件 —- ShadowLayout.javaShadowLayoutRelativeLayout 的子类,其源码如下所示(有较为详细的注释):

/**
 * ShadowLayout.java
 * <p>
 * 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 体验,如果有什么问题欢迎指出。我的工作邮箱:jiankunli24@gmail.com

原文发布于微信公众号 - 非著名程序员(non-famous-coder)

原文发表时间:2017-10-31

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏学海无涯

Android开发之Activity转场动画

引子 相信开发过iOS的程序员都知道iOS ViewController之间的跳转动画非常多,很酷对不对?这让开发Android的羡慕不已,曾几何时,Andro...

48160
来自专栏QQ音乐技术团队的专栏

小窗播放视频的原理和实现(下)

本文对小窗视频播放进行了详细的研究,针对几种实现方案进行了深入的对比分析,进而给出实现小窗视频播放的最优解。其中通过对系统源码的分析,详细探究了如何完美地实现移...

995100
来自专栏向治洪

SliferMenu详解

SlidingMenu简介: SlidingMenu的是一种比较新的设置界面或配置界面效果,在主界面左滑或者右滑出现设置界面,能方便的进行各种操作.目前有大...

20950
来自专栏三流程序员的挣扎

Android 透明状态栏(伪沉浸式)

而由于 Android API 的不同,需要考虑 4.4、5.0、6.0 前后的不同。

79820
来自专栏懒人开发

CoordinatorLayout使用(三):NestedScrollView & 嵌套滑动事件

上一篇,我们大体理解了 Behavior流程 和 事件流 具体代码可以见 https://github.com/2954722256/use_little_d...

3.3K40
来自专栏潇涧技术专栏

Fab and Dialog Morphing Animation

Fab and Dialog Morphing Animation on Android.

9220
来自专栏androidBlog

你真的了解View的坐标吗?

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/gdutxiaoxu/article/details/...

9720
来自专栏向治洪

listview滑动删除

今天还是给大家带来自定义控件的编写,自定义一个ListView的左右滑动删除Item的效果,这个效果之前已经实现过了,有兴趣的可以看下Android 使用Scr...

28270
来自专栏郭霖

Android自定义View的实现方法,带你一步步深入了解View(四)

不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回顾一下,我们一共学习了LayoutInflater的原理分析、视图的绘制流程、视图的状态及重...

31590
来自专栏Android干货园

Android自定义下拉刷新动画--仿百度外卖下拉刷新

版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/51...

15830

扫码关注云+社区

领取腾讯云代金券