后退时片段刷新-Android导航

10
我正在使用Android Navigation组件制作一个应用程序。但我遇到了一个非常基本的问题,可能会在整个应用程序的开发过程中导致问题。
情景描述:
我有一个Fragment,在其中的onViewCreated方法中,我正在观察来自我的ViewModel的一个字段。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel = ViewModelProviders.of(this).get(EventDetailsViewModel::class.java)
    viewModel.init(context!!,eventId)

    viewModel.onEventDetailsUpdated().observe(this, Observer {
        setEventDetails(it)
    })

}

setEventDetails方法中,我使用数据设置了recyclerviews。
问题:
这个片段是一个长滚动的片段。假设我向下滚动到一个部分并点击一个按钮,它会带我到另一个片段。但是当我回到这个片段时,它又把我带到了顶部,并且执行了第一次加载时做的所有操作。这可能会让人困扰。它似乎重新创建了整个片段,而不是保持其旧状态。
我尝试过:
我搜索了很多问题,并查看了这个Github查询这个SO问题另一个Git... 但我无法解决我的问题。
请帮忙,谢谢。

你正在替换片段还是仅添加? - Neha Rathore
朴素解决方案:在onPause中尝试将当前的scrollY保存在ViewModel中,在onResume中滚动到保存的scrollY。 - Ramees Thattarath
@NehaRathore 我正在使用安卓的Jetpack导航组件,因此我正在调用 findNavController().navigate 方法。我不使用片段管理器。 - The Bat
@TheBat,你有解决方案吗?我也遇到了同样的问题,我也在使用NavController。 - Abdul Rafay
@AbdulRafay,你找到任何解决方案了吗? - Pratik Fagadiya
4个回答

3

是的,当您导航到另一个片段时,片段的视图将被销毁。

即使创建了新的RecyclerView实例并设置了新的Adapter实例,只要使用与之前相同的数据集进行设置,RecyclerView的滚动位置应该会自动恢复。此外,您需要在第一次布局传递之前完成设置。

这意味着您需要旧数据,并且需要立即准备好它(不要异步加载!)。ViewModelProvider应返回相同的ViewModel实例。该ViewModel保存了您应该能够同步获取和显示在UI上的数据。确保重构viewModel.init方法——如果数据已经存在,则不要进行API调用以防后退。一个简单的布尔变量isInitialized可以在这里起作用,或者您甚至可以检查LiveData是否为空。

此外,在调用LiveDataobserve时,您有一个微妙的错误。对于同一片段,可以多次调用onViewCreated(每次前进和后退时!)- 因此每次都会调用observe。您的Fragment将订阅同一LiveData多次。这意味着您会多次收到事件(每个订阅一次)。这也可能导致RecyclerView状态恢复出现问题。您的订阅与传递的Lifecycle所有者相关联。您传递了Fragment的Lifecycle所有者,它与Fragment的生命周期相关联。您要做的是传递Fragment视图的Lifecycle所有者,因此每当视图被销毁时,订阅就会被清除,并且您只有1个订阅,仅在Fragment的视图存在时才有。为此,您可以使用getViewLifecycleOwner而不是this

1
你需要依赖于 ViewModel 来恢复片段状态,因为 ViewModel 在片段更改时不会被销毁。 在你的 viewModel 中,创建一个名为 listState 的变量。
class HomeViewModel : ViewModel() {
    var listState: Parcelable? = null
}

然后在你的片段中使用以下代码。

class HomeFragment : Fragment() {
    private val viewModel by navGraphViewModels<HomeViewModel>(R.id.mobile_navigation)

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        if (viewModel.listState != null) {
            list.layoutManager?.onRestoreInstanceState(viewModel.listState)
            viewModel.listState = null
        }else{
           //load data normally
    }

    override fun onDestroyView() {
        super.onDestroyView()
        viewModel.listState = list.layoutManager?.onSaveInstanceState()
    }
}

我能否得到相同的Java代码?我被卡在同样的问题上,而我找到的每个解决方案都是用Kotlin编写的。 - CodeRED Innovations
当我调用viewModel.listState = list.layoutManager?.onSaveInstanceState()时,list变量为null,因为onSaveInstanceState()返回null。 - Mark Lapasa

0

您不必每次都初始化视图模型。只需在初始化之前检查是否为空即可。虽然不知道Kotlin,但它应该类似于:

if(viewModel == null){
    viewModel = ViewModelProviders.of(this).get(EventDetailsViewModel::class.java)
    viewModel.init(context!!,eventId)
}

-1

尝试将此代码放置在您首次调用片段的位置。

ft = fm.beginTransaction();
 ft.replace(R.id.main_fragment, yourSearchFragment, "searchFragment");
 ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
 ft.commit();

当返回到片段时,就会出现这种情况

ft = fm.beginTransaction();
 ft.hide(getFragmentManager().findFragmentByTag("searchFragment"));
 ft.add(R.id.main_fragment, yourDetailfragment);
 ft.addToBackStack(null);
 ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
 ft.commit();

我正在使用Android的Jetpack Navigation组件,因此我调用findNavController().navigate方法。我没有使用片段管理器。 - The Bat

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接