【问题标题】:Fragment loses listener at orientation change片段在方向改变时失去了听众
【发布时间】:2019-07-15 09:45:55
【问题描述】:

我有一个使用片段的活动。为了从片段到活动进行通信,我使用接口。这是简化的代码:

活动:

class HomeActivity : AppCompatActivity(), DiaryFragment.IAddEntryClickedListener, DiaryFragment.IDeleteClickedListener {

    override fun onAddEntryClicked() {
        //DO something
    }

    override fun onEntryDeleteClicked(isDeleteSet: Boolean) {
        //Do something
    }

    private val diaryFragment: DiaryFragment = DiaryFragment()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_home)

        diaryFragment.setOnEntryClickedListener(this)
        diaryFragment.setOnDeleteClickedListener(this)

        supportFragmentManager.beginTransaction().replace(R.id.content_frame, diaryFragment)
    }
}

片段:

class DiaryFragment: Fragment() {

    private var onEntryClickedListener: IAddEntryClickedListener? = null
    private var onDeleteClickedListener: IDeleteClickedListener? = null


    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view: View = inflater.inflate(R.layout.fragment_diary, container, false)

        //Some user interaction
        onDeleteClickedListener!!.onEntryDeleteClicked()
        onDeleteClickedListener!!.onEntryDeleteClicked()

        return view
    }

    interface IAddEntryClickedListener {
        fun onAddEntryClicked()
    }

    interface IDeleteClickedListener {
        fun onEntryDeleteClicked()
    }

    fun setOnEntryClickedListener(listener: IAddEntryClickedListener) {
        onEntryClickedListener = listener
    }

    fun setOnDeleteClickedListener(listener: IDeleteClickedListener) {
        onDeleteClickedListener = listener
    }
}

这可行,但是当片段处于​​活动状态并且方向从纵向更改为横向或其他方式时,侦听器为空。我不能将它们放入已保存的InstanceState,或者我可以以某种方式吗?还是有其他方法可以解决这个问题?

【问题讨论】:

  • “我无法将它们放入 savedInstanceState”——正确。 “或者有其他方法可以解决这个问题吗?” -- 现代解决方案是使用共享的ViewModelLiveData。片段会将事件发布到活动将观察到的LiveData。 Kotlin 风格的遗留解决方案是将 requireActivity() 转换为您的接口并在其上调用函数,因为您的活动是实现接口的位置。
  • @CommonsWare 如果我的听众引用了上下文怎么办?据我了解,共享 ViewModel 成员不应直接或间接引用上下文。
  • @Neoh:如果您使用的是 Jetpack ViewModel,请使用 AndroidViewModel 访问 Application 单例。

标签: android fragment


【解决方案1】:

您的问题:

当您切换方向时,系统会为您保存和恢复片段的状态。但是,您没有在代码中考虑这一点,实际上您最终得到了片段的两个 (!!) 实例 - 一个由系统恢复(没有侦听器),另一个由您自己创建。当您观察到该片段的监听器为空时,这是因为为您恢复的实例尚未重置其监听器。

解决方案

首先,阅读the docs on how you should structure your code。 然后将您的代码更新为以下内容:

class HomeActivity : AppCompatActivity(), DiaryFragment.IAddEntryClickedListener, DiaryFragment.IDeleteClickedListener {

    override fun onAddEntryClicked() {
        //DO something
    }

    override fun onEntryDeleteClicked(isDeleteSet: Boolean) {
        //Do something
    }

    // DO NOT create new instance - only if starting from scratch
    private lateinit val diaryFragment: DiaryFragment

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_home)

        // Null state bundle means fresh activity - create the fragment
        if (savedInstanceState == null) {
            diaryFragment = DiaryFragment()
            supportFragmentManager.beginTransaction().replace(R.id.content_frame, diaryFragment)
        }
        else { // We are being restarted from state - the system will have
               // restored the fragment for us, just find the reference
            diaryFragment = supportFragmentManager().findFragment(R.id.content_frame)
        }

        // Now you can access the ONE fragment and set the listener on it
        diaryFragment.setOnEntryClickedListener(this)
        diaryFragment.setOnDeleteClickedListener(this)        
    }
}

希望有帮助!

【讨论】:

    【解决方案2】:

    不重写代码的简短答案是,您必须在活动恢复时恢复侦听器,并且当您检测到活动失去焦点时,您“应该”删除它们。活动视图被完全销毁并在旋转时重新绘制,因此自然不会在全新对象上发生任何事件。

    当您旋转时,“onDestroy”会在其他任何事情发生之前被调用。当它被重建时,“onCreate”被调用。 (见https://developer.android.com/guide/topics/resources/runtime-changes) 以这种方式完成的原因之一是没有任何东西强迫您在旋转后甚至使用相同的布局。可能有不同的控制。 您真正需要做的就是确保在 OnCreate 中分配了您的事件挂钩。 有关在 oncreate 中分配事件的示例,请参阅此问题的答案。 onSaveInstanceState not working

    【讨论】:

    • 您能再解释一下吗?我是一个新手。 “恢复”和“删除”到底是什么意思?
    • 我会编辑它以获取更多信息。这些 cmets 不能很长。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-22
    相关资源
    最近更新 更多