Сохранение состояния прокрутки NestedScrollView

Мое приложение вращается вокруг 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>

Я прочитал несколько сообщений о сохранении состояния экземпляра (этот, этот, например), и их решение либо не работает в моем сценарии, либо непрактично для реализации с учетом У меня есть 4-12 различных фрагментов, которые мне нужно изменить, чтобы заставить его работать.

Каков наилучший способ, чтобы представление вложенной прокрутки сохраняло положение прокрутки при изменении фрагмента?


person Julian Honma    schedule 07.06.2017    source источник


Ответы (4)


Одно решение, которое я нашел на inthecheesefactory, заключается в том, что фрагменты по умолчанию сохранить свое состояние (от ввода в EditText до положения прокрутки), но ТОЛЬКО, если элементу xml присвоен идентификатор.

В моем случае просто добавление идентификатора в мой NestedScrollView устранило проблему:

<?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>
person Julian Honma    schedule 07.06.2017
comment
вы спасли мой вечер :) - person ievgen; 01.03.2018
comment
Такое элегантное решение. ???????????????? - person mohammad reza sarsarabi; 11.10.2019
comment
Ух ты. Я не могу поверить, что это так. Спасибо за решение большой проблемы для меня. - person MobDev; 10.05.2020
comment
Привет, это работа с Navigation Component ? потому что я пробовал, но это не сработало, макет всегда возвращается наверх - person Aldan; 28.07.2020
comment
У меня такая же проблема с навигационным компонентом @Aldan, вы нашли решение? - person Logi24; 21.10.2020
comment
@Logi24 С Navigation Component это невозможно сделать, может быть, это может быть проверка решения эта ссылка - person Aldan; 22.10.2020
comment
Спасибо... Вы сэкономили мне время.... - person Amit Sen; 21.05.2021

Вы можете управлять состоянием экземпляра (включая состояние прокрутки) самостоятельно, сначала сделав соответствующие методы общедоступными:

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_NAMESPACE — это пространство имен класса SaveScrollNestedScrollViewer):

<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()
    }
}
person Florian Moser    schedule 09.08.2019
comment
Привет, я пытался реализовать это на фрагменте, но это не сработало, вы можете помочь? - person Aldan; 28.07.2020
comment
Я не знаю, должен ли этот подход работать и с фрагментами или нет. Я бы порекомендовал найти пример, реализующий это с помощью фрагментов, или подумать о том, чтобы задать новый вопрос. - person Florian Moser; 29.07.2020

Поскольку все ответы устарели, я дам вам новый вариант.

  1. Создайте переменную для хранения вложенного представления прокрутки в вашей модели представления:
class DummyViewModel : ViewModel() {
var estadoNestedSV:Int?=null
}
  1. Переопределите 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!!)
            }
    }

Удачного кодирования!

person Ramiro G.M.    schedule 27.02.2021

Глядя на реализацию NestedScrollView, мы видим, что свойство scrollY NestedScrollView сохраняется в его 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, я бы сохранил положение прокрутки NestedScrollView в моей ViewModel в методе фрагментов onViewDestroyed. Позже вы сможете наблюдать за состоянием через объект 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()
}

Это всего лишь простой пример, но концепция верна.

person Kaylen Travis Pillay    schedule 02.03.2021