Giter VIP home page Giter VIP logo

Comments (14)

rubensousa avatar rubensousa commented on August 18, 2024 1

Fix here: #204

Will be available in 1.3.0-alpha02

from dpadrecyclerview.

rubensousa avatar rubensousa commented on August 18, 2024

Hi @fankloano . Thank you for the detailed bug report, this will help me a lot to investigate.
Will let you know once a fix is available.

from dpadrecyclerview.

rubensousa avatar rubensousa commented on August 18, 2024

@fankloano 1.3.0-alpha02 is already available. Please let me know if the problem was solved in that version

from dpadrecyclerview.

fankloano avatar fankloano commented on August 18, 2024

@rubensousa
sorry for my late answer. The issue seems solved with 1.3.0-alpha02, thanks :-)
Another thing, regarding to the same recyclerview: I just realised that the first image/cardview is smaller then the others. I checked if I have set a margin or something similar that eventually could cause the problem, but there isn't set any. Can you reproduce it?
(should I open a new issue?)

from dpadrecyclerview.

rubensousa avatar rubensousa commented on August 18, 2024

@fankloano yes, please open a new issue and attach a sample project that reproduces it if possible. I won't be able to fix that one so soon since I will be away for vacation

from dpadrecyclerview.

tyrel-carlson avatar tyrel-carlson commented on August 18, 2024

Hi there, I hope you are doing well

I'm facing an issue that when I use setSpanSizeLookup, the last row of items won't get focused.
I've upgraded the library version to 1.3.0-alpha02 but the problem persists.

Is it related to this issue or do I need to open another one?

from dpadrecyclerview.

rubensousa avatar rubensousa commented on August 18, 2024

Hi there @tyrel-carlson . No, this issue should be solved. Please create a new one, ideally with reproduction steps and a video recording

from dpadrecyclerview.

tyrel-carlson avatar tyrel-carlson commented on August 18, 2024

Sorry for late answer, I'm using the dpad recycler with a span count of 4 integrated with a spanSizeLookup to decide if the span size should be 1 or 4 depending on the item type (header or cell) in a sectioned grid view.

the problem is that the last row of items (including a date header) is not getting focused unless I do a fast scrolling but not from direct upper items, so for your information I'm not allowed to publish any screen recording of the production app, so I provide some code here and let me know if I'm missing something, if not I'll open an issue:

private fun setupRecyclerView() = with(binding) {
        recycler.adapter = adapter

        recycler.setSpanCount(SPAN_COUNT)

        recycler.setSpanSizeLookup(object : DpadSpanSizeLookup() {
            override fun getSpanSize(position: Int): Int {
                return if (position < adapter.itemCount
                    && adapter.isSectionHeader(position)
                ) SPAN_COUNT else 1
            }
        })

        recycler.attachListController(viewModel.listController)
    }

    companion object {
        const val SPAN_COUNT = 4
    }

open fun isSectionHeader(position: Int): Boolean {
        return differ.currentList[position] is DataHolder.Section<*>
    }

fun DpadRecyclerView.attachListController(
    controller: StreamingListController
) {
    val listener = object : RecyclerView.OnScrollListener() {
        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
            super.onScrolled(recyclerView, dx, dy)

            val layoutManager = recyclerView.layoutManager as? PivotLayoutManager
                ?: return

            val firstVisibleIndex = layoutManager.findFirstVisibleItemPosition()
            val lastVisibleIndex = max(firstVisibleIndex, layoutManager.findLastVisibleItemPosition())

            controller.setIndexRange(range = StreamingListController.IndexRange(
                start = firstVisibleIndex,
                endInclusive = lastVisibleIndex
            ))
        }
    }

    addOnScrollListener(listener)
}

<com.rubensousa.dpadrecyclerview.DpadRecyclerView
        android:id="@+id/recycler"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_marginHorizontal="10dp"
        android:clipToPadding="false"
        android:paddingTop="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/guideline"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

from dpadrecyclerview.

tyrel-carlson avatar tyrel-carlson commented on August 18, 2024

I'm still stuck on this, can you please help me with that? @rubensousa

from dpadrecyclerview.

rubensousa avatar rubensousa commented on August 18, 2024

Please open a new issue with a video recording and attach this code snippet above. @tyrel-carlson

from dpadrecyclerview.

rubensousa avatar rubensousa commented on August 18, 2024

@tyrel-carlson did you solve this problem?

from dpadrecyclerview.

tyrel-carlson avatar tyrel-carlson commented on August 18, 2024

Yeah, I managed to solve this using my own focusSearch. Here is my solution, so feel free to use it to improve the repo.

import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.rubensousa.dpadrecyclerview.DpadRecyclerView

class SectionableRecyclerView : DpadRecyclerView {
    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle)

    init {
        installGlobalLayoutListener {
            // Here we prevent the focus from being lost when the user focuses the empty recyclerview
            if (hasFocus() && childCount > 1) {
                val focused = findFocus()
                if (focused == null || focused is RecyclerView) {
                    getChildAt(1)?.requestFocus()
                }
            }
        }
    }

    override fun setAdapter(adapter: Adapter<*>?) {
        if (adapter != null && adapter !is SectionedListAdapter<*, *, *, *>) {
            throw IllegalArgumentException("Must be a SectionedListAdapter")
        }

        super.setAdapter(adapter)
    }

    override fun focusSearch(focused: View, direction: Int): View? {
        try {
            val adapter = adapter as SectionedListAdapter<*, *, *, *>

            // Get the current position of the focused view
            val currentPosition = getChildAdapterPosition(focused)
            if (currentPosition == NO_POSITION) {
                return super.focusSearch(focused, direction)
            }

            // Find the next position based on the focus direction
            val nextPosition = when (direction) {
                View.FOCUS_UP,
                View.FOCUS_DOWN -> findNextPosition(adapter, currentPosition, direction)

                else -> return super.focusSearch(focused, direction)
            }

            // Ensure the next position is valid
            if (nextPosition == NO_POSITION || !isPositionValid(adapter, nextPosition)) {
                return when (direction) {
                    View.FOCUS_UP -> parent.focusSearch(focused, direction)
                    else -> super.focusSearch(focused, direction)
                }
            }

            // This is to prevent the recycler from reverting to previously focused item
            // in some situations
            layoutManager?.scrollToPosition(nextPosition)

            return findViewAtPosition(nextPosition) ?: return super.focusSearch(focused, direction)

        } catch (e: Exception) {
            return super.focusSearch(focused, direction)
        }
    }

    /**
     * Finds the next valid position to focus on when navigating in the specified direction.
     *
     * @param adapter The adapter of the RecyclerView, which must be a SectionedListAdapter.
     * @param currentPosition The current position of the focused item.
     * @param direction The direction of the focus movement, either View.FOCUS_DOWN or View.FOCUS_UP.
     * @return The next valid position to focus on, or NO_POSITION if no valid position is found.
     */
    private fun findNextPosition(adapter: SectionedListAdapter<*, *, *, *>, currentPosition: Int, direction: Int): Int {
        val spanCount = getSpanCount()
        val currentSpanIndex = getSpanIndex(currentPosition, spanCount)

        return when (direction) {
            View.FOCUS_DOWN -> {
                var nextPosition = currentPosition + 1

                while (
                    isPositionValid(adapter, nextPosition) &&
                    (currentSpanIndex != getSpanIndex(nextPosition, spanCount) || adapter.isSectionHeader(nextPosition))
                ) {
                    nextPosition++
                }

                // Prevent unusual focus movement if the destination cell is empty
                if (nextPosition - currentPosition > (spanCount + 1))
                    findNextPosition(adapter, currentPosition - 1, direction)
                else nextPosition
            }

            View.FOCUS_UP -> {
                var previousPosition = currentPosition - 1

                while (
                    isPositionValid(adapter, previousPosition) &&
                    (currentSpanIndex != getSpanIndex(previousPosition, spanCount) || adapter.isSectionHeader(previousPosition))
                ) {
                    previousPosition--
                }

                // Prevent unusual focus movement if the destination cell is empty
                if (currentPosition - previousPosition > (spanCount + 1))
                    findNextPosition(adapter, currentPosition - 1, direction)
                else previousPosition
            }

            else -> NO_POSITION
        }
    }

    /**
     * Checks if the given position is valid within the adapter's item count.
     *
     * @param adapter The adapter of the RecyclerView, which must be a SectionedListAdapter.
     * @param position The position to check.
     * @return True if the position is within the bounds of the adapter's item count, false otherwise.
     */
    private fun isPositionValid(adapter: SectionedListAdapter<*, *, *, *>, position: Int): Boolean {
        return position in 0 until adapter.itemCount
    }

    /**
     * Retrieves the span index for the given position in the grid.
     *
     * @param position The position of the item.
     * @param spanCount The number of spans in the grid.
     */
    private fun getSpanIndex(position: Int, spanCount: Int): Int {
        return getSpanSizeLookup().getSpanIndex(position, spanCount)
    }

    /**
     * Finds the view at the specified adapter position.
     *
     * @param position The adapter position of the item.
     * @return The view at the specified position, or null if no view is found.
     */
    private fun findViewAtPosition(position: Int): View? {
        val layoutManager = layoutManager ?: return null
        return (0 until layoutManager.childCount)
            .mapNotNull { layoutManager.getChildAt(it) }
            .firstOrNull { getChildAdapterPosition(it) == position }
    }

}

from dpadrecyclerview.

rubensousa avatar rubensousa commented on August 18, 2024

Can you please raise a new issue with the original problem so I can have a look for the next release? Meanwhile 1.3.0-alpha03 should remove the need for that workaround you have for focus in that constructor

from dpadrecyclerview.

tyrel-carlson avatar tyrel-carlson commented on August 18, 2024

Sure, I will open a new issue. FYI, I've tested 1.3.0-alpha03, and it doesn't solve this issue yet.

from dpadrecyclerview.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.