Android达成RecyclerView粘性头部效果,模拟微信账单列表的月份标题平移

news/2025/10/3 17:46:23/文章来源:https://www.cnblogs.com/yxysuanfa/p/19124806

效果链接

https://live.csdn.net/v/494980

1、在res/values/colors.xml中添加:


#07C160#FF3B30

2、月份标题布局 (item_header.xml)



3、主布局 (activity_main.xml)



4、账单项布局 (item_bill.xml)



5、自定义StickyHeaderDecoration 类

/*** @author: 魏* @date: 2025/9/29* 自定义StickyHeaderDecoration类*/
public class StickyHeaderDecoration extends RecyclerView.ItemDecoration {private StickyHeaderListener listener;private View currentStickyView;private int currentStickyPosition = RecyclerView.NO_POSITION;private int lastTranslateY = 0;public StickyHeaderDecoration(StickyHeaderListener listener) {this.listener = listener;}@Overridepublic void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,@NonNull RecyclerView.State state) {super.onDrawOver(c, parent, state);// 查找当前需要置顶的月份标题位置int topChildPosition = findTopVisibleItemPosition(parent);if (topChildPosition == RecyclerView.NO_POSITION) {return;}// 获取当前月份标题位置int headerPosition = listener.getHeaderPositionForItem(topChildPosition);if (headerPosition == RecyclerView.NO_POSITION) {return;}// 获取或创建月份标题视图if (currentStickyPosition != headerPosition || currentStickyView == null) {currentStickyView = listener.getHeaderView(parent, headerPosition);measureHeaderView(currentStickyView, parent);currentStickyPosition = headerPosition;}// 计算标题绘制位置int nextHeaderPosition = findNextHeaderPosition(parent, topChildPosition);int translateY = 0;if (nextHeaderPosition != RecyclerView.NO_POSITION) {View nextHeader = parent.getChildAt(nextHeaderPosition - topChildPosition);if (nextHeader != null) {int bottom = currentStickyView.getBottom();int nextHeaderTop = nextHeader.getTop();// 当两个标题相遇时的动画效果if (nextHeaderTop < bottom) {translateY = nextHeaderTop - bottom;}}}// 平滑过渡动画if (Math.abs(translateY - lastTranslateY) > 1) {ValueAnimator animator = ValueAnimator.ofInt(lastTranslateY, translateY);animator.setDuration(200);animator.setInterpolator(new DecelerateInterpolator());animator.addUpdateListener(animation -> {parent.invalidate();});animator.start();lastTranslateY = translateY;}// 绘制月份标题c.save();c.translate(0, translateY);currentStickyView.draw(c);c.restore();}private int findTopVisibleItemPosition(RecyclerView parent) {LinearLayoutManager layoutManager = (LinearLayoutManager) parent.getLayoutManager();return layoutManager.findFirstVisibleItemPosition();}private int findNextHeaderPosition(RecyclerView parent, int position) {int itemCount = parent.getAdapter().getItemCount();for (int i = position + 1; i < itemCount; i++) {if (listener.isHeader(i)) {return i;}}return RecyclerView.NO_POSITION;}private void measureHeaderView(View headerView, ViewGroup parent) {int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,parent.getPaddingLeft() + parent.getPaddingRight(), headerView.getLayoutParams().width);int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,parent.getPaddingTop() + parent.getPaddingBottom(), headerView.getLayoutParams().height);headerView.measure(childWidth, childHeight);headerView.layout(0, 0, headerView.getMeasuredWidth(), headerView.getMeasuredHeight());}
}

6、定义粘性头部监听接口

/*** @author: 魏* @date: 2025/9/29* 定义粘性头部监听接口*/
public interface StickyHeaderListener {// 判断是否是头部项boolean isHeader(int position);// 获取指定位置对应的头部位置int getHeaderPositionForItem(int itemPosition);// 获取头部视图View getHeaderView(RecyclerView parent, int headerPosition);
}

7、账单数据模型

/*** @author: 魏* @date: 2025/9/29* 数据模型与适配器* 账单数据模型*/
public class BillItem {public static final int TYPE_HEADER = 0;public static final int TYPE_ITEM = 1;private int type;private String month; // 用于标题private String date;  // 用于账单项private String description;private double amount;private boolean isIncome; // 收入或支出public BillItem(int type, String month, String date, String description, double amount, boolean isIncome) {this.type = type;this.month = month;this.date = date;this.description = description;this.amount = amount;this.isIncome = isIncome;}// Getter方法public int getType() { return type; }public String getMonth() { return month; }public String getDate() { return date; }public String getDescription() { return description; }public double getAmount() { return amount; }public boolean isIncome() { return isIncome; }
}

8、账单适配器实现

/*** @author: 魏* @date: 2025/9/29* 账单适配器实现*/
public class BillAdapter extends RecyclerView.Adapterimplements StickyHeaderListener {private List billItems;private Context context;public BillAdapter(Context context, List billItems) {this.context = context;this.billItems = billItems;}@Overridepublic int getItemViewType(int position) {return billItems.get(position).getType();}@NonNull@Overridepublic RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {if (viewType == BillItem.TYPE_HEADER) {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_header, parent, false);return new HeaderViewHolder(view);} else {View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_bill, parent, false);return new BillViewHolder(view);}}@Overridepublic void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {BillItem item = billItems.get(position);if (holder instanceof HeaderViewHolder) {((HeaderViewHolder) holder).tvMonth.setText(item.getMonth());} else if (holder instanceof BillViewHolder) {BillViewHolder billHolder = (BillViewHolder) holder;billHolder.tvDate.setText(item.getDate());billHolder.tvDescription.setText(item.getDescription());// 设置金额颜色(收入绿色,支出红色)if (item.isIncome()) {billHolder.tvAmount.setTextColor(ContextCompat.getColor(context, R.color.green));billHolder.tvAmount.setText(String.format("+¥%.2f", item.getAmount()));} else {billHolder.tvAmount.setTextColor(ContextCompat.getColor(context, R.color.red));billHolder.tvAmount.setText(String.format("-¥%.2f", item.getAmount()));}}}@Overridepublic int getItemCount() {return billItems.size();}// StickyHeaderListener接口实现@Overridepublic boolean isHeader(int position) {return billItems.get(position).getType() == BillItem.TYPE_HEADER;}@Overridepublic int getHeaderPositionForItem(int itemPosition) {for (int i = itemPosition; i >= 0; i--) {if (isHeader(i)) {return i;}}return RecyclerView.NO_POSITION;}@Overridepublic View getHeaderView(RecyclerView parent, int headerPosition) {HeaderViewHolder holder = new HeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_header, parent, false));holder.tvMonth.setText(billItems.get(headerPosition).getMonth());return holder.itemView;}public static class HeaderViewHolder extends RecyclerView.ViewHolder {TextView tvMonth;public HeaderViewHolder(@NonNull View itemView) {super(itemView);tvMonth = itemView.findViewById(R.id.tv_month);}}public static class BillViewHolder extends RecyclerView.ViewHolder {TextView tvDate;TextView tvDescription;TextView tvAmount;public BillViewHolder(@NonNull View itemView) {super(itemView);tvDate = itemView.findViewById(R.id.tv_date);tvDescription = itemView.findViewById(R.id.tv_description);tvAmount = itemView.findViewById(R.id.tv_amount);}}
}

9、Activity集成

/*** @param* @return* @author 魏* @time 2025/9/29 14:40*/
public class MainActivity extends AppCompatActivity {private RecyclerView recyclerView;private BillAdapter adapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);recyclerView = findViewById(R.id.recycler_view);recyclerView.setLayoutManager(new LinearLayoutManager(this));// 生成测试数据List billItems = generateTestData();adapter = new BillAdapter(this, billItems);recyclerView.setAdapter(adapter);// 添加粘性头部装饰器recyclerView.addItemDecoration(new StickyHeaderDecoration(adapter));}private List generateTestData() {List items = new ArrayList<>();// 添加测试数据items.add(new BillItem(BillItem.TYPE_HEADER, "2025年9月", null, null, 0, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "9月29日", "微信支付-超市购物", 128.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "9月28日", "微信支付-工资", 20000.00, true));items.add(new BillItem(BillItem.TYPE_ITEM, null, "9月27日", "微信支付-餐饮", 56.80, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "9月26日", "微信支付-超市购物", 128.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "9月25日", "微信支付-工资", 20000.00, true));items.add(new BillItem(BillItem.TYPE_ITEM, null, "9月24日", "微信支付-餐饮", 56.80, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "9月23日", "微信支付-超市购物", 128.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "9月22日", "微信支付-工资", 20000.00, true));items.add(new BillItem(BillItem.TYPE_ITEM, null, "9月21日", "微信支付-餐饮", 56.80, false));items.add(new BillItem(BillItem.TYPE_HEADER, "2025年8月", null, null, 0, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月31日", "水电费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月30日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月29日", "水电费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月28日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月27日", "水电费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月26日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月25日", "水电费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月24日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月23日", "水电费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月22日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_HEADER, "2025年7月", null, null, 0, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "7月29日", "旅游消费", 1200.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "7月28日", "电影票", 80.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月27日", "旅游消费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月26日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月25日", "旅游消费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月24日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月23日", "旅游消费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月22日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月21日", "旅游消费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月20日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月19日", "旅游消费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月18日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月17日", "旅游消费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月16日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月15日", "旅游消费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月14日", "房租", 2500.00, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月13日", "旅游消费", 320.50, false));items.add(new BillItem(BillItem.TYPE_ITEM, null, "8月12日", "房租", 2500.00, false));return items;}
}

完整的示例地址:
https://gitee.com/weicongxiang/imitation-we-chat-bill-list.git

拿走,拿走!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/926147.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

实用指南:【C语言】char * 、char [ ]、const char * 和 void *的使用以及区别

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

常德网站优化公司东莞大岭山天气预报

【我是谁】 1.学历&#xff1a;22届双非本科校企合作&#xff08;软外&#xff0c;软件工程服务外包&#xff09;&#xff0c;编程课大部分是印度的NIIT老师上课&#xff0c;印式英语一点儿听不懂。。。所以大学全都自学的&#xff0c;和非科班的也没什么区别和优势&#xff0c…

PowerShell注意点

$()和${}的区别: $()表示命令替换,将括号内的命令执行后得到的输出作为值。 例如,$(ls)将会执行ls命令后得到当前目录下的文件列表作为值。 ${}表示变量替换,将大括号内的变量的值作为值。 例如,${a}将取变量a的值…

自动化脚本的自动化执行实践 - 详解

自动化脚本的自动化执行实践 - 详解2025-10-03 17:36 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !imp…

做商业网站的服务费维护费直播型网站开发

在Kotlin中&#xff0c;注解&#xff08;Annotations&#xff09;是一种用于在程序代码中添加元数据的特殊标记。它们提供了对代码的描述性信息&#xff0c;但本身并不会影响程序的运行。注解可以应用于类、方法、属性等程序元素上&#xff0c;用于提供关于这些元素的额外信息。…

m3u8转mp4软件中文版推荐与使用指南

近年来,随着在线视频的普及,m3u8格式的流媒体文件变得越来越常见。不少用户希望将m3u8文件转换为通用的mp4格式,便于本地保存、播放或分享。那么,选择一款好用的m3u8转mp4软件中文版,就成了很多小伙伴的需求。下面…

Unity简易事件分发器

一、EventFunctionusing System; namespace EventCore {public struct EventFunction{public object _caller;public Action _action;}public struct EventFunction<T>{public object _caller;public Action<…

react怎么做pc网站外贸soho建站

本文给大家整理了腾讯视频网页下载_腾讯视频怎么下载视频方面的内容。腾讯视频独播剧质量还是可以的&#xff0c;比较给力的是腾讯视频大量买入了老剧的版权&#xff0c;不乏一些比较经典的港剧&#xff0c;还把这些老剧修复了。腾讯视频播放器是一款支持多种音视频格式的主流播…

实用指南:1、docker入门简介

实用指南:1、docker入门简介pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco"…

调试parlant的大模型配置,最终自己动手写了g4f的模块挂载 - 教程

调试parlant的大模型配置,最终自己动手写了g4f的模块挂载 - 教程pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "…

网站模板如何使用 如何修改吗网站视频插件

合并分支用rebase还是merge&#xff1f; 实际开发工作的时候&#xff0c;我们都是在自己的分支开发&#xff0c;然后将自己的分合并到主分支&#xff0c;那合并分支用2种操作&#xff0c;这2种操作有什么区别呢&#xff1f; git上新建一个项目&#xff0c;默认是有master分支…

迁安做网站教育培训机构设计图

Java核心类库篇6——IO 1、File 1.1、构造方法 方法声明功能介绍public File(File parent, String child)从父抽象路径名和子路径名字符串创建新的 File实例public File(String pathname)通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例public File(String pa…

12380网站建设情况报告网站总体规划设计说明

hive分区重命名后&#xff0c;新的分区的分区大小为0 , 例如 alter table entersv.ods_t_test partition(dt2022-11-08) rename to partition(dt2022-11-21) ods_t_test 的2022-11-21分区大小为0。怎样修复 使用 msck repair table 命令来修复表的元数据&#xff0c;让hive重新…

太极 - MKT

太极 环境 下雨 下午 卧室 附上音乐 (沙石头 鱼儿 本身不也是物质的一部分么,都在不同的层次适应存在。 石头在河里打磨成圆滑,在沙漠变成啥子,这么看好像都是被动的过程。 但本质沙子石头都是原子层面的硅原子在…

佛山营销网站旅游网站建设方案后台

0-1背包理论基础 基础 DP数组与其下标的含义 dp[i][j]&#xff0c;i为物品编号&#xff0c;j为背包容量 dp[i][j]表示从下标为[0-i]的物品里任意取&#xff0c;放进容量为j的背包&#xff0c;价值总和最大是多少。 递推公式 分类&#xff1a;是否要放入下标为i的物品&…

网站建设人员职责分布昌吉网站建设咨询电话

一、智能家居与会议系统 智能家居与会议系统分论坛将于3月28日同期举办&#xff01; 智能会议系统它通过先进的技术手段&#xff0c;提高了会议效率&#xff0c;降低了沟通成本&#xff0c;提升了参会者的会议体验。对于现代企业、政府机构和学术界是不可或缺的。在这里&#x…

题解:P12410 「知りたくなかった、失うのなら」

草 -我ら不会と算に时む复なりlink 说在前面 如果你看了这个东西你最好就看个乐子别真的去写,卡常卡死你。 做法什么的请直接看正文。 注意到其他题解给出了很优美的做法,那么我就来点不优美的。 先设几个数字吧,设…

unity面向组合开发二:EC的代码实践

一、ECCore 需要在Unity项目中使用插件:UniRx,通过UniRx代替Mono的Update,Mono下做轮询性能消耗会有点大。 EntityMono代码:using System; using System.Collections.Generic; using EC; using UniRx; using Unity…

《咳咳,未来编程大师,顶尖程序员的第一条博客》

Helloooooo World!本人目前是一个在校大二的学生,正在备战蓝桥杯,希望有相同目标的朋友联系我,我们可以一起备赛,一起刷题。我的目标是在2026蓝桥杯比赛上拿下国一,哈哈哈哈虽然听起来很扯,但是我是会用拿国一的…

CSP-JF36

CSP-JF36T2 B. 最小的公倍数小题 ((10^L / 210) + 1) * 210 就是最小值#include <bits/stdc++.h> using namespace std;int n; int main(){// for(int i = 2; i <= 18; i++){ // long long x = pow…