diff --git a/docs/changelog.md b/docs/changelog.md index 10853004..91c3c064 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -2,6 +2,15 @@ ## Version 1.2.0 +### 1.2.0-alpha02 + +2023-12-27 + +#### Bug fixes + +- Fixed layout jumping to the top when the user scrolls with touch events ([#171](https://github.com/rubensousa/DpadRecyclerView/issues/171)) +- Fixed alignment configuration not being respected for small lists ([#172](https://github.com/rubensousa/DpadRecyclerView/issues/172)) + ### 1.2.0-alpha01 2023-11-25 diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt index 5ed522cd..087d08a3 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt @@ -376,7 +376,7 @@ open class DpadRecyclerView @JvmOverloads constructor( final override fun onRtlPropertiesChanged(layoutDirection: Int) { super.onRtlPropertiesChanged(layoutDirection) - pivotLayoutManager?.onRtlPropertiesChanged() + pivotLayoutManager?.onRtlPropertiesChanged(layoutDirection) } final override fun smoothScrollBy(dx: Int, dy: Int) { diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/LayoutConfiguration.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/LayoutConfiguration.kt index e766e4ec..f180e80d 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/LayoutConfiguration.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/LayoutConfiguration.kt @@ -132,6 +132,9 @@ internal class LayoutConfiguration(properties: Properties) { var recycleChildrenOnDetach: Boolean = false private set + var keepLayoutAnchor: Boolean = false + private set + init { setSpanCount(properties.spanCount) setOrientation(properties.orientation) @@ -250,4 +253,10 @@ internal class LayoutConfiguration(properties: Properties) { } } + // This flag will be used to try to keep layout consistency + // whenever the user scrolls the layout using touch events + fun setKeepLayoutAnchor(keep: Boolean) { + keepLayoutAnchor = keep + } + } diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutManager.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutManager.kt index b05e7217..302ae18a 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutManager.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutManager.kt @@ -50,6 +50,7 @@ import com.rubensousa.dpadrecyclerview.layoutmanager.scroll.LayoutScroller */ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager() { + private var layoutDirection: Int = View.LAYOUT_DIRECTION_LTR private val configuration = LayoutConfiguration(properties) private val layoutInfo = LayoutInfo(this, configuration) private val pivotSelector = PivotSelector(this, layoutInfo) @@ -389,7 +390,11 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager() pivotLayout.onRestoreInstanceState(state) } - internal fun onRtlPropertiesChanged() { + internal fun onRtlPropertiesChanged(layoutDirection: Int) { + if (this.layoutDirection == layoutDirection) { + return + } + this.layoutDirection = layoutDirection requestLayout() } @@ -406,6 +411,7 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager() internal fun getConfig() = configuration internal fun setScrollingFromTouchEvent(isTouching: Boolean) { + configuration.setKeepLayoutAnchor(isTouching) isScrollingFromTouchEvent = isTouching } diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/focus/FocusDispatcher.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/focus/FocusDispatcher.kt index 45de7d0c..d0bba195 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/focus/FocusDispatcher.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/focus/FocusDispatcher.kt @@ -266,6 +266,7 @@ internal class FocusDispatcher( * Request focus to the current pivot if it exists */ fun onRequestFocusInDescendants(direction: Int, previouslyFocusedRect: Rect?): Boolean { + if (configuration.isFocusSearchDisabled) return false val view = layout.findViewByPosition(pivotSelector.position) ?: return false return view.requestFocus(direction, previouslyFocusedRect) } diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/layout/PivotLayout.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/layout/PivotLayout.kt index 71f3c0f5..3db66a21 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/layout/PivotLayout.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/layout/PivotLayout.kt @@ -38,7 +38,7 @@ internal class PivotLayout( private val configuration: LayoutConfiguration, private val pivotSelector: PivotSelector, private val scroller: LayoutScroller, - private val layoutInfo: LayoutInfo + private val layoutInfo: LayoutInfo, ) { companion object { @@ -50,6 +50,7 @@ internal class PivotLayout( private var structureEngineer = createStructureEngineer() private val layoutCompleteListeners = ArrayList() private val itemChanges = ItemChanges() + private var anchor: Int? = null fun updateStructure() { structureEngineer = createStructureEngineer() @@ -91,7 +92,7 @@ internal class PivotLayout( private fun preLayoutChildren( pivotPosition: Int, recycler: RecyclerView.Recycler, - state: RecyclerView.State + state: RecyclerView.State, ) { val childCount = layoutManager.childCount if (childCount == 0) { @@ -116,8 +117,16 @@ internal class PivotLayout( structureEngineer.logChildren() } + if (configuration.keepLayoutAnchor) { + saveAnchorState() + } + structureEngineer.layoutChildren(pivotSelector.position, itemChanges, recycler, state) + if (configuration.keepLayoutAnchor) { + restoreAnchorState(recycler, state) + } + if (DpadRecyclerView.DEBUG) { Log.i(TAG, "LayoutFinished") structureEngineer.logChildren() @@ -126,6 +135,41 @@ internal class PivotLayout( structureEngineer.onLayoutFinished() } + private fun saveAnchorState() { + val currentPivotPosition = if (!layoutInfo.shouldReverseLayout()) { + layoutInfo.findFirstVisiblePosition() + } else { + layoutInfo.findLastVisiblePosition() + } + if (currentPivotPosition == RecyclerView.NO_POSITION) { + return + } + pivotSelector.update(currentPivotPosition) + layoutInfo.findViewByPosition(currentPivotPosition)?.let { view -> + anchor = if (!layoutInfo.shouldReverseLayout()) { + layoutInfo.getDecoratedStart(view) + } else { + layoutInfo.getDecoratedEnd(view) + } + } + } + + private fun restoreAnchorState(recycler: RecyclerView.Recycler, state: RecyclerView.State) { + anchor?.let { previousAnchor -> + if (pivotSelector.position != RecyclerView.NO_POSITION) { + layoutInfo.findViewByPosition(pivotSelector.position)?.let { view -> + val currentAnchor = if (!layoutInfo.shouldReverseLayout()) { + layoutInfo.getDecoratedStart(view) + } else { + layoutInfo.getDecoratedEnd(view) + } + scrollBy(currentAnchor - previousAnchor, recycler, state) + } + } + anchor = null + } + } + fun reset() { structureEngineer.clear() } @@ -173,7 +217,7 @@ internal class PivotLayout( fun scrollHorizontallyBy( dx: Int, recycler: RecyclerView.Recycler, - state: RecyclerView.State + state: RecyclerView.State, ): Int { if (configuration.isVertical()) { return 0 @@ -184,7 +228,7 @@ internal class PivotLayout( fun scrollVerticallyBy( dy: Int, recycler: RecyclerView.Recycler, - state: RecyclerView.State + state: RecyclerView.State, ): Int { if (configuration.isHorizontal()) { return 0 @@ -216,7 +260,7 @@ internal class PivotLayout( private fun scrollBy( offset: Int, recycler: RecyclerView.Recycler, - state: RecyclerView.State + state: RecyclerView.State, ): Int { // Do nothing if we don't have children if (state.itemCount == 0 || offset == 0 || !configuration.isLayoutEnabled) { @@ -242,7 +286,7 @@ internal class PivotLayout( val selectedPosition: Int, val isLoopingStart: Boolean, val isLoopingAllowed: Boolean, - val loopDirection: DpadLoopDirection + val loopDirection: DpadLoopDirection, ) : Parcelable { companion object CREATOR : Parcelable.Creator { diff --git a/gradle.properties b/gradle.properties index 2fe1366e..4bb7eca8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,4 +22,4 @@ kotlin.code.style=official # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true android.enableR8.fullMode=true -LIBRARY_VERSION=1.2.0-alpha01 \ No newline at end of file +LIBRARY_VERSION=1.2.0-alpha02 \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 5eff1430..7b325e2b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -24,7 +24,7 @@ theme: extra: dpadrecyclerview: - version: '1.2.0-alpha01' + version: '1.2.0-alpha02' social: - icon: 'fontawesome/brands/github' link: 'https://github.com/rubensousa/DpadRecyclerView'