背景说明
一个Android TV项目,显示一个wifi列表,在监听到wifi状态改变时需要刷新该列表(也就是说该recyclerview可能随时需要刷新数据),实践过程中发现,如果当前控制遥控器焦点滑动停留在wifi列表的某一项,这时候外部条件导致列表自动刷新,会导致我遥控器的焦点丢失(自动跑到我的recyclerview外面的其他可以获取焦点的view上了)
如何解决
核心思路:记住刷新前的焦点位置,刷新列表时将焦点还原
这里有一点细节需要注意,我们要还原前需要判断当前的recyclerview是否包含焦点,如果用户根本没有将焦点滑动到该recyclerview(我们的页面上存在其它view,用户当前并没有将焦点移动到该recyclerview的某个条目),那么我们就不需要将焦点还原
如何记住呢,这里我使用的BRVAH 4.0版本实现adapter,原生adapter是一样的处理
class WifiListAdapter: BaseQuickAdapter<CMWiFiInfo, WifiListAdapter.VH>() {
var focusPosition = -1
class VH(parent: ViewGroup, val binding: AdapterWifiListBinding = AdapterWifiListBinding.inflate(
LayoutInflater.from(parent.context))): RecyclerView.ViewHolder(binding.root)
override fun onBindViewHolder(holder: VH, position: Int, item: CMWiFiInfo?) {
......//其它业务处理
holder.itemView.setOnFocusChangeListener { v, hasFocus ->
if(hasFocus){//在这里我们记录最后获取焦点的位置
focusPosition = holder.absoluteAdapterPosition
}
}
}
override fun onCreateViewHolder(context: Context, parent: ViewGroup, viewType: Int): VH {
return VH(parent)
}
那么在我们需要刷新列表的时候,将焦点还原需要如何处理呢
fun updateWifiList(){
val containsFocus = binding.rvWifi.hasFocus() //这里是判断当前recyclerview是否包含焦点,这个判断一定要做,原因在前面已经说过
mWifiListAdapter.submitList(customWifiManager.getList().filter { !TextUtils.isEmpty(it.SSID) }.sortedBy { it.level }.reversed())//这里主要是排序后刷新列表数据
if(containsFocus){
// binding.rvWifi.findViewHolderForAdapterPosition(lastfocusItemPosition)?.itemView?.requestFocus()
binding.rvWifi.post {//这里为什么要用view.post呢?因为我们应该保证view绘制完成后再进行焦点控制,否则可能导致列表未绘制完成找不到对应的position及position对应的view,造成异常crash
binding.rvWifi.scrollToPosition(mWifiListAdapter.focusPosition)
binding.rvWifi.findViewHolderForAdapterPosition(mWifiListAdapter.focusPosition)?.itemView?.requestFocus()//这里就是将焦点位置还原
}
}
}
上面讨论的实现逻辑我已经在Android12版本的设备上验证正常,需要注意的是这里的逻辑是按照位置还原焦点的,我们上一次刷新列表的第n条可能对应wifi名称是AAA,那么在下一次刷新列表时,第n条可能对应的是AAA也可能是BBB,所以不能保证焦点一直维持在原来数据(AAA)的地方。当然,如果你不想按照位置还原,只想按照数据的位置还原(比如不论怎么刷新,我就是想回到用户上次焦点选中的AAA),我们也可以按照这种上面思路扩展,这里先不讨论,如果有需要再拓展,继续牛马生活地赶项目了