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

Android RecyclerView打造悬浮效果的实现代码

程序员文章站 2023-12-02 17:44:58
本文介绍了android recyclerview悬浮效果,分享给大家,具体如下: 先看个效果 这是一个city列表,每个city都有所属的province,需要在...

本文介绍了android recyclerview悬浮效果,分享给大家,具体如下:

先看个效果

Android RecyclerView打造悬浮效果的实现代码

这是一个city列表,每个city都有所属的province,需要在滑动的时候,将对应的province悬浮在顶部。悬浮顶部的province需要根据列表的滑动而适当改变位置,实现“顶上去”的效果。

实现思路:

  1. 利用recyclerview.itemdecoration绘制province(就像绘制分割线一样)
  2. 同一组的city,只绘制一个province
  3. 计算偏移,将当前province固定在顶部
  4. 根据列表滑动,实现偏移效果

itemdecoration

既然是利用recyclerview.itemdecoration实现的悬浮效果,那么有必要了解下它。

itemdecoration字面意思:item的装饰。是的!是装饰!不只是画分割线。

其实itemdecoration的功能非常强大,而我们平时只是用它来实现分割线的效果(至少我是这样)。因此,可能很多同学认为itemdecoration就是用来绘制分割线的。其实不然,itemdecoration的功能远不止是分割线的绘制。

先看下recyclerview.itemdecoration的源码(部分):

public static abstract class itemdecoration {
 ...
 public void ondraw(canvas c, recyclerview parent, state state) {
  ondraw(c, parent);
 }
 public void ondrawover(canvas c, recyclerview parent, state state) {
  ondrawover(c, parent);
 }
 public void getitemoffsets(rect outrect, view view, recyclerview parent, state state) {
  getitemoffsets(outrect, ((layoutparams) view.getlayoutparams()).getviewlayoutposition(),
    parent);
 }
}

里面是我们常用的三个方法:

  1. getitemoffsets:通过rect为每个item设置偏移,用于绘制decoration。
  2. ondraw:通过该方法,在canvas上绘制内容,在绘制item之前调用。(如果没有通过getitemoffsets设置偏移的话,item的内容会将其覆盖)
  3. ondrawover:通过该方法,在canvas上绘制内容,在item之后调用。(画的内容会覆盖在item的上层)

recyclerview 的背景、ondraw绘制的内容、item、ondrawover绘制的内容,各层级关系如下:

Android RecyclerView打造悬浮效果的实现代码

层级关系

绘制分割线

先看看一般的分割线绘制。

定义分割线高度

private int mheight = 5; //分割线高度

通过getitemoffsets预留空间

@override
public void getitemoffsets(rect outrect, view view, recyclerview parent, recyclerview.state state) {
 super.getitemoffsets(outrect, view, parent, state);
 int position = parent.getchildadapterposition(view);
 if (position != 0) {
  //第一个item预留空间
  outrect.top = mheight;
 }
}

然后在ondraw中绘制

@override
public void ondraw(canvas c, recyclerview parent, recyclerview.state state) {
 super.ondraw(c, parent, state);
 final int left = parent.getleft();
 final int right = parent.getright();
 final int childcount = parent.getchildcount();
 for (int i = 0; i < childcount; i++) {
  final view childview = parent.getchildat(i);
  final int bottom = childview.gettop();
  final int top = bottom - mheight;
  c.drawrect(left, top, right, bottom, mpaint);
 }
}

获取当前recyclerview的item数量,遍历每个item。在对应的位置绘制一个高度为mheight的矩形 ,从而实现分割线的效果。

Android RecyclerView打造悬浮效果的实现代码

(详情代码见底部链接)

打造悬浮效果

这是一个城市列表,根据省份分组,相同的城市只会显示一个省份。滚动城市列表时,省份会悬浮在顶部。效果如下:

Android RecyclerView打造悬浮效果的实现代码

实现

由于需要悬浮效果,所以需要在ondrawover中绘制分组。

定义一个interface,根据position通过接口方法getgroupname获取当前省名(由activity实现)

public interface grouplistener {
 string getgroupname(int position);
}

创建stickydecoration继承recyclerview.itemdecoration

定义isfirstingroup方法。根据前一个省份,判断当前是否为新的省份

//判断是不是组中的第一个位置
//根据前一个组名,判断当前是否为新的组
private boolean isfirstingroup(int pos) {
 if (pos == 0) {
 return true;
 } else {
 string prevgroupid = getgroupname(pos - 1);
 string groupid = getgroupname(pos);
 return !textutils.equals(prevgroupid, groupid);
 }
}

通过position,对比上一个省份名称,判断当前省是否为第一个

重写getitemoffsets方法,为悬浮栏预留空间

@override
public void getitemoffsets(rect outrect, view view, recyclerview parent, recyclerview.state state) {
 super.getitemoffsets(outrect, view, parent, state);
 int pos = parent.getchildadapterposition(view);
 string groupid = getgroupname(pos);
 if (groupid == null) return;
 //只有是同一组的第一个才显示悬浮栏
 if (pos == 0 || isfirstingroup(pos)) {
 outrect.top = mgroupheight;
 }
}

只有第一个item或者新的省份才为悬浮栏预留空间

重写ondrawover方法(重点)

@override
public void ondrawover(canvas c, recyclerview parent, recyclerview.state state) {
super.ondrawover(c, parent, state);
final int itemcount = state.getitemcount();
final int childcount = parent.getchildcount();
final int left = parent.getleft() + parent.getpaddingleft();
final int right = parent.getright() - parent.getpaddingright();
string pregroupname;  //标记上一个item对应的group
string currentgroupname = null;  //当前item对应的group
for (int i = 0; i < childcount; i++) {
 view view = parent.getchildat(i);
 int position = parent.getchildadapterposition(view);
 pregroupname = currentgroupname;
 currentgroupname = getgroupname(position);
 if (currentgroupname == null || textutils.equals(currentgroupname, pregroupname))
  continue;
 int viewbottom = view.getbottom();
 float top = math.max(mgroupheight, view.gettop());//top 决定当前顶部第一个悬浮group的位置
 if (position + 1 < itemcount) {
  //获取下个groupname
  string nextgroupname = getgroupname(position + 1);
  //下一组的第一个view接近头部
  if (!currentgroupname.equals(nextgroupname) && viewbottom < top) {
   top = viewbottom;
  }
 }
 //根据top绘制group
 c.drawrect(left, top - mgroupheight, right, top, mgroutpaint);
 paint.fontmetrics fm = mtextpaint.getfontmetrics();
 //文字竖直居中显示
 float baseline = top - (mgroupheight - (fm.bottom - fm.top)) / 2 - fm.bottom;
 c.drawtext(currentgroupname, left + mleftmargin, baseline, mtextpaint);
 }
}

通过变量pregroupid和currentgroupid来保存当前分组名和上一个分组名。当前item与上一个item为同一个分组时,跳过该item的绘制。

其中代码:

float top = math.max(mgroupheight, view.gettop());

根据当前item的位置确定绘制分组的位置。top将在mgroupheight和view.gettop()中取最大值,也就是说top将不会小于mgroupheight,这样就能实现吸顶效果。

其中代码:

if (!currentgroupname.equals(nextgroupname) && viewbottom < top) {
  top = viewbottom;
 }

当下个分组的顶部(当前item的底部viewbottom可近似认为下个item的顶部)距离recyclerview顶部小于top时,偏移当前分组位置。实现下一组上滑时候,当前分组上移;上一组下滑的时候,当前分组下移。
最后计算baseline,并绘制背景和文字。

到目前为止,一个带有悬浮功能的列表就实现了。

(详细代码见底部链接)

Android RecyclerView打造悬浮效果的实现代码

进阶

当我们利用itemdecoration实现文字的悬浮的时候,是不是还可以搞点事情~ ~我有个大胆的想法
只有文字的悬浮怎么行!我还希望可以再来个icon?再来几个textview?来个自定义布局?那就来个自定义布局吧。

实现

实现的原理跟上面一样,由于需要自定义布局,所以需要在接口中添加一个获取view的方法。

定义powergrouplistener

public interface powergrouplistener {
 string getgroupname(int position);
 view getgroupview(int position);
}

相比之前,多了个getgroupview方法,用来获取view。

在ondrawover中绘制

@override
public void ondrawover(canvas c, recyclerview parent, recyclerview.state state) {
 ...
 for (int i = 0; i < childcount; i++) {
  ...
  //根据position获取view
  view groupview = mgrouplistener.getgroupview(position);
  if (groupview == null) return;
  viewgroup.layoutparams layoutparams = new viewgroup.layoutparams(viewgroup.layoutparams.wrap_content, mgroupheight);
groupview.setlayoutparams(layoutparams);
  groupview.setdrawingcacheenabled(true);
  groupview.measure(
    view.measurespec.makemeasurespec(0, view.measurespec.unspecified),
    view.measurespec.makemeasurespec(0, view.measurespec.unspecified));
  //指定高度、宽度的groupview
  groupview.layout(0, 0, right, mgroupheight);
  groupview.builddrawingcache();
  bitmap bitmap = groupview.getdrawingcache();
  c.drawbitmap(bitmap, left, top - mgroupheight, null);
 }
}

在原来的基础上做了点修改,通过接口的getgroupview方法获取需要绘制的分组view,将得到的view绘制到指定位置。

效果:

Android RecyclerView打造悬浮效果的实现代码

(详细代码见底部链接)

源码链接

已封装成库,欢迎来提issues

repositories {
 jcenter()// if not already there
}
dependencies {
 compile 'com.gavin.com.library:stickydecoration:1.0.2'
}

详细用法级源码请看github

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