When we develop android TV application, we need to use remote control to control the focus of RecyclerView to show the user which item is currently selected. Inevitably, the following issues will be involved:

  • Set up theitemThe effect of getting focus
  • RecyclerViewOnce you regain focus, select the last oneitem
  • RecyclerViewWhen you lose focus, keep ititemCheck effect of

Let’s tackle these three problems one by one

Set up theitemThe effect of getting focus

Let’s start with the renderings

Just set up the item layout like this,

  • useselectorSet the background
  • Set up theclickableandfocusablefortrue
<! --item.xml-->
<androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="@drawable/item_selector"
        android:clickable="true"
        android:focusable="true">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{item}"
            android:textColor="@android:color/white"
            android:textSize="24sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
Copy the code

item_selector.xml


      
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/select_bg_color" android:state_selected="true" />
    <item android:drawable="@color/select_bg_color" android:state_focused="true" />
    <item android:drawable="@android:color/darker_gray" />
</selector>
Copy the code

Once you regain focus, select the last oneitem

Let’s start with the renderings

The above effect can be achieved by using the VerticalGridView in the Leanback library.

Because VerticalGridView extends BaseGridView extends RecyclerView, the code that used to use RecyclerView basically doesn’t change and doesn’t call setLayoutManager.

Rely on

Implementation "androidx leanback: leanback: 1.0.0."

use

<androidx.leanback.widget.VerticalGridView
        android:id="@+id/vertical_gridview"
        android:layout_width="100dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
Copy the code

When you lose focus, keep ititemCheck effect of

Let’s start with the renderings

When the focus switches between items,

  • Item0 goes to item1, item0 loses focus, item1 gets focus

  • From item1 to item2, item1 loses focus, item2 gains focus

If the focus changes from RecyclerView to another control, Item2 loses focus.

So we record the item that gets focus and loses focus. If the item that gets focus and loses focus is the same item, then RecyclerView loses focus. We need to set this item as selected effect.

class MainAdapter() : ListAdapter<String, RecyclerView.ViewHolder>(MainDiffCallback()) {

    /** Records items that get focus and items that lose focus
    private val map = mutableMapOf<Boolean, String>()
    private var lastSelectedView: View? = null

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return MainViewHolder(
                ItemBinding.inflate(
                        LayoutInflater.from(parent.context),
                        parent,
                        false))}override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val item = getItem(position)
        with(holder as MainViewHolder) {
            itemView.tag = item
            bind(item)
        }
    }

    inner class MainViewHolder(private val binding: ItemBinding) : RecyclerView.ViewHolder(binding.root) {
        init {
            binding.root.setOnFocusChangeListener { view, hasFocus ->
                map[hasFocus] = view.tag as String
                if (map[true] == map[false]) {
                    // The same item gets focus and loses focus. There are two situations:
                    // RecyclerView loses focus
                    // RecyclerView regaining focus
                    // Keep this item selected,
                    view.isSelected = true
                    lastSelectedView = view
                } else{ lastSelectedView? .isSelected =false}}}fun bind(item: String) {
            binding.apply {
                this.item = item
                executePendingBindings()
            }
        }
    }
}
Copy the code

Complete demo

TvRecyclerViewDemo in github