Skip to content

Instantly share code, notes, and snippets.

@osipxd
Created February 11, 2022 06:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save osipxd/45357cc939844b0994c85e11ffa0187a to your computer and use it in GitHub Desktop.
Save osipxd/45357cc939844b0994c85e11ffa0187a to your computer and use it in GitHub Desktop.
Anchor view for DiffUtil.
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.View
import com.airbnb.epoxy.ModelView
/**
* Вьюха-якорь, которая нужна чтобы в зафиксировать положение скролла в `RecyclerView`.
*
* При обновлении данных в `RecyclerView`, список "сам решает" нужно ли скроллить сожержимое,
* при этом количество скроллов сводится к минимуму.
* Например, у нас есть входные данные `[1, 2, 3]`, которые превращаются в такой набор вьюх в списке:
* ```
* ======== <- Верхняя граница списка
* [Item 1]
* [Item 2]
* [Item 3]
* ======== <- Нижняя граница списка
* ```
* Представим, что на экране одновременно помещаются только три элемента и при добавлении
* четвёртого список нужно скроллить.
* Мы хотим передать новый набор данных с четырьмя элементами `[X, 1, 2, 3]`. Новый элемент будет
* добавлен в верх списка и при этом он окажется за границей списка, т.к. список "старается сохранить"
* позицию скролла, которая была до обновления списка. Позиция скролла определяется опираясь на
* элементы списка которые присутствуют и в старом списке, и в новом.
* ```
* [Item X] <- Новый элемент появился за верхней границей списка и его не видно
* ========
* [Item 1] <- Элемент из старого списка. Сохраняет свою позицию, то есть остаётся сверху экрана
* [Item 2]
* [Item 3]
* ========
* ```
* Если такое поведение списка не устраивает, мы можем добавить вьюху-якорь с нулевой высотой.
* Она будет визуально не видна, но будет являться тем самым элементом списка, для которого нужно
* сохранить позицию скролла:
* ```
* ========
* -------- <- Вьюха-якорь. Должна так же присутствовать и в старом списке
* [Item X] <- Новый элемент появился между "якорем" и первым элементом списка и теперь виден
* [Item 1]
* [Item 2]
* ========
* [Item 3] <- Нижний элемент уходит за границу списка
* ```
*
* В коде добавление якоря выглядит так:
* ```
* class ChatEpoxyController : TypedEpoxyController<List<String>>() {
*
* override fun buildModels(messages: List<String>) {
* anchorView { id("top_anchor") } // Добавляем якорь в верх списка
* for (message in messages) {
* chatMessageItem {
* id(message)
* text(message)
* }
* }
* }
* }
* ```
*/
@ModelView(autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT)
class AnchorView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
// RecyclerView сходит с ума, когда в начале списка оказывается вьюха с высотой 0,
// поэтому делаем размер 1x1 px.
// Если этого не сделать RecyclerView "думает", что список ещё есть куда скроллить, поэтому:
// - ломается работа флага liftOnScroll
// - ломается скролл боттом-шита, если в нём есть список
// Похожий баг - https://github.com/airbnb/epoxy/issues/74
setMeasuredDimension(1, 1)
}
@SuppressLint("MissingSuperCall")
override fun draw(canvas: Canvas) = Unit
}
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
class AnchorItem(private val id: String) : Item() {
override fun getId(): Long = id.hashCode()
override fun getLayout(): Int = R.layout.item_anchor // See item_anchor.xml
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
// do nothing
}
/** Возвращаем всегда true, чтобы DiffUtil "зацепился" за этот айтем. */
override fun hasSameContentAs(other: com.xwray.groupie.Item<*>): Boolean = true
}
<?xml version="1.0" encoding="utf-8"?>
<Space xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="1px"
android:layout_height="1px" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment