Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to keep the last item's focus of RecyclerView when navigating to the end of the list?

I used a RecyclerView with HORIZONTAL direction in my TV development which controlled by a D-pad to navigate the list from the left to right. the last item of the RecyclerView always lost focus when navigating to the right-most of the list.

So how can i keep the last item's focus when navigating to the end of the list?

like image 863
Xiaozou Avatar asked Feb 23 '16 06:02

Xiaozou


People also ask

What does notifyDataSetChanged do in RecyclerView?

notifyDataSetChanged. Notify any registered observers that the data set has changed. There are two different classes of data change events, item changes and structural changes. Item changes are when a single item has its data updated but no positional changes have occurred.

What is setHasFixedSize in RecyclerView?

setHasFixedSize(true) means the RecyclerView has children (items) that has fixed width and height. This allows the RecyclerView to optimize better by figuring out the exact height and width of the entire list based on the your adapter.


2 Answers

If you're using a subclass of BaseGridView, like HorizontalGridView or VerticalGridView, set an onKeyInterceptListener that swallows the movement key at the end of the list. For example, with a HorizontalGridView:

grid.setOnKeyInterceptListener { event ->
    val focused = grid.focusedChild
    event?.keyCode == KEYCODE_DPAD_RIGHT && grid.layoutManager.getPosition(focused) == grid.adapter.itemCount-1
}

If you're using RecyclerView directly, then use onInterceptFocusSearch with a custom LinearLayoutManager. For example, with a LinearLayoutManager.VERTICAL list:

list.layoutManager = object: LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) {
    override fun onInterceptFocusSearch(focused: View?, direction: Int): View? {
        if (direction == View.FOCUS_DOWN) {
            val pos = getPosition(focused)
            if (pos == itemCount-1)
                return focused
        }
        if (direction == View.FOCUS_UP) {
            val pos = getPosition(focused)
            if (pos == 0)
                return focused
        }
        return super.onInterceptFocusSearch(focused, direction)
    }
}
like image 88
tmm1 Avatar answered Sep 22 '22 16:09

tmm1


I dug into the source code of RecyclerView, i found the onInterceptFocusSearch method in the LayoutManager, inner class of RecyclerView.

/**
 * This method gives a LayoutManager an opportunity to intercept the initial focus search
 * before the default behavior of {@link FocusFinder} is used. If this method returns
 * null FocusFinder will attempt to find a focusable child view. If it fails
 * then {@link #onFocusSearchFailed(View, int, RecyclerView.Recycler, RecyclerView.State)}
 * will be called to give the LayoutManager an opportunity to add new views for items
 * that did not have attached views representing them. The LayoutManager should not add
 * or remove views from this method.
 *
 * @param focused The currently focused view
 * @param direction One of { @link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
 *                  {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT},
 *                  {@link View#FOCUS_BACKWARD}, {@link View#FOCUS_FORWARD}
 * @return A descendant view to focus or null to fall back to default behavior.
 *         The default implementation returns null.
 */
public View onInterceptFocusSearch(View focused, int direction) {
    return null ;
}

which gives a LayoutManager an opportunity to intercept the initial focus search before the default behavior of FocusFinder is used.

So i overrided the onInterceptFocusSearch likes below, and used the CustomGridLayoutManager for my RecylerView, which works like a charming.

public class CustomGridLayoutManager extends android.support.v7.widget.GridLayoutManager {

        public CustomGridLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
                                 int defStyleRes) {
            super (context, attrs, defStyleAttr, defStyleRes);
        }

        public CustomGridLayoutManager(Context context, int spanCount) {
            super (context, spanCount);
        }

        public CustomGridLayoutManager(Context context, int spanCount, int orientation,
                                 boolean reverseLayout) {
            super (context, spanCount, orientation, reverseLayout);
        }

        @Override
        public View onInterceptFocusSearch(View focused, int direction) {
            int pos = getPosition(focused);
            int count = getItemCount();
            int orientation = getOrientation();


            **********
            do some logic
            what i did was return the focused View when the focused view is the last item of RecyclerView.
            **********

            return super .onInterceptFocusSearch(focused, direction);
        }
}
like image 41
Xiaozou Avatar answered Sep 23 '22 16:09

Xiaozou