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绘制的内容,各层级关系如下:

层级关系

绘制分割线

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

定义分割线高度

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的矩形 ,从而实现分割线的效果。

(详情代码见底部链接)

打造悬浮效果

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

实现

由于需要悬浮效果,所以需要在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,并绘制背景和文字。

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

(详细代码见底部链接)

进阶

当我们利用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绘制到指定位置。

效果:

(详细代码见底部链接)

源码链接

已封装成库,欢迎来提Issues

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

详细用法级源码请看Github

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

(0)

相关推荐

  • android中RecyclerView悬浮吸顶效果

    MultiType-Adapter打造悬浮吸顶效果 注:当前版本只适合配合RecyclerView快速打造一款 展示UI 悬浮吸顶效果,如 通讯录效果,由于实现机制的原因,暂时不支持触摸事件. MultiType-Adapter介绍地址:MultiType-Adapter 是一款轻量级支持多数据类型的 RecyclerView 适配器; 使用简单,完全解耦; 悬浮吸顶效果 ```groovy // root build.gradle repositories { jcenter() maven

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

    本文介绍了Android RecyclerView悬浮效果,分享给大家,具体如下: 先看个效果 这是一个City列表,每个City都有所属的Province,需要在滑动的时候,将对应的Province悬浮在顶部.悬浮顶部的Province需要根据列表的滑动而适当改变位置,实现"顶上去"的效果. 实现思路: 利用RecyclerView.ItemDecoration绘制Province(就像绘制分割线一样) 同一组的City,只绘制一个Province 计算偏移,将当前Province固

  • Android实现顶部悬浮效果

    本文实例为大家分享了Android实现顶部悬浮效果的具体代码,供大家参考,具体内容如下 效果图 布局 <?xml version="1.0" encoding="utf-8"?> <android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http:

  • Android RecyclerView实现悬浮吸顶、分隔线、到底提示效果

    本文中所有效果通过ItemDecoration实现,通过此实现的可以与业务解耦,让RecyclerView的模板更加简洁,不关心任何辅助性ui,github地址 一.顶部吸附效果图 二.顶部不吸附效果图 三.不满一屏效果 四.核心实现点 1.为什么通过ItemDecoration能够实现,原理? ①通过getItemOffsets()方法获取当前模板view的left.top.right.bottom边距,这些留出的间距用于绘制这些辅助性ui. // RecyclerView的measure c

  • Android自定义滑动删除效果的实现代码

    先给大家展示下效果图,如果感觉不错,请参考实现代码: 序言 最近项目中需要用到滑动删除,然后去网上搜了一下,发现现有网上的各种解决办法各式各样,但是还是找不到一个能将所有细节和逻辑处理好的,至于滑动删除部分,我觉得处理的相对比较好的是 QQ(包括处理各种逻辑和细节);最终,苦寻无果,于是决定自己动手,丰衣足食 这篇文章将从现有 Android 滑动删除的痛点,到搭建好一个基本的框架,到最终提供一份完整的 Demo为止,争取为读者提供最大的可定制化 正文 一. 滑动删除的痛点 (1). 现有资料中

  • Android仿微信输入框效果的实现代码

    仿微信输入框效果图: 输入框: <EditText android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="50dp" android:layout_marginRight="50dp" android:background="@drawable/weixin_edit

  • Android仿Flipboard动画效果的实现代码

    1.上原图 前几天在 Hencoder 征稿看到的Filpboard 里的的动画效果: Filipboard.gif 先bb一句:在看本文的同时,如果觉得我写的模糊看不太懂的可以直接拉到文末,配合完整代码再一步一步看. 2.实现 整体思路: 用手机拍下来,逐帧观看了许久,恍然大悟,就是一张纸,折起一边之后,让其对折线绕中心点旋转. 关联自定义 View : Camera 来控制对折幅度, canves 控制旋转. 具体: 每当对折线旋转的时候,图标总是一边是折起来的,一边是平铺的,且中心对称,所

  • android 自定义圆角button效果的实例代码(自定义view Demo)

    概述 在平时开发过程中经常会碰到需要使用圆角button的情况,一般也会包括很多其他小功能,比如要在里面添加img,设置不同的圆角大小等. 针对这样的场景,直接使用创建多个shape,定义多个xml文件也是可以实现的.但是如果使用非常频繁,那么直接自定义一个就会来的非常方便. 甚至在一些情况下,不是可以用shape定义的规则图形,比如需要用到贝塞尔曲线等. 如果全局需要这样风格的view,那么自定义一个View是非常必要的. 本文主要是个demo记录,如有需要的读者可以借鉴学习. Demo 主要

  • Android输入框实时模糊搜索效果的示例代码

    Android输入框实时模糊搜索 很多开发场景会用到搜索框实时模糊搜索来帮助用户输入内容,如图 思路是在EditText 字符变动的时候 弹出ListPopupwindow并更新列表,这样的做法google已经封装为AutoCompleteTextView 用法 mAutoCompleteTextView.setAdapter(adapter); mAutoCompleteTextView.setFocusable(true); mAutoCompleteTextView.setOnItemCl

  • android实现音乐跳动效果的示例代码

    效果图 实现 整体的流程图如下 上面主要步骤分为3个 1.计算宽度能放下多少列的音频块. 2.计算每一列中音频块的个数 3.绘制音频块 1.计算宽度能放下多少列的音频块. 设置音频块的宽度为danceWidth,音频块横向之间的间距为danceGap,那么可以算出能放的列数: /** * 先计算当前宽度能够放下多少个音频块 */ val widthNum = (getAvailableWith() / (danceGap + danceWidth)).toInt() /** * 获取可以用的宽度

  • Android Flutter实现点赞效果的示例代码

    目录 前言 绘制小手 完整源码 前言 点赞这个动作不得不说在社交.短视频等App中实在是太常见了,当用户手指按下去的那一刻,给用户一个好的反馈效果也是非常重要的,这样用户点起赞来才会有一种强烈的我点了赞的效果,那么今天我们就用Flutter实现一个掘金App上的点赞效果. 首先我们看下掘金App的点赞组成部分,有一个小手,点赞数字.点赞气泡效果,还有一个震动反馈,接下来我们一步一步实现. 知识点:绘制.动画.震动反馈 绘制小手 这里我们使用Flutter的Icon图标中的点赞小手,Icons图标

随机推荐