6

I want to use RecyclerView with GridLayoutManager to achieve something like that:

Target

Here is GridLayoutManager with horizontal orientation and 2 rows. What's important for me here is that RecyclerView is set as wrap_content.

So I tried to achieve my goal with such code:

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private val adapter = Adapter()
    private val layoutManager = GridLayoutManager(this, 2, RecyclerView.HORIZONTAL, false)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView.adapter = adapter
        recyclerView.layoutManager = layoutManager

        adapter.submitList(listOf(Unit, Unit, Unit, Unit))
    }
}

Adapter.kt

class Adapter : ListAdapter<Any, ItemHolder>(DiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
        val itemView = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
        return ItemHolder(itemView)
    }

    override fun onBindViewHolder(holder: ItemHolder, position: Int) {
    }

    override fun getItemViewType(position: Int): Int {
        return when {
            position % 2 == 0 -> R.layout.item_small
            else -> R.layout.item_normal
        }
    }

    class DiffCallback : DiffUtil.ItemCallback<Any>() {
        override fun areItemsTheSame(oldItem: Any, newItem: Any) = false
        override fun areContentsTheSame(oldItem: Any, newItem: Any) = false
    }

    class ItemHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

item_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="100dp"
    android:layout_height="100dp"
    android:layout_margin="8dp"
    android:background="@color/colorAccent" />

item_small.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="100dp"
    android:layout_height="50dp"
    android:layout_margin="8dp"
    android:background="@color/colorAccent" />

Unfortunately as the result, each row stretches to the largest row in grid:

current

The question is how can I remove the spacing between first row and second row? Keep in mind that I have to have scrollable horizontal grid that its height depends on its content. And items have to have fixed size.

Nominalista
  • 4,632
  • 11
  • 43
  • 102
  • See if [FlexboxLayoutManager](https://github.com/google/flexbox-layout#flexboxlayoutmanager-within-recyclerview) will work for you. – Cheticamp Jul 14 '19 at 22:57
  • I tried to play with flexbox demo, but I couldn't achieve horizontally scrollable view. Can you give any clue? – Nominalista Jul 15 '19 at 11:26
  • Here is a quick demo: Download the [FlexboxLayout project](https://github.com/google/flexbox-layout) and run the _demo-cat-gallery_ app. You will see a vertical scrolling gallery. Now, in _MainActivity.kt_ change `flexDirection = FlexDirection.ROW` to `flexDirection = FlexDirection.COLUMN` and run the app. You will now see a horizontal scroll of the same gallery. This is the idea. – Cheticamp Jul 15 '19 at 12:08
  • I've checked that now, correct me if I'm wrong, but this would work I could specify height of the `RecyclerView`. With `FlexboxLayoutManager` I am unable to specify column count. – Nominalista Jul 15 '19 at 12:16
  • If you have design constraints that _FlexboxLayout_ can't accommodate then this isn't what will work for you. – Cheticamp Jul 15 '19 at 12:24
  • try this: https://stackoverflow.com/a/34225902/4079010 – Rahul Khurana Jul 18 '19 at 10:54
  • I tried. It gives the same result like normal GridLayoutManager. Use sample I provided. – Nominalista Jul 18 '19 at 10:55
  • 1
    @Nominalista did u get it to work ? – Santanu Sur Jan 07 '20 at 06:40

3 Answers3

3

Assuming you know the ratios between heights of individual rows, you can try to use SpanSizeLookup to adjust height of each row. In your simple case, the smaller item (50dp) will take one row (span size 1) while the larger item (100dp) will take two rows (span size 2) so the whole grid overall will contain 3 rows.

Of course, for a more complex row configuration, the ratios might get a little more complicated: Say I wanted rows of heights 32dp/48dp/64dp, then the height ratios are 32/144, 48/144 and 64/144, which we can simplify to 2/9, 3/9, 4/9, getting 9 rows in total, with span sizes 2, 3, and 4 for individual items. In extreme cases, this can result in large number of rows (when the fractions cannot be simplified), but assuming you are using some type of grid (x8, x10, etc.) and the items are reasonably sized, it should still be manageable.

Anyway, in your case, the code would be this:

val layoutManager = GridLayoutManager(this, 3, RecyclerView.HORIZONTAL, false)
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
    override fun getSpanSize(position: Int) = when (position % 2) {
        0 -> 1 // 50dp item
        else -> 2 // 100dp item
    }
}

Given more rows, the when statement is going to get more complex, but if you already have the adapter at hand, you can use getItemViewType to differentiate individual rows in the when statement more easily.

If the number of item types is large or changes often (for example different item types on different screens), you can of course also implement the logic above in code, assuming you have access to the heights of the individual item types. Simply sum the heights to obtain the denominator and then find greatest common divisor of all heights and the sum to find the "simplification factor".

daemontus
  • 1,047
  • 10
  • 17
-1

Try to use a StaggeredGridLayoutManager to have different heights. Replace your GridLayoutManager with the below StaggeredGridLayoutManager

StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);

enter image description here

sanoJ
  • 2,935
  • 2
  • 8
  • 18
  • Actually it's not working, because it adjust item width, not height, in horizontal `RecyclerView`. – Nominalista Jul 10 '19 at 14:39
  • @Nominalista Sorry i have made a mistake, need to set the orientation to `VERTICAL`. I updated my answer, have a look – sanoJ Jul 10 '19 at 15:08
  • Please notice that I mentioned in the description that it must be a horizontal recycler view. There are to many items in a real problem to fit in two columns. – Nominalista Jul 10 '19 at 15:10
  • Does the height of a row also change? – sanoJ Jul 10 '19 at 15:23
  • All items in a row have the same height. But they differ between other rows. I think in this case example above shows exactly how they look like. – Nominalista Jul 10 '19 at 15:25
-1

I think GridLayoutManager by default constraints the child views to be exactly same sizes.

I adjust the code to use a LinearLayoutManager, and the view for ViewHolder is to wrap two types views into a RelativeLayout.

See some code below:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <View
        android:id="@+id/top"
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:layout_margin="8dp"
        android:layout_alignParentTop="true"
        android:background="@color/colorAccent" />


    <View
        android:id="@+id/bottom"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_margin="8dp"
        android:layout_below="@+id/top"
        android:background="@color/colorAccent" />
</RelativeLayout>

RecyclerAdapter:

        View view = LayoutInflater.from(viewGroup.getContext()).inflate(
                R.layout.item_normal,
                viewGroup,
                false
        );
        ViewHolder viewHolder = new ViewHolder(view);
        return viewHolder;

And set LinearLayoutManager:

        LinearLayoutManager layoutManager = new LinearLayoutManager(this, RecyclerView.HORIZONTAL, false);
        recyclerView.setLayoutManager(layoutManager);

screenshot

LiuWenbin_NO.
  • 1,216
  • 1
  • 16
  • 25
  • Thanks for the idea, but I made the sample simpler than the real problem. I have many rows in different configurations. So your solution doesn't apply here. – Nominalista Jul 10 '19 at 14:42