Created
February 11, 2022 06:29
-
-
Save osipxd/45357cc939844b0994c85e11ffa0187a to your computer and use it in GitHub Desktop.
Anchor view for DiffUtil.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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