Android实现万能自定义阴影控件实例代码
目录介绍
- 01.阴影效果有哪些实现方式
- 02.实现阴影效果api
- 03.设置阴影需要注意哪些
- 04.常见shape实现阴影效果
- 05.自定义阴影效果控件
- 06.如何使用该阴影控件
- 07.在recyclerview中使用注意点
01.阴影效果有哪些实现方式
阴影效果有哪些实现方式
- 第一种:使用cardview,但是不能设置阴影颜色
- 第二种:采用shape叠加,存在后期ui效果不便优化
- 第三种:ui切图
- 第四种:自定义view
否定上面前两种方案原因分析?
- 第一个方案的cardview渐变色和阴影效果很难控制,只能支持线性或者环装形式渐变,这种不满足需要,因为阴影本身是一个四周一层很淡的颜色包围,在一个矩形框的层面上颜色大概一致,而且这个cardview有很多局限性,比如不能修改阴影的颜色,不能修改阴影的深浅。所以这个思路无法实现这个需求。
- 第二个采用shape叠加,可以实现阴影效果,但是影响ui,且阴影部分是占像素的,而且不灵活。
- 第三个方案询问了一下ui。他们给出的结果是如果使用切图的话那标注的话很难标,身为一个优秀的设计师大多对像素点都和敏感,界面上的像素点有一点不协调那都是无法容忍的。
- 在下面开源案例代码中,我会一一展示这几种不同方案实现的阴影效果。
网上一些介绍阴影效果方案
- 所有在深奥的技术,也都是为需求做准备的。也就是需要实践并且可以用到实际开发中,这篇文章不再抽象介绍阴影效果原理,理解三维空间中如何处理偏移光线达到阴影视差等,网上看了一些文章也没看明白或者理解。这篇博客直接通过调用api实现预期的效果。
阴影是否占位
- 使用cardview阴影不占位,不能设置阴影颜色和效果
- 使用shape阴影是可以设置阴影颜色,但是是占位的
02.实现阴影效果api
思考一下如何实现view阴影效果?
- 首先要明确阴影的实现思路是什么,其实就是颜色导致的视觉错觉。说白了就是在你的card周围画一个渐变的体现立体感的颜色。基于上述思路,我们在一个在一个view上画一个矩形的图形,让他周围有渐变色的阴影即可。于是我们想起几个api:
- 类:paint 用于在android上画图的类,相当于画笔
- 类:canvas 相当于画布,android上的view的绘制都与他相关
- 方法:paint.setshadowlayer可以给绘制的图形增加阴影,还可以设置阴影的颜色
paint.setshadowlayer(float radius, float dx, float dy, int shadowcolor);
- 这个方法可以达到这样一个效果,在使用canvas画图时给视图顺带上一层阴影效果。
简单介绍一下这几个参数:
- radius: 阴影半径,主要可以控制阴影的模糊效果以及阴影扩散出去的大小。
- dx:阴影在x轴方向上的偏移量
- dy: 阴影在y轴方向上的偏移量
- shadowcolor: 阴影颜色。
终于找到了设置颜色的,通过设置shadowcolor来控制视图的阴影颜色。
03.设置阴影需要注意哪些
其中涉及到几个属性,阴影的宽度,view到viewgroup的距离,如果视图和父布局一样大的话,那阴影就不好显示,如果要能够显示出来就必须设置clipchildren=false。
还有就是视图自带的圆角,大部分背景都是有圆角的,比如上图中的圆角,需要达到高度还原阴影的效果就是的阴影的圆角和背景保持一致。
04.常见shape实现阴影效果
多个drawable叠加
- 使用layer-list可以将多个drawable按照顺序层叠在一起显示,默认情况下,所有的item中的drawable都会自动根据它附上view的大小而进行缩放,layer-list中的item是按照顺序从下往上叠加的,即先定义的item在下面,后面的依次往上面叠放
阴影效果代码如下所示
- 这里有多层,就省略了一些。然后直接通过设置控件的background属性即可实现。
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <shape android:shape="rectangle"> <solid android:color="@color/indexshadowcolor_1" /> <corners android:radius="5dip" /> <padding android:bottom="1dp" android:left="1dp" android:right="1dp" android:top="1dp" /> </shape> </item> <item> <shape android:shape="rectangle"> <solid android:color="@color/indexshadowcolor_2" /> <corners android:radius="5dip" /> <padding android:bottom="1dp" android:left="1dp" android:right="1dp" android:top="1dp" /> </shape> </item> …… <item> <shape android:shape="rectangle"> <corners android:radius="5dip" /> <solid android:color="@color/indexcolor" /> </shape> </item> </layer-list>
05.自定义阴影效果控件
首先自定义属性
<declare-styleable name="shadowlayout"> <!--阴影的圆角大小--> <attr name="yc_cornerradius" format="dimension" /> <!--阴影的扩散范围(也可以理解为扩散程度)--> <attr name="yc_shadowlimit" format="dimension" /> <!--阴影颜色--> <attr name="yc_shadowcolor" format="color" /> <!--x轴的偏移量--> <attr name="yc_dx" format="dimension" /> <!--y轴的偏移量--> <attr name="yc_dy" format="dimension" /> <!--左边是否显示阴影--> <attr name="yc_leftshow" format="boolean" /> <!--右边是否显示阴影--> <attr name="yc_rightshow" format="boolean" /> <!--上边是否显示阴影--> <attr name="yc_topshow" format="boolean" /> <!--下面是否显示阴影--> <attr name="yc_bottomshow" format="boolean" /> </declare-styleable>
代码如下所示
/** * <pre> * @author yangchong * blog : https://github.com/yangchong211 * time : 2018/7/20 * desc : 自定义阴影 * revise: */ public class shadowlayout extends framelayout { /** * 阴影颜色 */ private int mshadowcolor; /** * 阴影的扩散范围(也可以理解为扩散程度) */ private float mshadowlimit; /** * 阴影的圆角大小 */ private float mcornerradius; /** * x轴的偏移量 */ private float mdx; /** * y轴的偏移量 */ private float mdy; /** * 左边是否显示阴影 */ private boolean leftshow; /** * 右边是否显示阴影 */ private boolean rightshow; /** * 上边是否显示阴影 */ private boolean topshow; /** * 下面是否显示阴影 */ private boolean bottomshow; private boolean minvalidateshadowonsizechanged = true; private boolean mforceinvalidateshadow = false; public shadowlayout(context context) { super(context); initview(context, null); } public shadowlayout(context context, attributeset attrs) { super(context, attrs); initview(context, attrs); } public shadowlayout(context context, attributeset attrs, int defstyleattr) { super(context, attrs, defstyleattr); initview(context, attrs); } @override protected int getsuggestedminimumwidth() { return 0; } @override protected int getsuggestedminimumheight() { return 0; } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); if (w > 0 && h > 0 && (getbackground() == null || minvalidateshadowonsizechanged || mforceinvalidateshadow)) { mforceinvalidateshadow = false; setbackgroundcompat(w, h); } } @override protected void onlayout(boolean changed, int left, int top, int right, int bottom) { super.onlayout(changed, left, top, right, bottom); if (mforceinvalidateshadow) { mforceinvalidateshadow = false; setbackgroundcompat(right - left, bottom - top); } } public void setinvalidateshadowonsizechanged(boolean invalidateshadowonsizechanged) { minvalidateshadowonsizechanged = invalidateshadowonsizechanged; } public void invalidateshadow() { mforceinvalidateshadow = true; requestlayout(); invalidate(); } private void initview(context context, attributeset attrs) { initattributes(context, attrs); int xpadding = (int) (mshadowlimit + math.abs(mdx)); int ypadding = (int) (mshadowlimit + math.abs(mdy)); int left; int right; int top; int bottom; if (leftshow) { left = xpadding; } else { left = 0; } if (topshow) { top = ypadding; } else { top = 0; } if (rightshow) { right = xpadding; } else { right = 0; } if (bottomshow) { bottom = ypadding; } else { bottom = 0; } setpadding(left, top, right, bottom); } @suppresswarnings("deprecation") private void setbackgroundcompat(int w, int h) { bitmap bitmap = createshadowbitmap(w, h, mcornerradius, mshadowlimit, mdx, mdy, mshadowcolor, color.transparent); bitmapdrawable drawable = new bitmapdrawable(getresources(), bitmap); if (build.version.sdk_int <= build.version_codes.jelly_bean) { setbackgrounddrawable(drawable); } else { setbackground(drawable); } } private void initattributes(context context, attributeset attrs) { typedarray attr = gettypedarray(context, attrs, r.styleable.shadowlayout); if (attr == null) { return; } try { //默认是显示 leftshow = attr.getboolean(r.styleable.shadowlayout_yc_leftshow, true); rightshow = attr.getboolean(r.styleable.shadowlayout_yc_rightshow, true); bottomshow = attr.getboolean(r.styleable.shadowlayout_yc_bottomshow, true); topshow = attr.getboolean(r.styleable.shadowlayout_yc_topshow, true); mcornerradius = attr.getdimension(r.styleable.shadowlayout_yc_cornerradius, 0); mshadowlimit = attr.getdimension(r.styleable.shadowlayout_yc_shadowlimit, 0); mdx = attr.getdimension(r.styleable.shadowlayout_yc_dx, 0); mdy = attr.getdimension(r.styleable.shadowlayout_yc_dy, 0); mshadowcolor = attr.getcolor(r.styleable.shadowlayout_yc_shadowcolor, getresources().getcolor(r.color.default_shadow_color)); } finally { attr.recycle(); } } private typedarray gettypedarray(context context, attributeset attributeset, int[] attr) { return context.obtainstyledattributes(attributeset, attr, 0, 0); } private bitmap createshadowbitmap(int shadowwidth, int shadowheight, float cornerradius, float shadowradius, float dx, float dy, int shadowcolor, int fillcolor) { //根据宽高创建bitmap背景 bitmap output = bitmap.createbitmap(shadowwidth, shadowheight, bitmap.config.argb_8888); //用画板canvas进行绘制 canvas canvas = new canvas(output); rectf shadowrect = new rectf(shadowradius, shadowradius, shadowwidth - shadowradius, shadowheight - shadowradius); if (dy > 0) { shadowrect.top += dy; shadowrect.bottom -= dy; } else if (dy < 0) { shadowrect.top += math.abs(dy); shadowrect.bottom -= math.abs(dy); } if (dx > 0) { shadowrect.left += dx; shadowrect.right -= dx; } else if (dx < 0) { shadowrect.left += math.abs(dx); shadowrect.right -= math.abs(dx); } paint shadowpaint = new paint(); shadowpaint.setantialias(true); shadowpaint.setcolor(fillcolor); shadowpaint.setstyle(paint.style.fill); if (!isineditmode()) { shadowpaint.setshadowlayer(shadowradius, dx, dy, shadowcolor); } canvas.drawroundrect(shadowrect, cornerradius, cornerradius, shadowpaint); return output; } } ```
06.如何使用该阴影控件
十分简单,如下所示
<com.ns.yc.yccardviewlib.shadow.shadowlayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_margintop="10dp" app:yc_cornerradius="18dp" app:yc_dx="0dp" app:yc_dy="0dp" app:yc_shadowcolor="#2a000000" app:yc_shadowlimit="5dp"> <textview android:layout_width="wrap_content" android:layout_height="36dp" android:background="@drawable/shape_show_" android:gravity="center" android:paddingleft="10dp" android:paddingright="10dp" android:text="完全圆形圆角" android:textcolor="#000" /> </com.ns.yc.yccardviewlib.shadow.shadowlayout>
07.在recyclerview中使用注意点
在createshadowbitmap方法中,其实也可以看到需要创建bitmap对象。大家都知道bitmap比较容易造成内存过大,如果是给recyclerview中的item设置阴影效果,那么如何避免重复创建,这时候可以用到缓存。所以可以在上面的基础上再优化一下代码。
先创建key,主要是用于map集合的键。这里为何用对象key作为map的键呢,这里是借鉴了glide缓存图片的思路,可以创建key对象的时候传入bitmap名称和宽高属性,并且需要重写hashcode和equals方法。
public class key { private final string name; private final int width; private final int height; public key(string name, int width, int height) { this.name = name; this.width = width; this.height = height; } public string getname() { return name; } public int getwidth() { return width; } public int getheight() { return height; } @override public boolean equals(object o) { if (this == o) { return true; } if (o == null || getclass() != o.getclass()) { return false; } key key = (key) o; if (width != key.width) { return false; } if (height != key.height) { return false; } return name != null ? name.equals(key.name) : key.name == null; } @override public int hashcode() { int result = name != null ? name.hashcode() : 0; result = 31 * result + width; result = 31 * result + height; return result; } }
然后存取操作如下所示
- 在查找的时候,通过key进行查找。注意:bitmap需要同时满足三个条件(高度、宽度、名称)都相同时才能算是同一个 bitmap。
key key = new key("bitmap", shadowwidth, shadowheight); bitmap output = cache.get(key); if(output == null){ //根据宽高创建bitmap背景 output = bitmap.createbitmap(shadowwidth, shadowheight, bitmap.config.argb_8888); cache.put(key, output); logutil.v("bitmap对象-----","----直接创建对象,然后存入缓存之中---"); } else { logutil.v("bitmap对象-----","----从缓存中取出对象---"); }
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。
上一篇: AngularJS上拉加载问题解决方法