RecyclerView с горизонтальным GridLayoutManager регулирует свою высоту до самой большой строки

Я хочу использовать RecyclerView с GridLayoutManager для достижения чего-то подобного:

Цель

Вот GridLayoutManager с горизонтальной ориентацией и 2 строками. Для меня здесь важно, чтобы RecyclerView было установлено как wrap_content.

Итак, я попытался достичь своей цели с помощью такого кода:

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" />

К сожалению, в результате каждая строка растягивается до самой большой строки в сетке:

текущий

Вопрос в том, как я могу удалить интервал между первой строкой и второй строкой? Имейте в виду, что у меня должна быть прокручиваемая горизонтальная сетка, высота которой зависит от ее содержимого. И элементы должны иметь фиксированный размер.


person Nominalista    schedule 10.07.2019    source источник
comment
Посмотрите, подойдет ли вам FlexboxLayoutManager.   -  person Cheticamp    schedule 15.07.2019
comment
Я пытался поиграть с демонстрацией flexbox, но не смог добиться просмотра с горизонтальной прокруткой. Можете ли вы дать какую-нибудь подсказку?   -  person Nominalista    schedule 15.07.2019
comment
Вот небольшая демонстрация: Загрузите проект FlexboxLayout и запустите demo-cat- Галерея приложение. Вы увидите галерею с вертикальной прокруткой. Теперь в MainActivity.kt измените flexDirection = FlexDirection.ROW на flexDirection = FlexDirection.COLUMN и запустите приложение. Теперь вы увидите горизонтальную прокрутку той же галереи. Это идея.   -  person Cheticamp    schedule 15.07.2019
comment
Я проверил это сейчас, поправьте меня, если я ошибаюсь, но это сработает, я могу указать высоту RecyclerView. С FlexboxLayoutManager я не могу указать количество столбцов.   -  person Nominalista    schedule 15.07.2019
comment
Если у вас есть конструктивные ограничения, которые FlexboxLayout не может учесть, то это не то, что вам нужно.   -  person Cheticamp    schedule 15.07.2019
comment
попробуйте это: stackoverflow.com/a/34225902/4079010   -  person Rahul Khurana    schedule 18.07.2019
comment
Я старался. Это дает тот же результат, что и обычный GridLayoutManager. Используйте образец, который я предоставил.   -  person Nominalista    schedule 18.07.2019
comment
@Номиналиста у тебя получилось?   -  person Santanu Sur    schedule 07.01.2020


Ответы (3)


Предполагая, что вы знаете соотношения между высотами отдельных строк, вы можете попробовать использовать SpanSizeLookup для регулировки высоты каждой строки. В вашем простом случае меньший элемент (50dp) займет одну строку (размер диапазона 1), а больший элемент (100dp) займет две строки (размер диапазона 2), поэтому вся сетка в целом будет содержать 3 строки.

Конечно, для более сложной конфигурации строк соотношения могут быть немного сложнее: скажем, мне нужны строки высотой 32/48/64 dp, тогда соотношения высот составляют 32/144, 48/144 и 64/144, которые мы можно упростить до 2/9, 3/9, 4/9, получив всего 9 рядов с размерами пролета 2, 3 и 4 для отдельных элементов. В крайних случаях это может привести к большому количеству строк (когда дроби не могут быть упрощены), но если вы используете какой-либо тип сетки (x8, x10 и т. д.) и элементы имеют разумный размер, это все равно должно быть управляемым. .

В любом случае, в вашем случае код будет таким:

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

При большем количестве строк оператор when будет становиться более сложным, но если у вас уже есть адаптер, вы можете использовать getItemViewType, чтобы легче различать отдельные строки в операторе when.

Если количество типов элементов велико или часто меняется (например, разные типы элементов на разных экранах), вы, конечно, также можете реализовать описанную выше логику в коде, предполагая, что у вас есть доступ к высотам отдельных типов элементов. Просто суммируйте высоты, чтобы получить знаменатель, а затем найдите наибольший общий делитель всех высот и сумму, чтобы найти «коэффициент упрощения».

person daemontus    schedule 15.07.2019
comment
Кажется очень многообещающим, я проверю ваше решение как можно скорее. - person Nominalista; 15.07.2019

Я думаю, что GridLayoutManager по умолчанию ограничивает дочерние представления точно такими же размерами.

Я корректирую код для использования LinearLayoutManager, а представление для ViewHolder состоит в том, чтобы обернуть представления двух типов в RelativeLayout.

См. код ниже:

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

И установите LinearLayoutManager:

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

скриншот

person LiuWenbin_NO.    schedule 10.07.2019
comment
Спасибо за идею, но я сделал образец проще, чем реальная задача. У меня много строк в разных конфигурациях. Так что ваше решение здесь не применимо. - person Nominalista; 10.07.2019

Попробуйте использовать StaggeredGridLayoutManager, чтобы иметь разную высоту. Замените свой GridLayoutManager на указанный ниже StaggeredGridLayoutManager

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

введите здесь описание изображения

person sanoJ    schedule 10.07.2019
comment
На самом деле это не работает, потому что он регулирует ширину элемента, а не высоту, по горизонтали RecyclerView. - person Nominalista; 10.07.2019
comment
@Nominalista Извините, я допустил ошибку, нужно установить ориентацию на VERTICAL. Я обновил свой ответ, посмотри - person sanoJ; 10.07.2019
comment
Обратите внимание, что я упомянул в описании, что это должен быть горизонтальный вид ресайклера. В реальной задаче слишком много элементов, чтобы поместиться в две колонки. - person Nominalista; 10.07.2019
comment
Меняется ли высота строки? - person sanoJ; 10.07.2019
comment
Все элементы в ряду имеют одинаковую высоту. Но они различаются между другими строками. Я думаю, что в этом случае пример выше показывает, как именно они выглядят. - person Nominalista; 10.07.2019