【问题标题】:Saving scroll state of NestedScrollView保存 NestedScrollView 的滚动状态
【发布时间】:2017-11-08 11:29:37
【问题描述】:

我的应用程序围绕着一个 HomeActivity,它在底部包含 4 个选项卡。这些选项卡中的每一个都是一个片段,它们都是从一开始就添加(而不是替换)的,并且在点击相应的选项卡时它们会被隐藏/显示。

我的问题是,每当我更改选项卡时,我的滚动状态都会丢失。显示该问题的每个片段都使用android.support.v4.widget.NestedScrollView(参见下面的示例)。

注意:我使用 RecyclerView 或 ListView 的片段出于某种原因保持滚动状态。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <include layout="@layout/include_appbar_title" />

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- Content -->

    </android.support.v4.widget.NestedScrollView>

</LinearLayout>

我阅读了几篇关于保存实例状态的帖子(例如this onethat one),他们的解决方案要么在我的场景中不起作用,要么在我有 4-12 个不同的片段时不实用我需要修改以使其正常工作。

让嵌套滚动视图在片段更改时保持其滚动位置的最佳方法是什么?

【问题讨论】:

    标签: android savestate android-nestedscrollview


    【解决方案1】:

    我在inthecheesefactory 上找到的一个解决方案是,默认情况下,片段会保存其状态(从 EditText 中的输入到滚动位置),但前提是为 xml 元素提供了 ID。

    就我而言,只需向我的 NestedScrollView 添加一个 ID 即可解决问题:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
    
        <include layout="@layout/include_appbar_title" />
    
        <android.support.v4.widget.NestedScrollView
            android:id="@+id/NestedScrollView"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <!-- Content -->
    
        </android.support.v4.widget.NestedScrollView>
    
    </LinearLayout>
    

    【讨论】:

    • 你拯救了我的夜晚 :)
    • 如此优雅的解决方案。 ????
    • 哇。我不敢相信这是这样的。感谢您为我解决了一个大问题。
    • 嗨,它与Navigation Component 一起工作?因为我试过了但是没用,所以布局总是回到顶部
    • 导航组件有同样的问题@Aldan你找到解决方案了吗?
    【解决方案2】:

    查看 NestedScrollView 的实现,我们看到 NestedScrollView 的 scrollY 属性存储在其 SavedState 作为其保存的滚动位置。

    // Source: NestedScrollView.java
    
    @Override
    protected Parcelable onSaveInstanceState() {
        Parcelable superState = super.onSaveInstanceState();
        SavedState ss = new SavedState(superState);
        ss.scrollPosition = getScrollY();
        return ss;
    }
    

    因此,我同意Ramiro G.M. 在配置更改时保留滚动位置的想法。在这种情况下,我认为不需要 NestedScrollView 的子类。

    如果您使用的是 Fragment 和 MVVM,那么我会在 Fragment onViewDestroyed 方法中将 NestedScrollView 的滚动位置保存到我的 ViewModel 中。您可以稍后在创建片段视图时通过 LiveData 对象观察状态。

    override fun onViewCreated(...) {
        mViewModel.scrollState.observe(viewLifecycleOwner, { scrollState ->
             binding.myNestedScrollView.scrollY = scrollState
        })
    }
    
    override fun onDestroyView() {
        val scrollState = binding.myNestedScrollView.scrollY
        mViewModel.setScrollState(scrollState)
        super.onDestroyView()
    }
    

    这只是一个简单的例子,但这个概念是正确的。

    【讨论】:

      【解决方案3】:

      由于现在所有答案都已弃用,我将为大家提供一个新选项。

      1. 创建一个变量来保存视图模型上的嵌套滚动视图:
      class DummyViewModel : ViewModel() {
      var estadoNestedSV:Int?=null
      }
      
      1. 在 Fragment 上覆盖 onStop 以保存嵌套滚动视图被破坏之前的状态:
      override fun onStop() {
              try {
                  super.onStop()
                  viewModel.estadoNestedSV = binding.nestedSV.scrollY
              } catch (e: Exception) {
                  Log.i((activity as MainActivity).constantes.TAG_GENERAL, e.message!!)
              }
          }
      
      1. 通过覆盖 onViewCreated 在片段上创建视图后恢复状态:
      override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
              try {
                 //Check first if data exists to know if this load is a first time or if the device was rotated.
                 if(viewModel.data.value != null)
                 binding.nestedSVPelisDetalles.scrollY = viewModel.estadoNestedSV!!
                  } catch (e: Exception) {
                  Log.i((activity as MainActivity).constantes.TAG_GENERAL, e.message!!)
                  }
          }
      

      编码愉快!

      【讨论】:

      • 嗨,这是有效的,但是当我滚动到滚动视图的末尾并转到另一个片段并返回带有滚动视图的片段时,滚动位置正在向上跳跃。有解决方案吗?谢谢。
      • Shafayat,有时 scrollY 变量需要时间来保存其状态...更好的方法可能是保存 recyclerview 的布局管理器的整个状态(包括滚动状态),然后在需要。只需在视图模型中保存从 myRecyclerView.LinearLayoutManager.onSaveInstanceState() 获得的 Parcelable 并使用 myRecyclerView.LinearLayoutManager.onRestoreInstanceState(Parcelable state) 恢复它
      【解决方案4】:

      您可以自己管理实例状态(包括滚动状态),方法是先公开相应的方法:

      class SaveScrollNestedScrollViewer : NestedScrollView {
          constructor(context: Context) : super(context)
      
          constructor(context: Context, attributes: AttributeSet) : super(context, attributes)
      
          constructor(context: Context, attributes: AttributeSet, defStyleAttr: Int) : super(context, attributes, defStyleAttr)
      
      
          public override fun onSaveInstanceState(): Parcelable? {
              return super.onSaveInstanceState()
          }
      
          public override fun onRestoreInstanceState(state: Parcelable?) {
              super.onRestoreInstanceState(state)
          }
      }
      

      然后在你的视图中使用它(YOUR_NAMESPACESaveScrollNestedScrollViewer 类的命名空间):

      <YOUR_NAMESPACE.SaveScrollNestedScrollViewer
           android:id="@+id/my_scroll_viewer"
           android:layout_width="match_parent"
           android:layout_height="match_parent">
      </YOUR_NAMESPACE.SaveScrollNestedScrollViewer>
      

      然后在显示它的活动中,根据需要保存/恢复状态。例如,如果您想在导航离开后恢复滚动位置,请使用以下命令:

      class MyActivity : AppCompatActivity() {
      
          companion object {
              var myScrollViewerInstanceState: Parcelable? = null
          }
      
          override fun onCreate(savedInstanceState: Bundle?) {
              super.onCreate(savedInstanceState)
              setContentView(R.layout.my_activity)
      
              if (myScrollViewerInstanceState != null) {
                  my_scroll_viewer.onRestoreInstanceState(myScrollViewerInstanceState)
              }
          }
      
          public override fun onPause() {
              super.onPause()
              myScrollViewerInstanceState = my_scroll_viewer.onSaveInstanceState()
          }
      }
      

      【讨论】:

      • 您好,我尝试在片段上实现此功能,但没有成功,您能帮忙吗?
      • 我不知道这种方法是否也适用于片段。我建议寻找一个使用片段实现此功能的示例,或者考虑提出一个新问题。
      猜你喜欢
      • 2018-12-09
      • 1970-01-01
      • 1970-01-01
      • 2018-06-26
      • 1970-01-01
      • 2020-05-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多