如何为RecyclerView添加分隔线
我在简书上发布了我个人的第一篇技术文档:recyclerview系列之: recyclerview系列之(1)为recyclerview添加header和footer,也很有幸,能够得到那么多人的支持,这让我迫不及待的赶紧写第二篇文章。今天我将谈谈:为recyclerview添加分隔线。
一. 理解listview和recyclerview中的childview
在讲为item加入分割线本质的前,先来介绍,认识一下childview,也就是平时我们用到的listview,recyclerview中的getchildat(int position)这个返回的childview是哪一部分?到底是哪一部分呢?一开始的时候,我理解错了,但是经过下面两张图这么一比较,你就明白了:
item布局layout_margin == 0
item布局layout_margin == 16dp
下面看代码的区别:
第一张图的代码, 也就是每一个list_item的布局文件(下同)如下:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="50dp"> <textview android:id="@+id/list_item" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textsize="20sp" android:textcolor="#262526" android:background="#08da1d"/> </linearlayout>
第二张图的代码:
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="50dp" android:layout_margin="16dp"> <textview android:id="@+id/list_item" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textsize="20sp" android:textcolor="#262526" android:background="#08da1d"/> </linearlayout>
仔细看一下,它们的不同之处, 就是第二个图的代码中多了:
android:layout_margin = "16dp"
就多这一句而已。
所以到这里我们应该知道了childview是哪一部分了,就是图二中绿色这一部分,边距这一部分并不属于childview, 而是属于childview的布局。
这样我们了解childview之后,下面再来理解加入分隔线的原理就简单多了。
二. 理解加入分隔线的原理
在listview中,google为我们提供了setdivider(drawable divider)这样的方法来设置分隔线,那么在recyclerview中,google又为我们提供了什么样的方法去添加分隔线呢?通过查看官方文档,它,提供了:additemdecoration(recyclerview.itemdecoration decor)这个方法了设置分隔线,那问题又来了,recyclerview.itemdecoration是什么东西呢?继续查:然后发现如下:它原来是一个类,里面封装了三个方法:
(1)void getitemoffsets ()
(2)void ondraw ()
(3)void ondrawover ()
通过上面的三个方法,可以看出,这是要自己直接画上去,准确的说这几个方法是:添加divider,主要是找到添加divider的位置, 而divider是在drawable文件中写好了的。 利用ondraw和ondrawover都差不多,我们在创建自己的decoration类继承recyclerview.itemdecoration的时候,我们只要重写getitemoffsets(),还有ondraw()和ondrawover两者其中之一就可以了.
那getitemoffsets()方法有什么用呢?从字面意思就是item要偏移, 由于我们在item和item之间加入了分隔线,线其实本质就是一个长方形,也是用户自定义的,既然线也有长宽高,就画横线来说,上面的item加入了分隔线,那下面的item就要往下平移,平移的量就是分隔线的高度。不理解每关系,后面看代码就容易理解了。
现在我们知道了如何添加了,就是通过画,那到底是画在哪里呢?画的位置又怎么确定呢?下面看图:
分隔线的位置图
我现在拿画横线来说,从上面这个图中,我们很容易就可以看到,我们画分隔线的位置,是在每一个item的布局之间,注意:是布局之间。
好了,我们确定了画在哪里,那我们怎么确定画线的具体的坐标位置呢?也就是我们要确定:分隔线的left, top, right, bottom. 在adapter中,我们很容易通过parent(这个parent它其实就是我们能看到的部分)获取每一个childview:
(1)left:parent.getpaddingleft()
(2)right: parent. getwidth()-parent.getpaddingright();
(3)top : 就是红线的上面:我们通过childview.getbottom()来得到这个item的底部的高度,也就是蓝线位置,蓝线和红线之间间距:就是这个item布局文件的:layout_marginbottom, 然后top的位置就是两者之和。
(4)bttom: 就是top加上分隔线的高度:top+线高
通过上面的解析,你也许知道了加入分隔线的原理,不理解也没有关系,说也不是说得很清楚,下面直接上代码,通过代码来理解。
三. talk is cheap, show you the code.
(1)首先,先来看主布局文件:activity_main.xml:
<?xml version="1.0" encoding="utf-8"?> <android.support.design.widget.coordinatorlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitssystemwindows="true" tools:context="com.study.wnw.recyclerviewdivider.mainactivity"> <android.support.v7.widget.recyclerview android:id="@+id/recyclerview" android:layout_width="match_parent" android:layout_height="match_parent"> </android.support.v7.widget.recyclerview> </android.support.design.widget.coordinatorlayout>
我在这里面仅仅加入了一个recyclerview
(2)recyclerview里面每个item的布局文件:item_view.xml
<?xml version="1.0" encoding="utf-8"?> <linearlayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="50dp" android:layout_margin="16sp"> <textview android:id="@+id/list_item" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:textsize="20sp" android:textcolor="#f7f4f7" android:background="#08da1d"/> </linearlayout>
这也没有什么可讲的,就是在里面添加一个textview用来显示文本
(3)我们recyclerview的适配器myadapater.java:
package com.study.wnw.recyclerviewdivider; import android.content.context; import android.support.v7.widget.recyclerview; import android.view.layoutinflater; import android.view.view; import android.view.viewgroup; import android.widget.textview; import java.util.list; import java.util.concurrent.copyonwritearraylist; /** * created by wnw on 16-5-22. */ public class myadapter extends recyclerview.adapter<recyclerview.viewholder> { //定义一个集合,接收从activity中传递过来的数据和上下文 private list<string> mlist; private context mcontext; myadapter(context context, list<string> list){ this.mcontext = context; this.mlist = list; } //得到child的数量 @override public int getitemcount() { return mlist.size(); } //创建childview @override public recyclerview.viewholder oncreateviewholder(viewgroup parent, int viewtype) { view layout = layoutinflater.from(mcontext).inflate(r.layout.item_view, parent, false); return new myholder(layout); } //将数据绑定到每一个childview中 @override public void onbindviewholder(recyclerview.viewholder holder, int position) { if (holder instanceof myholder){ final string itemtext = mlist.get(position); ((myholder)holder).tv.settext(itemtext); } } // 通过holder的方式来初始化每一个childview的内容 class myholder extends recyclerview.viewholder{ textview tv; public myholder(view itemview) { super(itemview); tv = (textview)itemview.findviewbyid(r.id.list_item); } } }
好了,这里也没有什么好讲的,也不是我们这篇文章的重点,下面重点来了。
(4)我们自定义的mydecoration.java:(继承recyclerview.itemdecoration)
package com.study.wnw.recyclerviewdivider; import android.content.context; import android.content.res.typedarray; import android.graphics.canvas; import android.graphics.rect; import android.graphics.drawable.drawable; import android.support.v7.widget.linearlayoutmanager; import android.support.v7.widget.recyclerview; import android.util.log; import android.view.view; /** * created by wnw on 16-5-22. */ public class mydecoration extends recyclerview.itemdecoration{ private context mcontext; private drawable mdivider; private int morientation; public static final int horizontal_list = linearlayoutmanager.horizontal; public static final int vertical_list = linearlayoutmanager.vertical; //我们通过获取系统属性中的listdivider来添加,在系统中的apptheme中设置 public static final int[] atrrs = new int[]{ android.r.attr.listdivider }; public mydecoration(context context, int orientation) { this.mcontext = context; final typedarray ta = context.obtainstyledattributes(atrrs); this.mdivider = ta.getdrawable(0); ta.recycle(); setorientation(orientation); } //设置屏幕的方向 public void setorientation(int orientation){ if (orientation != horizontal_list && orientation != vertical_list){ throw new illegalargumentexception("invalid orientation"); } morientation = orientation; } @override public void ondraw(canvas c, recyclerview parent, recyclerview.state state) { if (morientation == horizontal_list){ drawverticalline(c, parent, state); }else { drawhorizontalline(c, parent, state); } } //画横线, 这里的parent其实是显示在屏幕显示的这部分 public void drawhorizontalline(canvas c, recyclerview parent, recyclerview.state state){ int left = parent.getpaddingleft(); int right = parent.getwidth() - parent.getpaddingright(); final int childcount = parent.getchildcount(); for (int i = 0; i < childcount; i++){ final view child = parent.getchildat(i); //获得child的布局信息 final recyclerview.layoutparams params = (recyclerview.layoutparams)child.getlayoutparams(); final int top = child.getbottom() + params.bottommargin; final int bottom = top + mdivider.getintrinsicheight(); mdivider.setbounds(left, top, right, bottom); mdivider.draw(c); //log.d("wnw", left + " " + top + " "+right+" "+bottom+" "+i); } } //画竖线 public void drawverticalline(canvas c, recyclerview parent, recyclerview.state state){ int top = parent.getpaddingtop(); int bottom = parent.getheight() - parent.getpaddingbottom(); final int childcount = parent.getchildcount(); for (int i = 0; i < childcount; i++){ final view child = parent.getchildat(i); //获得child的布局信息 final recyclerview.layoutparams params = (recyclerview.layoutparams)child.getlayoutparams(); final int left = child.getright() + params.rightmargin; final int right = left + mdivider.getintrinsicwidth(); mdivider.setbounds(left, top, right, bottom); mdivider.draw(c); } } //由于divider也有长宽高,每一个item需要向下或者向右偏移 @override public void getitemoffsets(rect outrect, view view, recyclerview parent, recyclerview.state state) { if(morientation == horizontal_list){ //画横线,就是往下偏移一个分割线的高度 outrect.set(0, 0, 0, mdivider.getintrinsicheight()); }else { //画竖线,就是往右偏移一个分割线的宽度 outrect.set(0, 0, mdivider.getintrinsicwidth(), 0); } } }
从上面的代码中,我们还通过系统属性来适应屏幕的横屏和竖屏,然后确定画横的,还是竖的divider,其实在里面我们做了三件事,第一件是:获取到系统中的listdivider, 我们就是通过它在主题中去设置的,下面第(6)小点看一下代码就知道了。第二件事:就是找到我们需要添加divider的位置,从ondraw方法中去找到,并将divider添加进去。第三个是:得到item的偏移量。
(5)看看我们的mainactivity.java
package com.study.wnw.recyclerviewdivider; import android.os.bundle; import android.support.v7.app.appcompatactivity; import android.support.v7.widget.linearlayoutmanager; import android.support.v7.widget.recyclerview; import java.util.arraylist; import java.util.list; public class mainactivity extends appcompatactivity { //定义recyclerview private recyclerview mrecyclerview = null; //定义一个list集合,用于存放recyclerview中的每一个数据 private list<string> mdata = null; //定义一个adapter private myadapter madapter; //定义一个linearlayoutmanager private linearlayoutmanager mlayoutmanager; @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); //recyclerview三步曲+layoutmanager initview(); initdata(); madapter = new myadapter(this,mdata); mrecyclerview.setlayoutmanager(mlayoutmanager); mrecyclerview.setadapter(madapter); //这句就是添加我们自定义的分隔线 mrecyclerview.additemdecoration(new mydecoration(this, mydecoration.vertical_list)); } //初始化view private void initview(){ mlayoutmanager = new linearlayoutmanager(this); mrecyclerview = (recyclerview)findviewbyid(r.id.recyclerview); } //初始化加载到recyclerview中的数据, 我这里只是给每一个item添加了string类型的数据 private void initdata(){ mdata = new arraylist<string>(); for (int i = 0; i < 20; i++){ mdata.add("item" + i); } } }
(6)分隔线divider的drawable文件:divider..xml
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="#7b7a7a"/> <size android:height="1dp"/> </shape>
我们在这里面,画了一个:rectangle, 给它填充颜色,还有高度,这样就搞定了,高度小,显示出来也是一条线:其实线的本质就是长方形。这里可以根据个人需要,画不同类型的divider
(7)在styles.xml的apptheme中,设置listdivider为我们的divider.xml文件:
<style name="apptheme" parent="theme.appcompat.light.darkactionbar"> <item name="android:listdivider">@drawable/divider</item> </style>
这样,我们将系统的listdivider设置成我们自定义的divider. 还记得我们在mydecoration中获取系统的listdivider这个属性吗,这样通过这个属性,我们就可以将我们的divider.xml文件和mydecoration.java进行关联了。
到这里所有的工作就完成了,下面展示一下运行结果:
竖屏效果图
横屏效果图
经过几个小时的写作,终于搞定了,虽然仅仅是一个添加分隔线的功能,但是还是想尽可能的通过自己的语言去理解,去认知它的原理,这样做起来就简单多了。一开始的时候,我夜不知道怎么去用,也参考了别人写的文章,特别是鸿洋大神的:android recyclerview 使用完全解析 体验艺术般的控件, 写得特别的棒,从中也学到了一些知识。
好了,这篇文章暂时写到这里了,简单的介绍了一些recyclerview分隔线的原理和添加方法,希望大家能够多多交流,过几天我会继续写下一篇文章,recyclerview系列之(3):为recyclerview添加下拉刷新和上拉加载的功能。最后还是要感谢大家,感谢这个平台,能够让我们一起交流,一切学习。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
推荐阅读