diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/focus/DefaultFocusInterceptor.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/focus/DefaultFocusInterceptor.kt index ca656a9a..26de6fe8 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/focus/DefaultFocusInterceptor.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/focus/DefaultFocusInterceptor.kt @@ -38,12 +38,67 @@ internal class DefaultFocusInterceptor( position: Int, direction: Int ): View? { - val absoluteDirection = FocusDirection.getAbsoluteDirection( + val focusAbsoluteDirection = FocusDirection.getAbsoluteDirection( direction = direction, isVertical = configuration.isVertical(), reverseLayout = layoutInfo.shouldReverseLayout() ) - return focusFinder.findNextFocus(recyclerView, focusedView, absoluteDirection) + // Exit early if the focus finder can't find focus already + val nextFocusFinderView = focusFinder.findNextFocus( + recyclerView, focusedView, focusAbsoluteDirection + ) ?: return null + val currentViewHolder = layoutInfo.getChildViewHolder(focusedView) + val nextViewHolder = layoutInfo.getChildViewHolder(nextFocusFinderView) + /** + * Check if the focus finder has found another focusable view for the same ViewHolder + * This might happen when sub positions are used + */ + if (nextViewHolder === currentViewHolder && nextFocusFinderView !== focusedView) { + return nextFocusFinderView + } + return if (configuration.spanCount == 1) { + val relativeFocusDirection = FocusDirection.from( + direction, isVertical = configuration.isVertical(), + reverseLayout = configuration.reverseLayout + ) ?: return nextFocusFinderView + findNextLinearChild(position, relativeFocusDirection) + } else { + return nextFocusFinderView + } } + private fun findNextLinearChild(position: Int, direction: FocusDirection): View? { + // We only support the main direction + if (direction.isSecondary()) { + return null + } + val positionIncrement = layoutInfo.getPositionIncrement( + goingForward = direction == FocusDirection.NEXT_ROW + || direction == FocusDirection.NEXT_COLUMN + ) + val nextPosition = position + positionIncrement + // Jump early if we're going out of bounds + if (nextPosition < 0 || nextPosition == layoutInfo.getItemCount()) { + return null + } + return findNextFocusableView( + fromPosition = nextPosition, + positionIncrement = positionIncrement + ) + } + + private fun findNextFocusableView( + fromPosition: Int, + positionIncrement: Int + ): View? { + var currentPosition = fromPosition + while (currentPosition >= 0 && currentPosition < layoutInfo.getItemCount()) { + val view = layoutInfo.findViewByPosition(currentPosition) + if (view != null && layoutInfo.isViewFocusable(view)) { + return view + } + currentPosition += positionIncrement + } + return null + } } diff --git a/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/dynamic/MediumListDelegate.kt b/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/dynamic/MediumListDelegate.kt index 39d20ff3..486d25e0 100644 --- a/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/dynamic/MediumListDelegate.kt +++ b/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/dynamic/MediumListDelegate.kt @@ -19,7 +19,7 @@ package com.rubensousa.dpadrecyclerview.sample.ui.screen.dynamic import android.view.LayoutInflater import android.view.ViewGroup import com.rubensousa.dpadrecyclerview.UnboundViewPool -import com.rubensousa.dpadrecyclerview.sample.databinding.AdapterListComposeBinding +import com.rubensousa.dpadrecyclerview.sample.databinding.AdapterListMediumBinding import com.rubensousa.dpadrecyclerview.sample.ui.model.RecyclerViewItem import com.rubensousa.dpadrecyclerview.sample.ui.model.ViewHolderDelegate import com.rubensousa.dpadrecyclerview.state.DpadScrollState @@ -32,7 +32,7 @@ class MediumListDelegate( override fun onCreateViewHolder(parent: ViewGroup): MediumListViewHolder { return MediumListViewHolder( viewPool, - AdapterListComposeBinding.inflate( + AdapterListMediumBinding.inflate( LayoutInflater.from(parent.context), parent, false ) ) diff --git a/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/dynamic/MediumListViewHolder.kt b/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/dynamic/MediumListViewHolder.kt index b4672117..0f3a81d1 100644 --- a/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/dynamic/MediumListViewHolder.kt +++ b/sample/src/main/java/com/rubensousa/dpadrecyclerview/sample/ui/screen/dynamic/MediumListViewHolder.kt @@ -18,13 +18,13 @@ package com.rubensousa.dpadrecyclerview.sample.ui.screen.dynamic import com.rubensousa.dpadrecyclerview.DpadViewHolder import com.rubensousa.dpadrecyclerview.UnboundViewPool -import com.rubensousa.dpadrecyclerview.sample.databinding.AdapterListComposeBinding +import com.rubensousa.dpadrecyclerview.sample.databinding.AdapterListMediumBinding import com.rubensousa.dpadrecyclerview.sample.ui.model.DelegateViewHolder import com.rubensousa.dpadrecyclerview.sample.ui.widgets.common.ListAnimator class MediumListViewHolder( private val recycledViewPool: UnboundViewPool, - private val binding: AdapterListComposeBinding, + private val binding: AdapterListMediumBinding, ) : DelegateViewHolder(binding.root), DpadViewHolder { val recyclerView = binding.cardRecyclerView diff --git a/sample/src/main/res/layout/adapter_list_medium.xml b/sample/src/main/res/layout/adapter_list_medium.xml new file mode 100644 index 00000000..b6543edf --- /dev/null +++ b/sample/src/main/res/layout/adapter_list_medium.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + \ No newline at end of file