BArtWell
BArtWell

Reputation: 4044

How to implement scrolling in RecyclerView on Android TV?

I have an application which I need adapt for Android TV. This application contains horizontal RecyclerView and it doesn't scroll when I press D-pad buttons on remote control. I found this solution, but it crashes. Here is the code:

<ru.myapp.package.HorizontalPersistentFocusWrapper
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
       <android.support.v7.widget.RecyclerView
           android:id="@+id/recycler_view"
           android:layout_width="match_parent"
           android:layout_height="250dp"
           android:background="@null"
           android:scrollbars="none"/>
</ru.myapp.package.HorizontalPersistentFocusWrapper>

HorizontalPersistentFocusWrapper is the same as PersistentFocusWrapper but mPersistFocusVertical = false;

Crash occure in this place:

@Override
    public void requestChildFocus(View child, View focused) {
        super.requestChildFocus(child, focused);
        View view = focused;
        while (view != null && view.getParent() != child) {
            view = (View) view.getParent(); <<<------ Crash here
        }
        mSelectedPosition = view == null ? -1 : ((ViewGroup) child).indexOfChild(view);
        if (DEBUG) Log.v(TAG, "requestChildFocus focused " + focused + " mSelectedPosition " + mSelectedPosition);
    }

Crash stacktrace:

java.lang.ClassCastException: android.view.ViewRootImpl cannot be cast to android.view.View
         at ru.myapp.package.HorizontalPersistentFocusWrapper.requestChildFocus(HorizontalPersistentFocusWrapper.java:108)
         at android.view.View.handleFocusGainInternal(View.java:5465)
         at android.view.ViewGroup.handleFocusGainInternal(ViewGroup.java:714)
         at android.view.View.requestFocusNoSearch(View.java:8470)
         at android.view.View.requestFocus(View.java:8449)
         at android.view.ViewGroup.requestFocus(ViewGroup.java:2747)
         at android.view.View.requestFocus(View.java:8416)
         at android.support.v4.widget.NestedScrollView.arrowScroll(NestedScrollView.java:1222)
         at android.support.v4.widget.NestedScrollView.executeKeyEvent(NestedScrollView.java:551)
         at android.support.v4.widget.NestedScrollView.dispatchKeyEvent(NestedScrollView.java:512)
         at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1640)
         at android.view.ViewGroup.dispatchKeyEvent(ViewGroup.java:1640)

Upvotes: 9

Views: 6790

Answers (3)

s-hunter
s-hunter

Reputation: 25826

Set the focusable to true in the root view of the recyclerview item. android:focusable="true" and apply a selector background to the root view item. Everything can be done in the xml files. With the following settings, the dpad or remote controller will be able to scroll up and down the list, the current selected item in the list will be highlighted.

The layout file for the root view of the list item in the RecyclerView: list_item.xml , the important parts here are android:background="@drawable/item_selector" and android:focusable="true"

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/item_selector"
    android:focusable="true">
    <TextView
        android:id="@+id/topic"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:textColor="#ffffff"
        android:gravity="center"
        tools:text="Education"/>
</LinearLayout>

The drawable selector file in the drawable folder: item_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="#000000" android:state_pressed="true"/>
<item android:drawable="#000000" android:state_selected="true"/>
<item android:drawable="#000000" android:state_focused="true"/>
<item android:drawable="#03A9F4"></item>
</selector>

The layout file containing the RecyclerView: activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
                                             xmlns:tools="http://schemas.android.com/tools"
                                             android:layout_width="match_parent"
                                             android:layout_height="match_parent"
                                             xmlns:app="http://schemas.android.com/apk/res-auto"
                                             tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="none"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintBottom_toBottomOf="parent"/>

</android.support.constraint.ConstraintLayout>

Upvotes: 3

Yani2000
Yani2000

Reputation: 893

Use the lastest version of RecyclerView. Or use at least
com.android.support:recyclerview-v7:23.2.0
See this link for more info:
https://code.google.com/p/android/issues/detail?id=190526&thanks=190526&ts=1445108573

Now for the important part:
New versions of RecyclerView started to obey the rules of its children (like height and width). You must set your root view in child item XML to:
android:focusable="true"

Now, scrolling will go like it was intended.

Upvotes: 10

Try this. Works for me.

    @Override
public void requestChildFocus(View child, View focused) {
    super.requestChildFocus(child, focused);
    View view = focused;
    while (view != null && view.getParent() != child) {
        try {
            view = (View) view.getParent();
        } catch (ClassCastException e) {
            view = null;
        }
    }
    mSelectedPosition = view == null ? -1 : ((ViewGroup) child).indexOfChild(view);
    if (DEBUG)
        Log.v(TAG, "requestChildFocus focused " + focused + " mSelectedPosition " + mSelectedPosition);
}

Upvotes: 1

Related Questions