Русский
Русский
English
Статистика
Реклама

Перевод Кастомный ItemDecoration для RecyclerView

Предисловие переводчика:

Оригинальная статья содержит GIF-анимации с демонстрацией работы кода (т. е. результат). К сожалению, мне не удалось вставить их в статью, т. к. я использовал новый редактор от Хабра, а он очень криво работает с изображениями :) Для просмотра анимаций, вы можете перейти к оригинальной статье

Как-то раз мне нужно было создать собственный ItemDecoration, и я обнаружил, что в Интернете. почти нет ответов на этот вопрос. Надеюсь, что эта статья будет кому-нибудь полезна.

  1. Простой ItemDecoration

  2. Кастомный ItemDecoration

    1. ItemDecorationоснованный на позиции

    2. ItemDecorationбез отрисовки после последнего списка

    3. ItemDecorationоснованный на типе View

1. Простой ItemDecoration : DividerItemDecoration

Основной вариант использования может быть реализован с помощью DividerItemDecorationпредоставляемый Android-ом.

DividerItemDecoration(Context context, int orientation)

Создает разделитель RecyclerView.ItemDecorationкоторый можно использовать с LinearLayoutManager.

@param context[]будет использоваться для доступа к ресурсам.

@param orientation[]должен быть #HORIZONTAL или #VERTICAL.

Что вам нужно, так это установить правильную ориентацию, а затем предоставить drawable-ресурс, используемый для разделения каждого элемента.

recyclerView.addItemDecoration(    DividerItemDecoration(context, LinearLayoutManager.HORIZONTAL)        .apply { setDrawable(myDrawable) })
Простой DividerItemDecorationПростой DividerItemDecoration

Преимущества

  • 4 строчки кода

  • Предоставляется самим Android

  • Прост в использовании

Недостатки

  • Как было упомянуто: ограниченные варианты использования - в качестве разделителя пустым пространством или линией.

  • Ограничение при использовании и "бесконечной" прокруткой (или пагинацией): декоратор будет отрисован для каждого элемента, в том числе и последнего. В большинстве случаев мы этого не хотим.

2. Custom ItemDecoration

Для лучшего контроля и более сложных вещей мы расширим RecyclerView.ItemDecoration.

Нам доступны 3 метода:

  • getItemOffsets используется для определения расстояния между элементами.

  • onDraw используется для отрисовки в пространстве между элементами.

  • onDrawOver то же, что и onDraw, только вызывается после того, как сам элемент отрисован.

? Cм. официальную документацию: getItemOffsets, onDraw, onDrawOver.

? Обратите внимание, что в примерах используется горизонтальная ориентация, но то же самое можно сделать и с вертикальной.

2.1. Decoration в зависимости от позиции элемента

Для начала простой пример: давайте украсим элементы с нечетной позицией в адаптере.

Кастомный ItemDecoration на основе позиции ViewКастомный ItemDecoration на основе позиции View

Главный метод здесь здесь parent.getChildAdapterPosition (view)

Он возвращает позицию переданной View в адаптере. См. полную документацию.


Не путать с дочерними позициями родителей (parent.children - это элементы, отображаемые на экране).

Не забудьте обработать случай с RecyclerView.NO_POSITION, т. к.getChildAdapterPosition может вернуть -1.

import android.graphics.Canvasimport android.graphics.Rectimport android.graphics.drawable.Drawableimport android.view.Viewimport androidx.core.view.childrenimport androidx.recyclerview.widget.RecyclerViewclass CustomPositionItemDecoration(private val dividerDrawable: Drawable) :    RecyclerView.ItemDecoration() {    override fun getItemOffsets(rect: Rect, view: View, parent: RecyclerView, s: RecyclerView.State) {        val position = parent.getChildAdapterPosition(view)            .let { if (it == RecyclerView.NO_POSITION) return else it }        rect.right =            if (position % 2 == 0) 2            else dividerDrawable.intrinsicWidth    }    override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {        parent.children            .forEach { view ->                val position = parent.getChildAdapterPosition(view)                    .let { if (it == RecyclerView.NO_POSITION) return else it }                if (position % 2 != 0) {                    val left = view.right                    val top = parent.paddingTop                    val right = left + dividerDrawable.intrinsicWidth                    val bottom = top + dividerDrawable.intrinsicHeight - parent.paddingBottom                    dividerDrawable.bounds = Rect(left, top, right, bottom)                    dividerDrawable.draw(canvas)                }            }    }}

2.2. Удаляем ItemDecoration в конце списка

Это наиболее распространенный вариант использования на StackOverflow, о котором вы спрашиваете. Это кастомный ItemDecoration, основанный на позиции элемента в адаптере.

Пользовательское оформление DividerItemDecoration, за исключением последнего элементаПользовательское оформление DividerItemDecoration, за исключением последнего элемента

Исключим последний элемент, добавивif (childAdapterPosition == adapter.itemCount-1).

Нужно помнить, чтоparent.adapterможет быть null.

mport android.graphics.Canvasimport android.graphics.Rectimport android.graphics.drawable.Drawableimport android.view.Viewimport androidx.core.view.childrenimport androidx.recyclerview.widget.RecyclerViewclass DividerItemDecorationLastExcluded(private val dividerDrawable: Drawable) :    RecyclerView.ItemDecoration() {    private val dividerWidth = dividerDrawable.intrinsicWidth    private val dividerHeight = dividerDrawable.intrinsicHeight    override fun getItemOffsets(rect: Rect, v: View, parent: RecyclerView, s: RecyclerView.State) {        parent.adapter?.let { adapter ->            val childAdapterPosition = parent.getChildAdapterPosition(v)                .let { if (it == RecyclerView.NO_POSITION) return else it }            rect.right = // Add space/"padding" on right side                if (childAdapterPosition == adapter.itemCount - 1) 0    // No "padding"                else dividerWidth                                       // Drawable width "padding"        }    }    override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {        parent.adapter?.let { adapter ->            parent.children // Displayed children on screen                .forEach { view ->                    val childAdapterPosition = parent.getChildAdapterPosition(view)                        .let { if (it == RecyclerView.NO_POSITION) return else it }                    if (childAdapterPosition != adapter.itemCount - 1) {                        val left = view.right                        val top = parent.paddingTop                        val right = left + dividerWidth                        val bottom = top + dividerHeight - parent.paddingBottom                        dividerDrawable.bounds = Rect(left, top, right, bottom)                        dividerDrawable.draw(canvas)                    }                }        }    }}

? Обратите внимание, что в большинстве случаев вам, вероятно, нужен только интервал между вашими элементами (за исключением последнего). Нет необходимости переопределять метод onDraw, достаточно установить правильный размер Rect в методе getItemsOffsets.

import android.graphics.Rectimport android.view.Viewimport androidx.recyclerview.widget.RecyclerViewclass SimpleDividerItemDecorationLastExcluded(private val spacing: Int) :    RecyclerView.ItemDecoration() {    override fun getItemOffsets(        rect: Rect,        view: View,        parent: RecyclerView,        s: RecyclerView.State    ) {        parent.adapter?.let { adapter ->            rect.right = when (parent.getChildAdapterPosition(view)) {                RecyclerView.NO_POSITION,                adapter.itemCount - 1 -> 0                else -> spacing            }        }    }}
Пользовательский DividerItemDecoration, с добавлением интервала между элементами, за исключением последнегоПользовательский DividerItemDecoration, с добавлением интервала между элементами, за исключением последнего

2.3. Оформление на основе типа View

В этом примере у нас есть 2 типа элементов, показанных черным и серым. Мы оформляем элементы синими и красными Drawable в зависимости от типа View (объявленных в Adapter-е).

Пользовательский ItemDecoration в зависимости от типа ViewПользовательский ItemDecoration в зависимости от типа View

Главная строчка кода здесь здесь - adapter.getItemViewType(childAdapterPosition). Нам нужно переопределить метод getItemViewType в нашем адаптере.

Он возвращает тип View элемента по позиции для повторного использования View. [] Подумайте над тем, чтобы использовать id-ресурсы для уникальной идентификации типов View.

См. официальную документацию getItemViewType.

Как только вы сможете определить тип вашего элемента, вы можете установить правильный интервал и украсить его по своему желанию ?.

import android.content.Contextimport android.graphics.Canvasimport android.graphics.Rectimport android.graphics.drawable.Drawableimport android.view.Viewimport androidx.core.content.ContextCompatimport androidx.core.view.childrenimport androidx.recyclerview.widget.RecyclerViewclass CustomItemDecoration(context: Context) : RecyclerView.ItemDecoration() {    private val decorationRed = ContextCompat.getDrawable(context, R.drawable.item_decoration_red)!!    private val decorationBlue = ContextCompat.getDrawable(context, R.drawable.item_decoration_blue)!!    override fun getItemOffsets(rect: Rect, view: View, parent: RecyclerView, s: RecyclerView.State) {        parent.adapter?.let { adapter ->            val childAdapterPosition = parent.getChildAdapterPosition(view)                .let { if (it == RecyclerView.NO_POSITION) return else it }            rect.right = when (adapter.getItemViewType(childAdapterPosition)) {                CustomAdapter.EVEN_ITEM_ID -> decorationRed.intrinsicWidth                CustomAdapter.ODD_ITEM_ID -> decorationBlue.intrinsicWidth                else -> 0            }        }    }    override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {        parent.adapter?.let { adapter ->            parent.children                .forEach { view ->                    val childAdapterPosition = parent.getChildAdapterPosition(view)                        .let { if (it == RecyclerView.NO_POSITION) return else it }                    when (adapter.getItemViewType(childAdapterPosition)) {                        CustomAdapter.EVEN_ITEM_ID -> decorationRed.drawSeparator(view, parent, canvas)                        CustomAdapter.ODD_ITEM_ID -> decorationBlue.drawSeparator(view, parent, canvas)                        else -> Unit                    }                }        }    }    private fun Drawable.drawSeparator(view: View, parent: RecyclerView, canvas: Canvas) =        apply {            val left = view.right            val top = parent.paddingTop            val right = left + intrinsicWidth            val bottom = top + intrinsicHeight - parent.paddingBottom            bounds = Rect(left, top, right, bottom)            draw(canvas)        }}

Вы можете найти полный код в моём GitHub репозитории.

Источник: habr.com
К списку статей
Опубликовано: 17.12.2020 00:16:58
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Разработка мобильных приложений

Разработка под android

Android

Recyclerview

Itemdecoration

Android ui

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru