Fix snapping after scroll actions in paging gallery
commit_hash:0bc9521306e4f743628b8bd796b4f15670d0e1b8
@@ -3,6 +3,7 @@ package com.yandex.div.core.view2.items
|
||||
import android.util.DisplayMetrics
|
||||
import android.view.View
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -10,6 +11,7 @@ import com.yandex.div.core.view2.divs.availableHeight
|
||||
import com.yandex.div.core.view2.divs.availableWidth
|
||||
import com.yandex.div.core.view2.divs.dpToPx
|
||||
import com.yandex.div.core.view2.divs.gallery.DivGalleryAdapter
|
||||
import com.yandex.div.core.view2.divs.gallery.PagerSnapStartHelper
|
||||
import com.yandex.div.core.view2.divs.pager.DivPagerAdapter
|
||||
import com.yandex.div.core.view2.divs.spToPx
|
||||
import com.yandex.div.core.view2.divs.widgets.DivPagerView
|
||||
@@ -73,9 +75,20 @@ internal sealed class DivViewWithItems {
|
||||
*/
|
||||
internal class PagingGallery(view: DivRecyclerView, direction: Direction) : Gallery(view, direction) {
|
||||
|
||||
override var currentItem: Int
|
||||
get() = view.currentItem(direction)
|
||||
set(value) = checkItem(value, itemCount) { view.smoothScrollToPosition(value) }
|
||||
override fun createSmoothScroller(): RecyclerView.SmoothScroller {
|
||||
return object : DivSmoothScroller(view) {
|
||||
override fun calculateDtToFit(
|
||||
viewStart: Int, viewEnd: Int,
|
||||
boxStart: Int, boxEnd: Int,
|
||||
snapPreference: Int
|
||||
): Int {
|
||||
val itemSpacing = view.pagerSnapStartHelper?.itemSpacing ?: return 0
|
||||
val isHorizontal = (view.adapter as? DivGalleryAdapter)?.orientation == RecyclerView.HORIZONTAL
|
||||
val size = if (isHorizontal) view.width else view.height
|
||||
return (size - viewStart - viewEnd + itemSpacing) / 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,19 +104,19 @@ internal sealed class DivViewWithItems {
|
||||
get() = view.currentItem(direction)
|
||||
set(value) {
|
||||
checkItem(value, itemCount) {
|
||||
val smoothScroller = object : LinearSmoothScroller(view.context) {
|
||||
private val MILLISECONDS_PER_INCH = 50f // default is 25f, bigger - slower
|
||||
override fun getHorizontalSnapPreference() = SNAP_TO_START
|
||||
override fun getVerticalSnapPreference() = SNAP_TO_START
|
||||
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
|
||||
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi
|
||||
}
|
||||
}
|
||||
val smoothScroller = createSmoothScroller()
|
||||
smoothScroller.targetPosition = value
|
||||
view.layoutManager?.startSmoothScroll(smoothScroller)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun createSmoothScroller(): RecyclerView.SmoothScroller {
|
||||
return object : DivSmoothScroller(view) {
|
||||
override fun getHorizontalSnapPreference() = SNAP_TO_START
|
||||
override fun getVerticalSnapPreference() = SNAP_TO_START
|
||||
}
|
||||
}
|
||||
|
||||
override val itemCount: Int
|
||||
get() = view.itemCount
|
||||
|
||||
@@ -117,10 +130,27 @@ internal sealed class DivViewWithItems {
|
||||
|
||||
override fun setCurrentItemNoAnimation(index: Int) {
|
||||
checkItem(index, itemCount) {
|
||||
view.scrollToPosition(index)
|
||||
view.pagerSnapStartHelper?.snapToPosition(index, waitForLayout = true)
|
||||
?: view.scrollToPosition(index)
|
||||
}
|
||||
}
|
||||
|
||||
private fun PagerSnapStartHelper.snapToPosition(position: Int, waitForLayout: Boolean) {
|
||||
val layoutManager = view.layoutManager ?: return
|
||||
layoutManager.findViewByPosition(position)?.let { target ->
|
||||
val offset = calculateDistanceToFinalSnap(layoutManager, target)
|
||||
view.scrollBy(offset[0], offset[1])
|
||||
return
|
||||
}
|
||||
|
||||
if (!waitForLayout) return
|
||||
|
||||
view.doOnNextLayout {
|
||||
snapToPosition(position, waitForLayout = false)
|
||||
}
|
||||
view.scrollToPosition(position)
|
||||
}
|
||||
|
||||
override fun getIndicesOfItemWithId(id: String): List<Int> {
|
||||
val adapter = view.adapter as? DivGalleryAdapter ?: return emptyList()
|
||||
return adapter.visibleItems.getIndicesWithId(id) { div }
|
||||
@@ -205,6 +235,16 @@ internal sealed class DivViewWithItems {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private open class DivSmoothScroller(view: RecyclerView): LinearSmoothScroller(view.context) {
|
||||
|
||||
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics) =
|
||||
MILLISECONDS_PER_INCH / displayMetrics.densityDpi
|
||||
|
||||
companion object {
|
||||
private const val MILLISECONDS_PER_INCH = 50f // default is 25f, bigger - slower
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -160,11 +160,13 @@ class GalleryItemsViewTest {
|
||||
on { resources } doReturn mock()
|
||||
}
|
||||
whenever(view.layoutManager).thenReturn(layoutManager)
|
||||
val scrollCaptor = argumentCaptor<LinearSmoothScroller>()
|
||||
val underTest = createUnderTest(drv = view)
|
||||
|
||||
underTest.currentItem = 5
|
||||
|
||||
verify(view).smoothScrollToPosition(5)
|
||||
verify(layoutManager).startSmoothScroll(scrollCaptor.capture())
|
||||
Assert.assertEquals(5, scrollCaptor.firstValue.targetPosition)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
|
Before Width: | Height: | Size: 770 B After Width: | Height: | Size: 777 B |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 714 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
Before Width: | Height: | Size: 704 B After Width: | Height: | Size: 714 B |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
@@ -84,7 +84,7 @@
|
||||
{
|
||||
"div_actions": [
|
||||
{
|
||||
"url": "div-action://set_next_item?id=gallery",
|
||||
"url": "div-action://set_next_item?id=gallery&animated=false",
|
||||
"log_id": "scroll_forward"
|
||||
}
|
||||
],
|
||||
@@ -93,7 +93,7 @@
|
||||
{
|
||||
"div_actions": [
|
||||
{
|
||||
"url": "div-action://scroll_to_end?id=gallery",
|
||||
"url": "div-action://scroll_to_end?id=gallery&animated=false",
|
||||
"log_id": "scroll_to_end"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
{
|
||||
"div_actions": [
|
||||
{
|
||||
"url": "div-action://set_next_item?id=gallery",
|
||||
"url": "div-action://set_next_item?id=gallery&animated=false",
|
||||
"log_id": "scroll_forward"
|
||||
}
|
||||
],
|
||||
@@ -106,7 +106,7 @@
|
||||
{
|
||||
"div_actions": [
|
||||
{
|
||||
"url": "div-action://scroll_to_end?id=gallery",
|
||||
"url": "div-action://scroll_to_end?id=gallery&animated=false",
|
||||
"log_id": "scroll_to_end"
|
||||
}
|
||||
],
|
||||
|
||||