专栏名称: Anlia
Android工程师
目录
相关文章推荐
开发者全社区  ·  董事长十几刀刺死 ... ·  16 小时前  
开发者全社区  ·  逆天了,OnlyFans杭州岗 ·  16 小时前  
开发者全社区  ·  55 ... ·  昨天  
开发者全社区  ·  H家最新进展 ·  2 天前  
开发者全社区  ·  没有大于40岁的P7了 ·  2 天前  
51好读  ›  专栏  ›  Anlia

Android 从零开始实现RecyclerView分组及粘性头部效果

Anlia  · 掘金  · android  · 2018-01-01 15:35

正文

版权声明:本文为博主原创文章,未经博主允许不得转载

系列教程: Android开发之从零开始系列

源码: AnliaLee/android-RecyclerViews ,欢迎star

大家要是看到有错误的地方或者有啥好的建议,欢迎留言评论

前言

最近项目中要实现 列表分组 粘性头部 的效果,网上翻了很多资料和开源库,感觉都不是太好用,有的扩展性不强有的用起来又太复杂,于是决定自己动手造轮子。行动之前,研究了许多前人的源码,决定了几点开发方向

  • 尽可能方便用户使用, 减少调用的代码量及与其他类的耦合度
  • 使用 RecyclerView 实现列表功能
  • 自定义 RecyclerView.ItemDecoration 绘制 分组Item 粘性头部
  • 通过 layoutInflater.inflate 获取 layout 中的布局并传入 ItemDecoration 进行绘制(方便用户布局分组Item)
  • ItemDecoration 中提供 接口 让用户对列表数据 进行分组 设置分组Item的显示内容

目前 GroupItemDecoration 第一阶段已开发完成(会继续更新和扩展功能),源码及示例已上传至 Github ,具体效果如图


GroupItemDecoration使用简介

GroupItemDecoration 目前只支持 LinearLayoutManager.VERTICAL 类型,使用流程如下

LayoutInflater layoutInflater = LayoutInflater.from(this);
View groupView = layoutInflater.inflate(R.layout.item_group,null);
  • 调用 recyclerView.addItemDecoration 添加 GroupItemDecoration
recyclerView.addItemDecoration(new GroupItemDecoration(this,groupView,new GroupItemDecoration.DecorationCallback() {
	@Override
	public void setGroup(List<GroupItem> groupList) {
		//设置分组,GroupItem(int startPosition),例如:
		GroupItem groupItem = new GroupItem(0);
		groupItem.setData("name","第1组");
		groupList.add(groupItem);

		groupItem = new GroupItem(5);
		groupItem.setData("name","第2组");
		groupList.add(groupItem);
	}

	@Override
	public void buildGroupView(View groupView, GroupItem groupItem) {
		//构建groupView,通过groupView.findViewById找到内部控件(暂不支持点击事件等),例如
		TextView textName = (TextView) groupView.findViewById(R.id.text_name);
		textName.setText(groupItem.getData("name").toString());
	}
}));

如果还是不清楚可以去看下demo


实现思路

在我们 自定义ItemDecoration 之前首先得了解 ItemDecoration 有什么用,不清楚的可以看下这两篇博客

RecyclerView之ItemDecoration由浅入深

深入理解 RecyclerView 系列之一:ItemDecoration

简单来说,我们实现分组及粘性头部效果分三步

  1. 重写 ItemDecoration.getItemOffsets RecyclerView 中为 GroupView 预留位置
  2. 重写 ItemDecoration.onDraw 在上一步预留的位置中绘制 GroupView
  3. 重写 ItemDecoration.onDrawOver 绘制 顶部悬停的GroupView(粘性头部

我们按顺序一步步讲,首先,创建 GroupItemDecoration 继承自 ItemDecoration ,在初始化方法中获取用户设置的 GroupView ,并提供接口给用户设置分组相关

public class GroupItemDecoration extends RecyclerView.ItemDecoration {
	private Context context;
    private View groupView;
    private DecorationCallback decorationCallback;

    public GroupItemDecoration(Context context,View groupView,DecorationCallback decorationCallback) {
        this.context = context;
        this.groupView = groupView;
        this.decorationCallback = decorationCallback;
    }

    public interface DecorationCallback {
        /**
         * 设置分组
         * @param groupList
         */
        void setGroup(List<GroupItem> groupList);

        /**
         * 构建GroupView
         * @param groupView
         * @param groupItem
         */
        void buildGroupView(View groupView, GroupItem groupItem);
    }
}

然后重写 getItemOffsets 方法,根据用户设置的分组为 GroupView 预留位置,其中最主要的是测量出 GroupView 宽高和位置 measureView 方法中按着 View的绘制顺序 调用 View.measure View.layout ,只有先完成了这两步,才能将 View 绘制到屏幕上,关于如何测量 View 大家可以看下这篇博客 Android如何在初始化的时候获取加载的布局的宽高 。接下来是具体的实现代码

public class GroupItemDecoration extends RecyclerView.ItemDecoration {
    //省略部分代码...
    private List<GroupItem> groupList = new ArrayList<>();//用户设置的分组列表
    private Map<Object,GroupItem> groups = new HashMap<>();//保存startPosition与分组对象的对应关系
    private int[] groupPositions;//保存分组startPosition的数组
    private int positionIndex;//分组对应的startPosition在groupPositions中的索引
	
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if(!isLinearAndVertical(parent)){//若RecyclerView类型不是LinearLayoutManager.VERTICAL,跳出(下同)
            return;
        }

        if(isFirst){
            measureView(groupView,parent);//绘制View需要先测量View的大小及相应的位置
            decorationCallback.setGroup(groupList);//获取用户设置的分组列表
            if(groupList.size()==0){//若用户没有设置分组,跳出(下同)
                return;
            }
            groupPositions = new int[groupList.size()];
            positionIndex = 0;

            int a = 0;
            for(int i=0;i<groupList.size();i++){//保存groupItem与其startPosition的对应关系
                int p = groupList.get(i).getStartPosition();
                if(groups.get(p)==null){
                    groups.put(p,groupList.get(i));
                    groupPositions[a] = p;
                    a++;
                }
            }
            isFirst = false;
        }

        int position = parent.getChildAdapterPosition(view);
        if(groups.get(position)!=null){
			//若RecyclerView中该position对应的childView之前需要绘制groupView,则为其预留相应的高度空间
            outRect.top = groupViewHeight;
        }
    }

    /**
     * 测量View的大小和位置
     * @param view
     * @param parent
     */
    private void






请到「今天看啥」查看全文