欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

如何为RecyclerView添加分隔线

程序员文章站 2024-02-27 16:41:33
我在简书上发布了我个人的第一篇技术文档:recyclerview系列之: recyclerview系列之(1)为recyclerview添加header和footer,也很...

我在简书上发布了我个人的第一篇技术文档:recyclerview系列之: recyclerview系列之(1)为recyclerview添加header和footer,也很有幸,能够得到那么多人的支持,这让我迫不及待的赶紧写第二篇文章。今天我将谈谈:为recyclerview添加分隔线。

一. 理解listview和recyclerview中的childview
在讲为item加入分割线本质的前,先来介绍,认识一下childview,也就是平时我们用到的listview,recyclerview中的getchildat(int position)这个返回的childview是哪一部分?到底是哪一部分呢?一开始的时候,我理解错了,但是经过下面两张图这么一比较,你就明白了:

如何为RecyclerView添加分隔线

item布局layout_margin == 0

如何为RecyclerView添加分隔线

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就要往下平移,平移的量就是分隔线的高度。不理解每关系,后面看代码就容易理解了。

现在我们知道了如何添加了,就是通过画,那到底是画在哪里呢?画的位置又怎么确定呢?下面看图:

如何为RecyclerView添加分隔线

分隔线的位置图

我现在拿画横线来说,从上面这个图中,我们很容易就可以看到,我们画分隔线的位置,是在每一个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进行关联了。

到这里所有的工作就完成了,下面展示一下运行结果:

如何为RecyclerView添加分隔线

竖屏效果图

如何为RecyclerView添加分隔线

横屏效果图

经过几个小时的写作,终于搞定了,虽然仅仅是一个添加分隔线的功能,但是还是想尽可能的通过自己的语言去理解,去认知它的原理,这样做起来就简单多了。一开始的时候,我夜不知道怎么去用,也参考了别人写的文章,特别是鸿洋大神的:android recyclerview 使用完全解析 体验艺术般的控件, 写得特别的棒,从中也学到了一些知识。

好了,这篇文章暂时写到这里了,简单的介绍了一些recyclerview分隔线的原理和添加方法,希望大家能够多多交流,过几天我会继续写下一篇文章,recyclerview系列之(3):为recyclerview添加下拉刷新和上拉加载的功能。最后还是要感谢大家,感谢这个平台,能够让我们一起交流,一切学习。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。