LayoutManager for RecyclerView Grid with different cell width

后端 未结 3 1611
半阙折子戏
半阙折子戏 2020-12-04 07:34

StaggeredGridLayoutManager doesn\'t seem to allow customising a cell width or span multiple columns (except full span) for vertical orientation

相关标签:
3条回答
  • 2020-12-04 08:26

    Finally did it, it will help everyone who is trying to achieve any kind of patterns

    I was looking for the same result but with one header and lazy loading footer. I tried @Kristyna answers of Nick Butcher Library too. But it has many issues like fast scrolling view disappear problem with the extra space or fixed cell height issue.

    I found another library of Arasthel SpannedGridLayoutManager and tried to implement it. But it also has many issues like fixed height and footers extra space issue

    Finally, after 5hr of digging Arasthel's SpannedGridLayoutManager, I fixed the issues and found my result.

    My final edited forked library download from here SpannedGridLayoutManager.

    In my case

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

    For the first image result

      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
    

    And the second image result

     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
    

    This spanSizeLookup logic may vary according to the requirement, for me I had set it randomly for testing purposes.

    0 讨论(0)
  • 2020-12-04 08:29

    You can use StaggeredGridLayoutManager.LayoutParams to change width and height of cells, What you expect to design follows a simple pattern, If you assign every cell an index (in the order that StaggeredGridLayoutManager arranges them by default) you will have this:

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

    first declare some constants in adapter to define span types:

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

    Then override getItemViewType method in your adapter like this:

    @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;
    }
    

    All you need to do is change layoutparams considering viewType of the holder:

    @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;
    }
    

    My adapter always returns 50 for items count(just a test) and I used a simple layout file for items, It contains a LinearLayout and a TextView to show the position of holder, Remember you should pass 2 for spanCount (int the StaggeredGridLayoutManager constructor) because of your design.

    @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));
    
    }
    

    PS: For lazy people like me maybe it's a simpler way rather than extending my custom LayoutManager, But I'm sure there are better ways to achieve this.

    Update: Updated part of your question is more simpler, You can use 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);
    

    In this way you set 3 for default span size, Then considering the position of your span, Each item occupies 1, 2 or 3 spans, Something like this:

    Item0.width == 2 X Item1.width
    Item2.width == 3 X Item1.width
    Item0.width == 2/3 X Item1.width
    
    0 讨论(0)
  • 2020-12-04 08:29

    For everyone else, looking for a solution without simplifying your layout, you can have a look to my answer here, or you can read more.

    Big thanks to Nick Butcher! His layout manager called SpannableGridLayoutManager is capable of doing this.

    1. Download SpannableGridLayoutManager from here

      For some reason I had to change this line:

      while (availableSpace > 0 && lastVisiblePosition < lastItemPosition) {
      

      to

      while (lastVisiblePosition < lastItemPosition) {
      

      to got the manager working.

    2. Set SpannableGridLayoutManger to your RecyclerView

    In your case:

    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
        );
    

    Which will give you exactly what is in the first picture of the question.

    0 讨论(0)
提交回复
热议问题