Android TV--RecyclerView中item焦点实战

8,173 阅读2分钟

我们在开发android tv应用时需要使用遥控器来控制RecyclerView的焦点,来向用户展示当前选中的是哪个item。不可避免的会涉及以下几个问题:

  • 设置item获得焦点时的效果
  • RecyclerView重新获得焦点后,选中上次的item
  • RecyclerView失去焦点后,继续保持item的选中效果

下面对这三个问题逐个击破

设置item获得焦点时的效果

先上效果图

只需要按下面这样设置item布局即可,

  • 使用selector设置背景
  • 设置clickablefocusabletrue
<!--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>

item_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<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>

重新获得焦点后,选中上次的item

先上效果图

上面的效果,我们使用Leanback库中的VerticalGridView即可实现。

因为VerticalGridView extends BaseGridView extends RecyclerView,所以之前使用RecyclerView的代码基本不用改变,并且不用调用setLayoutManager

依赖

implementation "androidx.leanback:leanback:1.0.0"

使用

<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" />

失去焦点后,继续保持item的选中效果

先上效果图

当焦点在item中切换时,

  • item0item1item0失去焦点,item1获得焦点

  • item1item2item1失去焦点,item2获得焦点

如果此时,焦点从RecyclerView切换到其他控件,item2失去焦点。

所以我们记录获得焦点和失去焦点的item,如果获得焦点和失去焦点的是同一个item,那么就表示RecyclerView失去了焦点,我们需要设置这个item为选中效果。

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

    /** 记录获得焦点和失去焦点的item */
    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]) {
                    // 获得焦点和失去焦点的是同一个item,会有以下两种情况:
                    //  RecyclerView失去焦点
                    //  RecyclerView重新获得焦点
                    // 让此item保持选中状态,
                    view.isSelected = true
                    lastSelectedView = view
                } else {
                    lastSelectedView?.isSelected = false
                }
            }
        }


        fun bind(item: String) {
            binding.apply {
                this.item = item
                executePendingBindings()
            }
        }
    }
}

完整demo

TvRecyclerViewDemo in github