我们在开发android tv应用时需要使用遥控器来控制RecyclerView的焦点,来向用户展示当前选中的是哪个item。不可避免的会涉及以下几个问题:
- 设置
item获得焦点时的效果 -
RecyclerView重新获得焦点后,选中上次的item -
RecyclerView失去焦点后,继续保持item的选中效果
下面对这三个问题逐个击破
设置item获得焦点时的效果
先上效果图

获得焦点.gif
只需要按下面这样设置item布局即可,
- 使用
selector设置背景 - 设置
clickable和focusable为true
<!--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
先上效果图

选中上次位置.gif
上面的效果,我们使用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的选中效果
先上效果图

保持选中效果.gif
当焦点在item中切换时,
从
item0到item1,item0失去焦点,item1获得焦点从
item1到item2,item1失去焦点,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()
}
}
}
}