【安卓笔记】RecyclerView之ItemDecoration实现吸顶效果
0. 环境:
电脑:Windows10
Android Studio: 2024.3.2
编程语言: Java
Gradle version:8.11.1
Compile Sdk Version:35
Java 版本:Java11
1. ItemDecoration简单介绍
itemDecoration允许给具体的view添加具体的图画或者layout的偏移。大部分用于给每个item之间画分割线。
调用方法:recyclerView.addItemDecoration()
我们将使用ItemDecoration,来实现吸顶效果
2. 吸顶效果展示
应用场景:城市--省份,姓氏--首字母,列表--首字母 等
3. 实现步骤:
关键代码:
ProvinceDecoration.java
package com.liosen.androidnote;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;public class ProvinceDecoration extends RecyclerView.ItemDecoration {private Context context;private int provinceHeight; // 省份部分高度private Paint headPaint; // 头部画笔,即 吸顶那部分用到的画笔private Paint textPaint; // 文字画笔private Rect textRect; // 文字Rectpublic ProvinceDecoration(Context context) {this.context = context;this.provinceHeight = dp2px(context, 100); // 设置100,可调整headPaint = new Paint(); // 实例化头部画笔headPaint.setColor(Color.GREEN); // 设置头部画笔的颜色textPaint = new Paint(); // 实例化文字画笔textPaint.setTextSize(50); // 设置文字画笔的大小textPaint.setColor(Color.WHITE); // 设置文字画笔的颜色textRect = new Rect(); // 设置文字画笔必须要Rect}@Overridepublic void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {super.onDraw(c, parent, state);if (parent.getAdapter() instanceof ProvinceAdapter) {ProvinceAdapter adapter = (ProvinceAdapter) parent.getAdapter();int count = parent.getChildCount(); // 获取当前屏幕item的个数int left = parent.getPaddingLeft(); // 获取paddingLeftint right = parent.getWidth() - parent.getPaddingRight(); // 计算rightfor (int i = 0; i < count; i++) {// 获取ViewView view = parent.getChildAt(i);// 获取view的布局位置int pos = parent.getChildLayoutPosition(view);// 是否为省份boolean isProvince = adapter.isProvince(pos);if (isProvince) {// 如果为省份,则用画笔画出头部部分:headPaint、文字部分textPainc.drawRect(left, view.getTop() - provinceHeight, right, view.getTop(), headPaint);String provinceName = adapter.getProvinceName(pos);textPaint.getTextBounds(provinceName, 0, provinceName.length(), textRect);c.drawText(provinceName, left + 10, view.getTop() - provinceHeight / 2 + textRect.height() / 2, textPaint);} else {// 如果是城市,则画出分割线c.drawRect(left, view.getTop() - 1, right, view.getTop(), headPaint);}}}}@Overridepublic void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {super.onDrawOver(c, parent, state);if (parent.getAdapter() instanceof ProvinceAdapter) {ProvinceAdapter adapter = (ProvinceAdapter) parent.getAdapter();// 返回可见区域内,第一个item的position// 注意!!!如果你的recyclerView被ScrollView或者NestedScrollView 包裹,此处只会返回0;// 解决方法: 1. 要么移除ScrollView/NestedScrollView// 2. 布局文件中设置recyclerView: android:nestedScrollingEnabled="false",并且android:layout_height="wrap_content"// 3. 自己手动计算可见位置int firstPos = ((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();// 获取对应position的itemViewView itemView = parent.findViewHolderForAdapterPosition(firstPos).itemView;int left = parent.getPaddingLeft();int top = parent.getPaddingTop();int right = parent.getWidth() - parent.getPaddingRight();// 当第二个是省份的时候boolean isProvince = adapter.isProvince(firstPos + 1);String provinceName = adapter.getProvinceName(firstPos);if (isProvince) {// 如果是省份,则需要向上推走 上一个省份int bottom = Math.min(provinceHeight, itemView.getBottom());c.drawRect(left, top, right, top + bottom, headPaint);textPaint.getTextBounds(provinceName, 0, provinceName.length(), textRect);c.drawText(provinceName, left + 10, top + bottom - provinceHeight / 2 + textRect.height() / 2, textPaint);} else {// 如果不是省份,则继续吸顶c.drawRect(left, top, right, top + provinceHeight, headPaint);textPaint.getTextBounds(provinceName, 0, provinceName.length(), textRect);c.drawText(provinceName, left + 10, top + provinceHeight / 2 + textRect.height() / 2, textPaint);}}}@Overridepublic void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {super.getItemOffsets(outRect, view, parent, state);if (parent.getAdapter() instanceof ProvinceAdapter) {ProvinceAdapter adapter = (ProvinceAdapter) parent.getAdapter();int pos = parent.getChildLayoutPosition(view);boolean isProvince = adapter.isProvince(pos);if (isProvince) {// 如果是省份,则 顶部腾出省份高度,也就是初始化的时候传入的100outRect.set(0, provinceHeight, 0, 0);} else {// 如果不是省份,则画出分割线,这边设置分割线高度为1outRect.set(0, 1, 0, 0);}}}/*** @return dp转px*/private int dp2px(Context context, float dpValue) {float scale = context.getResources().getDisplayMetrics().density;return (int) (dpValue * scale * 0.5f);}
}
ProvinceAdapter.java(适配器adapter部分,没啥好讲的。基本操作)
package com.liosen.androidnote;import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;import java.util.List;public class ProvinceAdapter extends RecyclerView.Adapter<ProvinceAdapter.CityVH> {private List<CityBean> cityList; // recyclerView数据private Context context;public ProvinceAdapter(Context context, List<CityBean> cityList) {this.context = context;this.cityList = cityList;}/*** @return 是否为省份*/public boolean isProvince(int pos) {if (pos == 0) {return true;} else {String provinceName = getProvinceName(pos);String preProvinceName = getProvinceName(pos - 1);// 当前item和上一个item对比,如果相同,则不是省份;如果不相同,则为新的省份if (preProvinceName.equals(provinceName)) {return false;} else {return true;}}}public String getProvinceName(int pos) {return cityList.get(pos).getProvinceName();}@NonNull@Overridepublic CityVH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = LayoutInflater.from(context).inflate(R.layout.rv_item_city, null);return new CityVH(view);}@Overridepublic void onBindViewHolder(@NonNull CityVH holder, int position) {holder.textView.setText(cityList.get(position).getCityName());}@Overridepublic int getItemCount() {return cityList == null ? 0 : cityList.size();}public class CityVH extends RecyclerView.ViewHolder {TextView textView;public CityVH(@NonNull View itemView) {super(itemView);textView = itemView.findViewById(R.id.tv);}}
}
只要按照我贴的ProvinceDecoration部分代码,就可以实现吸顶功能。至于其他部分,根据自己项目的业务,稍微修改即可。
4. 优化部分
adapter部分,我推荐BaseRecyclerViewAdapterHelper,github:https://github.com/CymChad/BaseRecyclerViewAdapterHelper
5. 源码上传
源码已上传:https://download.csdn.net/download/liosen/91415153
给伸手党
6. 写在最后
至此,我们就简单完成了吸顶效果。UI部分按照项目修改即可