RecyclerView跨行跨列的LayoutManager:Spannedgridlayoutmanager

前言: 
RecyclerView可以使用GridLayoutManager实现跨行,但是不能跨列;瀑布流布局可以跨列但是又不能跨行。原生自带的各个LayoutManager中并没有可以又跨行又能跨列的。网上搜寻了一番,找到了一个亲测可行好用的三方库:spannedgridlayoutmanager。

实现效果预览:

依赖库的地址:

GitHubGitHub - jmartinesp/SpannedGridLayoutManager: Android RecyclerView.LayoutManager that resizes and reorders views based on SpanSize

gitee:SpannedGridLayoutManager: Android RecyclerView.LayoutManager that resizes and reorders views based on SpanSize (gitee.com)

使用:

1、添加依赖:
dependencies {
	implementation 'com.arasthel:spannedgridlayoutmanager:3.0.2'
}

如果依赖不成功或者想要自定义spannedgridlayoutmanager里面的代码,使用Module的方式来导入该项目。这边提供资源地址:spannedgridlayoutmanager-3.0.2资源-CSDN文库

2、代码中使用:
        recyclerView.setAdapter(adapter);
        //参数Orientation.VERTICAL表示列表竖向;4表示4列
        SpannedGridLayoutManager gridLayoutManager = new SpannedGridLayoutManager(
                SpannedGridLayoutManager.Orientation.VERTICAL, 4);
        gridLayoutManager.setSpanSizeLookup(new SpannedGridLayoutManager.SpanSizeLookup(position -> {
            int col = 1;
            int row = 1;
            switch (adapter.getItemViewType(position)) {
                case ITEM_TYPE_1x2:
                    col = 2;
                    break;
                case ITEM_TYPE_1x4:
                    col = 4;
                    break;
                case ITEM_TYPE_2x1:
                    row = 2;
                    break;
                case ITEM_TYPE_2x2:
                    col = 2;
                    row = 2;
                    break;
                case ITEM_TYPE_1x1:
                default:
                    break;
            }
            return new SpanSize(col, row);
        }));
        recyclerView.setLayoutManager(gridLayoutManager);

根据你每一项item所需要占的格子数大小设置所跨宽和列的单元格个数。

就可以啦。

完整代码:

这边代码包含了item拖动功能,详细解释和实现方式可以参考我另一篇博客:Android:RecyclerView自由拖动item不需要拖拽功能的忽略即可。

适配器:
public class AppsCardAdapter extends RecyclerView.Adapter<AppsCardAdapter.MyHolder> {
    private final List<Integer> mList;
    private final Context mContext;
 
    private final int ITEM_TYPE_1x1 = 0;
    private final int ITEM_TYPE_1x2 = 1;
    private final int ITEM_TYPE_1x4 = 2;
    private final int ITEM_TYPE_2x2 = 3;
    private final int ITEM_TYPE_2x1 = 4;
 
    AppsCardAdapter(Context context) {
        this.mContext = context;
        mList = new ArrayList<>();
    }
 
    @NonNull
    @Override
    public MyHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int itemType) {
        View rootView = LayoutInflater.from(mContext).inflate(
                R.layout.item_apps_card, viewGroup, false);
        return new MyHolder(rootView);
    }
 
    @SuppressLint("SetTextI18n")
    @Override
    public void onBindViewHolder(@NonNull MyHolder holder, int position) {
        int item = mList.get(position);
        holder.name.setText(item + "");
        holder.itemView.setOnClickListener(view -> {
            Toast.makeText(mContext, "item = "+item, Toast.LENGTH_SHORT).show();
        });
    }
 
    @Override
    public int getItemCount() {
        return mList.size();
    }
 
    @Override
    public int getItemViewType(int position) {
         TODO: 根据实际项目item的类型来,这边为了演示直接定死位置的类型了 
        switch (mList.get(position)){
            case 0:
                return ITEM_TYPE_1x4;
            case 1:
            case 4:
            case 6:
                return ITEM_TYPE_1x2;
            case 2:
            case 3:
                return ITEM_TYPE_2x1;
            case 5:
                return ITEM_TYPE_2x2;
            default:
                return ITEM_TYPE_1x1;
        }
    }
 
    static class MyHolder extends RecyclerView.ViewHolder {
        TextView name;
 
        public MyHolder(@NonNull  View itemView) {
            super(itemView);
            name = itemView.findViewById(R.id.name);
        }
    }
 
    //拖动功能的回调类
    private static class MyItemTouchHelperCallback extends ItemTouchHelper.Callback{
 
        private final AppsCardAdapter appsCardAdapter;
        public MyItemTouchHelperCallback(AppsCardAdapter appsCardAdapter) {
            this.appsCardAdapter = appsCardAdapter;
        }
 
        @Override
        public int getMovementFlags(@NonNull  RecyclerView recyclerView,
                                    @NonNull  RecyclerView.ViewHolder viewHolder) {
            if (viewHolder.getItemViewType() == appsCardAdapter.ITEM_TYPE_1x4) {
                //不可拖动
                return makeMovementFlags(0, 0);
            }
            final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN
                    | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
            return makeFlag(ItemTouchHelper.ACTION_STATE_DRAG, dragFlags);
        }
 
        @Override
        public boolean onMove(@NonNull  RecyclerView recyclerView,
                              @NonNull  RecyclerView.ViewHolder viewHolderSource,
                              @NonNull  RecyclerView.ViewHolder viewHolderTarget) {
            if (viewHolderTarget.getItemViewType() == appsCardAdapter.ITEM_TYPE_1x4) {
                //不可拖动到这里
                return false;
            }
            appsCardAdapter.onMove(viewHolderSource.getAdapterPosition(),
                    viewHolderTarget.getAdapterPosition());
            return true;
        }
 
        @Override
        public void onSwiped(@NonNull  RecyclerView.ViewHolder viewHolder, int i) {
 
        }
    }
 
    private void onMove(int sourcePosition, int targetPosition) {
        int item = mList.get(sourcePosition);
        mList.remove(sourcePosition);
        mList.add(targetPosition, item);
        notifyItemMoved(sourcePosition, targetPosition);
    }
 
    public void setRecyclerView(RecyclerView recyclerView) {
        recyclerView.setAdapter(this);
        SpannedGridLayoutManager gridLayoutManager = new SpannedGridLayoutManager(
                SpannedGridLayoutManager.Orientation.VERTICAL, 4);
        gridLayoutManager.setSpanSizeLookup(new SpannedGridLayoutManager.SpanSizeLookup(position -> {
            int col = 1;
            int row = 1;
            switch (getItemViewType(position)) {
                case ITEM_TYPE_1x2:
                    col = 2;
                    break;
                case ITEM_TYPE_1x4:
                    col = 4;
                    break;
                case ITEM_TYPE_2x1:
                    row = 2;
                    break;
                case ITEM_TYPE_2x2:
                    col = 2;
                    row = 2;
                    break;
                case ITEM_TYPE_1x1:
                default:
                    break;
            }
            return new SpanSize(col, row);
        }));
        recyclerView.setLayoutManager(gridLayoutManager);
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(
                new MyItemTouchHelperCallback(this));
        itemTouchHelper.attachToRecyclerView(recyclerView);
    }
 
    public void setNewData(List<Integer> list) {
        this.mList.clear();
        this.mList.addAll(list);
        notifyDataSetChanged();
    }
}
Activity中使用:
        AppsCardAdapter adapter = new AppsCardAdapter(getContext());
        adapter.setRecyclerView(recyclerView);
        //测试数据
        ArrayList<Integer> list = new ArrayList<>();
        for (int i = 0; i < 13; i++) {
            list.add(i);
        }
        adapter.setNewData(list);

————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_35584878/article/details/135968049

<think>好的,我现在需要帮助用户了解如何在RecyclerView中实现跨行的布局,特别是通过自定义GridLayoutManager的SpanSizeLookup。首先,我得回忆一下RecyclerView的相关知识,尤其是GridLayoutManager和SpanSizeLookup的作用。 用户提到的跨行布局,常见于需要某些Item占据多个网格的情况。比如,一个Item可能横或两行。我记得GridLayoutManager默认是固定每个Item占据的Span数量,但通过自定义SpanSizeLookup可以动态调整。这可能涉及到重写getSpanSize方法,根据不同的位置返回不同的Span值。 接下来,我需要确定具体步骤。首先,创建GridLayoutManager并设置SpanCount,比如设为3。然后,自定义SpanSizeLookup,在getSpanSize中根据条件返回不同的Span大小。例如,某个位置需要,则返回2,其他返回1。这样该Item就会占据两的空间,其他Item正常显示。 还需要考虑布局的排方式。比如,如果一行总共有3,一个Item占2,那么剩下的空间应该由下一个Item占据1。这时候SpanSizeLookup需要正确计算每个Item的Span,避免布局错乱。 然后,结合用户提供的引用,特别是引用[4]提到的自定义GridLayoutManager,可能需要进一步说明如何实现。不过根据标准做法,通常不需要自定义整个LayoutManager,而是通过SpanSizeLookup调整即可。但有些复杂情况可能需要更深入的修改,比如处理跨行的情况,这时候可能需要自定义LayoutManager,如引用[1]中的SpannedGridLayoutManager。不过用户的问题可能更倾向于使用内置的GridLayoutManager结合SpanSizeLookup来实现,所以需要明确这两种方法的区别和适用场景。 另外,用户提供的引用[2]和[3]提到RecyclerView的分割线和基本使用,但当前问题主要集中在跨行布局,所以可能不需要深入讨论分割线的问题,但可以提一下分割线的自定义可能会影响布局的显示,需要注意调整。 最后,需要给出示例代码,展示如何设置SpanSizeLookup,以及Adapter中如何根据位置返回不同的Span大小。同时,需要指出可能遇到的问题,比如Span总数与数的匹配,避免出现无法填满一行的情况。 总结步骤: 1. 创建GridLayoutManager并设置数。 2. 自定义SpanSizeLookup,重写getSpanSize方法。 3. 根据Item类型或位置返回不同的Span值。 4. 将自定义的SpanSizeLookup设置给GridLayoutManager。 5. 在Adapter中处理不同Item的布局。 可能还需要提到第三方库如SpannedGridLayoutManager,但主要解答使用标准组件的方法。</think>在RecyclerView中实现跨行的布局,核心是通过自定义$GridLayoutManager$的$SpanSizeLookup$类来动态调整每个Item占据的网格数量。以下是具体实现步骤: --- ### **1. 基础配置GridLayoutManager** 首先创建$GridLayoutManager$并设置数(假设为3): ```java GridLayoutManager layoutManager = new GridLayoutManager(context, 3); recyclerView.setLayoutManager(layoutManager); ``` --- ### **2. 自定义SpanSizeLookup** 通过重写$SpanSizeLookup$的$getSpanSize(int position)$方法,根据位置返回不同的Span值: ```java layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { // 假设位置0的Item需要3,其他占1 if (position == 0) { return 3; } else { return 1; } } }); ``` --- ### **3. 复杂场景示例** 若需要动态跨行(例如主柜与子柜的混合布局),需结合**Item类型**判断: ```java @Override public int getSpanSize(int position) { int itemType = adapter.getItemViewType(position); switch (itemType) { case TYPE_HEADER: // 3 return 3; case TYPE_SUB_ITEM: // 2 return 2; default: // 默认1 return 1; } } ``` --- ### **4. 布局效果说明** - ****:若数为3,一个Item的SpanSize为2,则它会占据当前行剩余2中的空间,下一个Item自动换行[^4]。 - **跨行**:需结合行高调整,可能需要自定义LayoutManager(如开源库$SpannedGridLayoutManager$[^1])。 --- ### **5. 第三方库推荐** 对于更复杂的跨行需求(如瀑布流中的动态度),可直接使用现成解决方案: ```gradle implementation 'com.github.jmartinesp:SpannedGridLayoutManager:1.0.0' ``` 该库支持通过$SpanSize$定义每个Item的行度。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值