具有不同单元格宽度的 RecyclerView Grid 的 LayoutManager

Posted

技术标签:

【中文标题】具有不同单元格宽度的 RecyclerView Grid 的 LayoutManager【英文标题】:LayoutManager for RecyclerView Grid with different cell width 【发布时间】:2016-07-30 14:52:12 【问题描述】:

StaggeredGridLayoutManager 似乎不允许自定义 单元格宽度 或跨越多个列(全跨度除外)以实现垂直方向。

如上所示用于组织单元格的首选LayoutManager 是什么?

P.S.我只想知道如何使用StaggeredGridLayoutManager 自定义单元格宽度而不是高度。我知道高度可以按照sample 中的实现进行自定义。

public class VerticalStaggeredGridFragment extends RecyclerFragment 

    public static VerticalStaggeredGridFragment newInstance() 
        VerticalStaggeredGridFragment fragment = new VerticalStaggeredGridFragment();
        Bundle args = new Bundle();
        fragment.setArguments(args);
        return fragment;
    

    @Override
    protected RecyclerView.LayoutManager getLayoutManager() 
        return new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
    

    @Override
    protected RecyclerView.ItemDecoration getItemDecoration() 
        return new InsetDecoration(getActivity());
    

    @Override
    protected int getDefaultItemCount() 
        return 100;
    

    @Override
    protected SimpleAdapter getAdapter() 
        return new SimpleStaggeredAdapter();
    

适配器

public class SimpleStaggeredAdapter extends SimpleAdapter 
    @Override
    public void onBindViewHolder(VerticalItemHolder itemHolder, int position) 
        super.onBindViewHolder(itemHolder, position);

        final View itemView = itemHolder.itemView;
        if (position % 4 == 0) 
            int height = itemView.getContext().getResources()
                    .getDimensionPixelSize(R.dimen.card_staggered_height);
            itemView.setMinimumHeight(height);
         else 
            itemView.setMinimumHeight(0);
        
    

itemView.setMinimumWidth(customWidth) 不影响单元格宽度。

为了简化,我将网格布局更新为

StaggeredGridLayoutManager 或任何其他布局管理器中的单元格 1 相比,如何增加单元格 0 的宽度?

【问题讨论】:

【参考方案1】:

您可以使用StaggeredGridLayoutManager.LayoutParams 更改单元格的宽度和高度,您期望的设计遵循一个简单的模式,如果您为每个单元格分配一个索引(按照StaggeredGridLayoutManager 默认排列它们的顺序),您将拥有这个:

index % 4 == 3           --->  full span cell
index % 8 == 0, 5        --->  half span cell
index % 8 == 1, 2, 4, 6  ---> quarter span cell

首先在adapter中声明一些常量来定义span类型:

private static final int TYPE_FULL = 0;
private static final int TYPE_HALF = 1;
private static final int TYPE_QUARTER = 2;

然后像这样覆盖适配器中的getItemViewType 方法:

@Override
public int getItemViewType(int position) 
    final int modeEight = position % 8;
    switch (modeEight) 
        case 0:
        case 5:
            return TYPE_HALF;
        case 1:
        case 2:
        case 4:
        case 6:
            return TYPE_QUARTER;
    
    return TYPE_FULL;

您需要做的就是更改 layoutparams 考虑 viewTypeholder

@Override
public MyHolder onCreateViewHolder(final ViewGroup parent, final int viewType) 
    final View itemView =
            LayoutInflater.from(mContext).inflate(R.layout.items, parent, false);
    itemView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() 
        @Override
        public boolean onPreDraw() 
            final int type = viewType;
            final ViewGroup.LayoutParams lp = itemView.getLayoutParams();
            if (lp instanceof StaggeredGridLayoutManager.LayoutParams) 
                StaggeredGridLayoutManager.LayoutParams sglp =
                        (StaggeredGridLayoutManager.LayoutParams) lp;
                switch (type) 
                    case TYPE_FULL:
                        sglp.setFullSpan(true);
                        break;
                    case TYPE_HALF:
                        sglp.setFullSpan(false);
                        sglp.width = itemView.getWidth() / 2;
                        break;
                    case TYPE_QUARTER:
                        sglp.setFullSpan(false);
                        sglp.width = itemView.getWidth() / 2;
                        sglp.height = itemView.getHeight() / 2;
                        break;
                
                itemView.setLayoutParams(sglp);
                final StaggeredGridLayoutManager lm =
                        (StaggeredGridLayoutManager) ((RecyclerView) parent).getLayoutManager();
                lm.invalidateSpanAssignments();
            
            itemView.getViewTreeObserver().removeOnPreDrawListener(this);
            return true;
        
    );

    MyHolder holder = new MyHolder(itemView);
    return holder;

我的适配器总是返回 50 的项目计数(只是一个测试),我使用了一个简单的项目布局文件,它包含一个 LinearLayout 和一个 TextView 来显示持有人的位置,记住你应该传递 2 spanCount (int the StaggeredGridLayoutManager 构造函数) 因为你的设计。

@Override
protected void onCreate(Bundle savedInstanceState) 
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    RecyclerView rv = (RecyclerView) findViewById(R.id.rv);
    StaggeredGridLayoutManager lm =
            new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
    rv.setLayoutManager(lm);
    rv.setAdapter(new MyAdapter(this));


附言: 对于像我这样的懒人来说,这可能是一种更简单的方法,而不是扩展我的自定义 LayoutManager,但我确信有更好的方法来实现这一点。

更新: 更新部分你的问题比较简单,可以使用GridLayoutManager

    GridLayoutManager glm = new GridLayoutManager(this, 3);
    glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() 
        @Override
        public int getSpanSize(int position) 
            if (position % 3 == 2) 
                return 3;
            
            switch (position % 4) 
                case 1:
                case 3:
                    return 1;
                case 0:
                case 2:
                    return 2;
                default:
                    //never gonna happen
                    return  -1 ;
            
        
    );
    rv.setLayoutManager(glm);

这样你将默认跨度大小设置为 3,然后考虑你的跨度的位置,每个项目占用 1、2 或 3 个跨度,如下所示:

Item0.width == 2 X Item1.width
Item2.width == 3 X Item1.width
Item0.width == 2/3 X Item1.width

【讨论】:

您是否可以将单元格宽度设置为 1/4 或 3/4 或在 dp 中指定跨度大小为 2 的网格的特定大小?我可以使用 staggeredgridlayoutmanager 设置自定义高度但不能设置宽度。您会注意到我图像中的单元格在第一行的宽度不相等。我也会在一段时间内尝试您的解决方案并回来。 如果你检查 onCreateViewHolder 方法,这正是我所做的。 您能否更新答案中我提供的网格图像的代码?直到明天我才能尝试你的解决方案。 非常感谢!!关于gridlayout的最后一部分解决了我的问题:) 帮助很大。大喊开放社区:D【参考方案2】:

对于其他人,在不简化布局的情况下寻找解决方案,您可以查看my answer here,或者您可以阅读更多内容。

非常感谢Nick Butcher!他的布局管理器SpannableGridLayoutManager 能够做到这一点。

    下载 SpannableGridLayoutManagerfrom here

    由于某种原因,我不得不更改这一行:

    while (availableSpace > 0 && lastVisiblePosition < lastItemPosition) 
    

    while (lastVisiblePosition < lastItemPosition) 
    

    让经理工作。

    将 SpannableGridLayoutManger 设置为您的 RecyclerView

在你的情况下:

SpannedGridLayoutManager manager = new SpannedGridLayoutManager(
            new SpannedGridLayoutManager.GridSpanLookup() 
                @Override
                public SpannedGridLayoutManager.SpanInfo getSpanInfo(int position) 
                    switch (position % 8) 
                        case 0:
                        case 5:
                            return new SpannedGridLayoutManager.SpanInfo(2, 2);

                        case 3:
                        case 7:
                            return new SpannedGridLayoutManager.SpanInfo(3, 2);

                        default:
                            return new SpannedGridLayoutManager.SpanInfo(1, 1);
                    
                
            ,
            3, // number of columns
            1f // default size of item
    );

这将为您提供问题的第一张图片中的确切内容。

【讨论】:

我下载了您提到的文件,将其包含在我的项目中,进行了您提到的更改,但是当滚动浏览项目时,应用程序崩溃:java.lang.NullPointerException: Attempt to invoke virtual method 'int android.view.View.getTop()' on a null object reference @blueware Where 你能告诉我在哪里吗?哪个文件在哪一行? 文件:SpannableGridLayoutManger,没有确切的行。如果你愿意,我可以为那个异常附加我的堆栈跟踪。 @blueware 你可以把你的stacktrace放到Pastebin并分享url @kristyna ... recyclerView 从顶部占用大量空间,请帮帮我【参考方案3】:

终于做到了,它将帮助所有试图实现任何模式的人

我一直在寻找相同的结果,但只有一个 header 和延迟加载 footer。 我也尝试了 Nick Butcher Library 的@Kristyna 答案。但它有很多问题,例如extra space or fixed cell height 问题快速滚动视图消失问题

我找到了另一个 Arathel 库SpannedGridLayoutManager 并尝试实现它。但它也有许多问题,例如 fixed heightfooters extra space 问题

最后,在挖掘 Arasthel 的 SpannedGridLayoutManager 5 小时后,我修复了问题并找到了我的结果。

我最终编辑的分叉库下载从这里SpannedGridLayoutManager。

就我而言

  val adapter = GridItemAdapter()//This is your adapter
  val spannedGridLayoutManager = SpannedGridLayoutManager(orientation = VERTICAL, spans = 3)
  spannedGridLayoutManager.itemOrderIsStable = true
  recyclerview.layoutManager = spannedGridLayoutManager

对于第一张图片结果

  spannedGridLayoutManager.spanSizeLookup = SpannedGridLayoutManager.SpanSizeLookup  position ->
        when 
            position == 0 -> 
                /**
                 * 150f is now static 
                 * should calculate programmatically in runtime
                 * for to manage header hight for different resolution devices
                 */
                SpanSize(3, 1, 150f)
            
            position % 7 == 1 ->
                SpanSize(2, 2)
            else ->
                SpanSize(1, 1)
        
    
  recyclerview.adapter = adapter

以及第二张图片结果

 spannedGridLayoutManager.spanSizeLookup = SpannedGridLayoutManager.SpanSizeLookup  position ->
        when (position % 8) 
            0, 5 ->
                SpanSize(2, 2)
            3, 7 ->
                SpanSize(3, 2)
            else ->
                SpanSize(1, 1)
        
    
    recyclerview.adapter = adapter

这个 spanSizeLookup 逻辑可能会根据要求而有所不同,对我来说,我出于测试目的随机设置了它。

【讨论】:

只是想和你谈谈。这个库很棒,正是我所需要的。我花了几天时间才找到这样的东西。 谢谢@Tgo1014。如果你想和我谈谈,请给我发电子邮件。 ops,我的意思是谢谢你而不是说你。也感谢您让自己可以通过电子邮件交谈:)

以上是关于具有不同单元格宽度的 RecyclerView Grid 的 LayoutManager的主要内容,如果未能解决你的问题,请参考以下文章

集合视图中单元格的宽度不正确

如何限制标题单元格的宽度

具有不同高度的垂直对齐的集合视图单元格

uicollectionview 动态高度但固定宽度

具有可变宽度单元格的 UITableView

当单元格具有动态宽度时,CollectionView 中的间距不正确?