在 RecyclerView 中按组划分元素

Posted

技术标签:

【中文标题】在 RecyclerView 中按组划分元素【英文标题】:Divide elements on groups in RecyclerView 【发布时间】:2016-04-23 06:00:53 【问题描述】:

我需要在具有标题的组中划分 RecyclerView 中的元素(如下图的收件箱应用程序中),因此请帮助我找出哪种方法更适合我的情况: 1)我可以使用异构布局,但在组中插入新元素不太方便(因为我需要检查是否已经添加了同一组的元素或者我需要添加新的分隔符)。所以在这种情况下,我会将所有具有这种数据结构的操作包装到一个单独的类中。

2) 理论上我可以用标签将每个组包装在自己的 RecyclerView 中,这是个好主意吗?

【问题讨论】:

这不是在 ui 级别使用多个 recyclerview 对元素进行分组的问题,因为滚动会有问题。您可以只使用一种具有两种元素类型的回收视图,一种用于标题,另一种用于项目。 @thetonrifles 你说得对 UI 应该尽可能简单,我好像在提问的时候忘记了,谢谢! 【参考方案1】:

例如,您可以:

    使用TreeMap<Date,List<Event>> 按日期拆分元素。这将是一个用于保存您的业务对象的集合。当然,如果您已经有类似的结构,您可以保留它。有一些东西可以轻松构建项目列表以使用正确的元素顺序填充 UI,这一点很重要。

    List 项目(例如ListItem)定义一个专用抽象类型来包装您的业务对象。它的实现可能是这样的:

    public abstract class ListItem 
    
        public static final int TYPE_HEADER = 0;
        public static final int TYPE_EVENT = 1;
    
        abstract public int getType();
     
    

    为您的每个 List 元素类型定义一个类(这里我只添加了两种类型,但您可以根据需要使用多种类型):

    public class HeaderItem extends ListItem 
    
        private Date date;
    
        // here getters and setters 
        // for title and so on, built
        // using date
    
        @Override
        public int getType() 
            return TYPE_HEADER;
        
    
    
    
    public class EventItem extends ListItem 
    
        private Event event;
    
        // here getters and setters 
        // for title and so on, built 
        // using event
    
        @Override
        public int getType() 
            return TYPE_EVENT;
        
    
    
    

    如下创建一个列表(其中 mEventsMap 是在第 1 点构建的地图):

    List<ListItem> mItems;
    // ...
    mItems = new ArrayList<>();
    for (Date date : mEventsMap.keySet()) 
        HeaderItem header = new HeaderItem();
        header.setDate(date); 
        mItems.add(header);
        for (Event event : mEventsMap.get(date)) 
            EventItem item = new EventItem();
            item.setEvent(event);
            mItems.add(item);
        
    
    

    为您的RecyclerView 定义一个适配器,处理在第 4 点定义的List。这里重要的是重写getItemViewType 方法,如下所示:

    @Override
    public int getItemViewType(int position) 
        return mItems.get(position).getType();
    
    

    然后你需要有两个布局和 ViewHolder 用于标题和事件项。适配器方法应该相应地处理这个问题:

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) 
        if (viewType == ListItem.TYPE_HEADER) 
            View itemView = mLayoutInflater.inflate(R.layout.view_list_item_header, parent, false);
            return new HeaderViewHolder(itemView);
         else 
            View itemView = mLayoutInflater.inflate(R.layout.view_list_item_event, parent, false);
            return new EventViewHolder(itemView);
        
    
    
    
    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder viewHolder, final int position) 
        int type = getItemViewType(position);
        if (type == ListItem.TYPE_HEADER) 
            HeaderItem header = (HeaderItem) mItems.get(position);
            HeaderViewHolder holder = (HeaderViewHolder) viewHolder;
            // your logic here
         else             
            EventItem event = (EventItem) mItems.get(position);
            EventViewHolder holder = (EventViewHolder) viewHolder;
            // your logic here
        
    
    

Here 它是 GitHub 上的存储库,提供上述方法的实现。

【讨论】:

目前我有几乎相同的解决方案(但没有明确的映射到列表转换),它可以工作,但我认为(可能我错了)支持该解决方案并不容易,并且也许一些最佳实践已经存在 @Leo 使用这种方法,主要问题可能是在许多视图类型的情况下保持代码清洁。在这种情况下,可能会将每个 ViewHolder 和相应的绑定逻辑封装到一个专用类中,从而使适配器实现更轻。 我的问题应该是,你如何将物品包装成卡片? OP 询问了具有标题的卡片容器分组 UI。您如何使用一个回收器视图将事件项分组到“卡片”(或 UI 上的任何内容,有阴影)?我想答案是将每一行包装成卡片视图:***.com/questions/31273203/… @Csabi 当然有不同的方法(在回答您链接的问题时描述了一些内容)。这里一种可能的方法是,我们应该有四种视图类型(标题、顶部项目、中间项目、底部项目),而不是只有两种视图类型(标题和项目)。这是因为每个项目的布局会根据位置而变化。然后,您可以根据您链接的答案中的报告调整卡片视图,或者通过使用已经包含阴影的一些 9-patch 背景继续进行。核心概念是你需要有多种视图类型来渲染不同类型的元素。 @kinsleykajiva 我今晚有一些时间。 Here 是一个带有示例项目的分支,它报告了相同的答案示例。希望这会有所帮助。【参考方案2】:

您可以尝试使用我编写的库来解决我的项目中的这个问题。 Gradle 依赖(需要包含 jcenter 存储库):

dependencies 
    //your other dependencies
    compile 'su.j2e:rv-joiner:1.0.3'//latest version by now

那么,在你的情况下,你可以这样做:

//init your RecyclerView as usual
RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
rv.setLayoutManager(new LinearLayoutManager(this));

//construct a joiner
RvJoiner rvJoiner = new RvJoiner();
rvJoiner.add(new JoinableLayout(R.layout.today));
YourAdapter todayAdapter = new YourAdapter();
rvJoiner.add(new JoinableAdapter(todayAdapter));
rvJoiner.add(new JoinableLayout(R.layout.yesterday));
YourAdapter yesterdayAdapter = new YourAdapter();
rvJoiner.add(new JoinableAdapter(yesterdayAdapter));

//set join adapter to your RecyclerView
rv.setAdapter(rvJoiner.getAdapter());

当您需要添加项目时,将其添加到适当的适配器,例如:

if (timeIsToday) 
    todayAdapter.addItem(item);//or other func you've written
 else if (timeIsYesterday) 
    yesterdayAdapter.addItem(item);

如果需要动态添加新组到recycler view,可以使用以下方法:

rvJoiner.add(new JoinableLayout(R.layout.tomorrow));
YourAdapter tomorrowAdapter = new YourAdapter();
rvJoiner.add(new JoinableAdapter(tomorrowAdapter));

您可以查看this link 了解更多库描述。我不能说这肯定是实现目标的最佳方式,但它有时对我有帮助。

UPD:

我找到了不使用外部库的方法。使用RecyclerView.ItemDecoration 类。例如,要按组中的 3 个项目对项目进行分组,您可以这样做:

recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() 

        private int textSize = 50;
        private int groupSpacing = 100;
        private int itemsInGroup = 3;

        private Paint paint = new Paint();
        
            paint.setTextSize(textSize);
        

        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) 
            for (int i = 0; i < parent.getChildCount(); i++) 
                View view = parent.getChildAt(i);
                int position = parent.getChildAdapterPosition(view);
                if (position % itemsInGroup == 0) 
                    c.drawText("Group " + (position / itemsInGroup + 1), view.getLeft(),
                            view.getTop() - groupSpacing / 2 + textSize / 3, paint);
                
            
        

        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) 
            if (parent.getChildAdapterPosition(view) % itemsInGroup == 0) 
                outRect.set(0, groupSpacing, 0, 0);
            
        
    );

希望对你有帮助。

【讨论】:

这不是一个大项目,我是 android 开发的新手,所以我想找出尽可能多的基础知识和最佳实践,所以我会尽量使用 google android 库尽可能在项目中。但可能稍后我会寻找这样的图书馆。谢谢! @Leo 我已经更新了一个答案,以展示仅使用谷歌库的方式。看看:) 更新后的答案很有创意!使用新 ItemDecoration 的方法,它也支持旧版本的 android,我使用此代码生成了一个看起来像 ios 菜单的菜单 是否可以将您编写的库用于许多不固定的组? 嘿!什么是 paint.setTextSize(..);.. ?叫什么名字?【参考方案3】:

这个问题来自 2016 年,与此同时(2020 年)有不同的库可用于对回收站视图进行分组。最受欢迎的:

Groupie Epoxy (airbnb)

【讨论】:

以上是关于在 RecyclerView 中按组划分元素的主要内容,如果未能解决你的问题,请参考以下文章

在 Pandas 中按组均值创建大均值中心变量

在熊猫中按组顺序计算差异

在数据框中按组折叠文本[重复]

在具有重复行的 SQL Server 表中按组查找行号

如何在mysql中按组查找累积值?

在 R 中按组创建组合