调用notifyDataSetChanged后EditText失去焦点的解决方案求助
问题根因
你现有代码存在两个核心问题导致焦点恢复失败:
- 每次更新列表时直接清空所有数据、创建全新的
TestDataObject实例,之前保存在旧对象里的焦点状态完全丢失,自然无法恢复焦点。 - 全量调用
notifyDataSetChanged会触发所有item重绘,系统会主动清除所有子View的焦点,导致你设置的焦点监听器只能拿到false的状态值。
最优解决方案:使用DiffUtil做局部刷新
该方案不需要额外处理焦点逻辑,只要不刷新持有焦点的item,焦点就不会丢失,同时大幅提升列表刷新性能。
步骤1:改造数据类和比对规则
给TestDataObject新增唯一标识id,用于DiffUtil比对item:
data class TestDataObject( val id: Int, // 每个item的id固定不变 val titleText: String = "", val infoText: String = "", var fieldHasFocus: Boolean = false )
新增DiffUtil比对规则:
class TestDataDiffCallback : DiffUtil.ItemCallback<TestDataObject>() { override fun areItemsTheSame(oldItem: TestDataObject, newItem: TestDataObject): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: TestDataObject, newItem: TestDataObject): Boolean { // 仅内容变化时才刷新item,焦点状态变更不触发刷新 return oldItem.titleText == newItem.titleText && oldItem.infoText == newItem.infoText } }
步骤2:改造适配器继承ListAdapter
ListAdapter会自动根据DiffUtil比对结果做局部刷新,不需要手动调用notify系列方法:
class ViewPagerAdapter : ListAdapter<TestDataObject, ViewPagerAdapter.TestHolder>(TestDataDiffCallback()) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.holder_test, parent, false) val binding: HolderTestBinding = DataBindingUtil.bind(view)!! return TestHolder(binding) } override fun onBindViewHolder(holder: TestHolder, position: Int) { holder.bind(getItem(position)) } inner class TestHolder(private val binding: HolderTestBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(testDataObject: TestDataObject) { binding.apply { titleTv.text = testDataObject.titleText textTv.text = testDataObject.infoText editText.setOnFocusChangeListener { _, focus -> testDataObject.fieldHasFocus = focus } } } } }
步骤3:改造数据更新逻辑
更新列表时要迁移旧列表的焦点状态到新对象上,避免状态丢失:
private fun setDataToList() { val oldList = testList.toList() testList.clear() repeat(6) { id -> // 迁移旧的焦点状态 val oldFocusState = oldList.find { it.id == id }?.fieldHasFocus ?: false testList.add( TestDataObject( id = id, titleText = "title for page $id ${Random.nextInt()}", infoText = "infoText for page $id ${Random.nextInt()}", fieldHasFocus = oldFocusState ) ) } } // 数据更新时调用submitList触发局部刷新 _testLiveData.observeEvent(this) { adapter?.submitList(it) constantlyUpdateList() }
兼容方案:必须全量刷新时的处理逻辑
如果业务场景必须使用notifyDataSetChanged全量刷新,可以通过主动记录焦点位置、布局完成后再恢复焦点的方式实现:
改造适配器代码
class ViewPagerAdapter(var list: List<TestDataObject>) : RecyclerView.Adapter<ViewPagerAdapter.TestHolder>() { // 记录持有焦点的item对应id private var focusItemId: Int = -1 override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TestHolder { val view = LayoutInflater.from(parent.context) .inflate(R.layout.holder_test, parent, false) val binding: HolderTestBinding = DataBindingUtil.bind(view)!! return TestHolder(binding) } override fun onBindViewHolder(holder: TestHolder, position: Int) { val item = list[position] holder.bind(item, item.id == focusItemId) } override fun getItemCount(): Int = list.size fun updateData(items: List<TestDataObject>) { // 更新数据前先同步焦点状态 val currentFocusItem = list.find { it.id == focusItemId } focusItemId = items.find { it.id == currentFocusItem?.id }?.id ?: -1 list = items notifyDataSetChanged() } inner class TestHolder(private val binding: HolderTestBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(testDataObject: TestDataObject, needFocus: Boolean) { binding.apply { titleTv.text = testDataObject.titleText textTv.text = testDataObject.infoText // 实时记录持有焦点的item id editText.setOnFocusChangeListener { _, focus -> if (focus) focusItemId = testDataObject.id } if (needFocus) { // 等待布局绘制完成后再申请焦点,比固定延迟可靠 editText.post { editText.requestFocus() // 可选:自动弹出软键盘 val imm = editText.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT) } } } } } }
内容的提问来源于stack exchange,提问作者ReaverKres
相关产品推荐
相关产品推荐

