notifyDataSetChanged() принудительно прокручивает вверх в FragmentStatePagerAdapter

У меня есть FragmentStatePagerAdapter, который обновляется каждую секунду новыми данными для некоторых его страниц.

Проблема в том, что на некоторых страницах много контента и вертикальная прокрутка, поэтому каждую секунду при вызове notifyDataSetChanged() прокрутка принудительно перемещается в верхнюю позицию. это очень ненормальное и раздражающее поведение.

Я нахожу это в stackoverflow: ) обновляет список и прокручивает вверх

Проблема в том, что эти решения предназначены для обычного ViewPager или обычного адаптера и не могут работать с FragmentStatePageAdapter или чем-то еще, потому что после их попытки у меня все еще есть та же проблема.

Это мой адаптер:

public class CollectionPagerAdapter extends FragmentStatePagerAdapter {
    public CollectionPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int i) {
        Fragment fragment = new ObjectFragment();
        Bundle args = new Bundle();
        args.putString(ObjectFragment.ARG_TEXT, children[i]);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public int getCount() {
        return infoTitlesArray.length;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return infoTitlesArray[position];
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }
}

ViewPager, у которого есть проблема:

<android.support.v4.view.ViewPager
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v4.view.PagerTitleStrip
            android:id="@+id/pager_title_strip"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="top"
            android:paddingTop="4dp"
            android:paddingBottom="4dp"
            style="@style/CustomPagerTitleStrip"/>
    </android.support.v4.view.ViewPager>

Макет фрагмента:

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbarSize="5dip"
    style="@style/CustomScrollBar">
    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="left"
        android:textSize="16sp"
        android:padding="5dp"
        style="@style/CustomTextView"/>
</ScrollView>

Код Java для фрагмента:

public static class ObjectFragment extends Fragment {
    public static final String ARG_TEXT = "object";

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_collection_object, container, false);
        Bundle args = getArguments();
        TextView tv = ((TextView) rootView.findViewById(R.id.text));
        tv.setText(Html.fromHtml(args.getString(ARG_TEXT)));
        return rootView;
    }
}

person NullPointerException    schedule 12.08.2016    source источник
comment
Если вы испытываете такое же поведение без просмотра пейджера, проблема может быть во фрагменте, содержащем список.   -  person Akshay Bhat 'AB'    schedule 12.08.2016
comment
и какая проблема может быть?   -  person NullPointerException    schedule 12.08.2016
comment
Не глядя на код, сложно сказать. Я думаю Адаптер.   -  person Akshay Bhat 'AB'    schedule 12.08.2016
comment
переходник есть в моем вопросе, вы его видели?   -  person NullPointerException    schedule 12.08.2016
comment
Я имею в виду адаптер списка. или может быть в макете, если он сложный.   -  person Akshay Bhat 'AB'    schedule 12.08.2016
comment
не сложный, это простой просмотрщик с pagerTitleStrip, теперь он добавлен в вопрос   -  person NullPointerException    schedule 12.08.2016
comment
Макет вашего фрагмента, а не viewpager.   -  person Akshay Bhat 'AB'    schedule 12.08.2016
comment
добавил...... но это очень просто   -  person NullPointerException    schedule 12.08.2016
comment
Где находится listview/recyclerview в макете фрагмента? Вы указали в вопросе, что звоните notifyDatasetChanged().   -  person Akshay Bhat 'AB'    schedule 12.08.2016
comment
нет, есть в некоторых потоках моего кода, вызывается каждую секунду, потому что мой массив содержимого (массив строк) обновляется. Этот массив контента используется для заполнения фрагментов окна просмотра.   -  person NullPointerException    schedule 12.08.2016


Ответы (1)


РЕДАКТИРОВАТЬ:

Я создал демонстрационный проект. Вот несколько важных фрагментов.

  1. Используйте подкласс FragmentStatePagerAdapter.

    Нам нужен базовый класс FragmentStatePagerAdapter, чтобы сохранить состояние фрагмента.

  2. Сохраните положение прокрутки ScrollView в onSaveInstanceState() и установите для положения прокрутки сохраненное значение при (повторном) создании представления фрагмента.

    Теперь, когда мы сохраняем/восстанавливаем состояние фрагмента, мы помещаем позицию прокрутки в это состояние:

        @Override
        public void onSaveInstanceState(Bundle outState) {
            int scrollY = scrollView.getScrollY();
            outState.putInt("scrollY", scrollY);
            super.onSaveInstanceState(outState);
        }
    

    и восстановить его в onCreateView():

            if (savedInstanceState != null) {
                final int scrollY = savedInstanceState.getInt("scrollY");
                scrollView.post(new Runnable() {
                    @Override
                    public void run() {
                        scrollView.setScrollY(scrollY);
                    }
                });
            }
    
  3. Настройте систему уведомлений прослушивателя для обновлений.

    У нас есть интерфейс с именем DataUpdateListener, который реализуется фрагментом. Активность предоставляет методы регистрации/отмены регистрации:

    public void addDataUpdateListener(DataUpdateListener listener) {
        mListenerMap.put(listener.getPage(), listener);
    }
    
    public void removeDataUpdateListener(DataUpdateListener listener) {
        mListenerMap.remove(listener.getPage());
    }
    

    ... и фрагмент регистрируется и отменяет регистрацию с помощью действия:

    in onCreateView():

        ((MainActivity) getActivity()).addDataUpdateListener(this);
    

    также

        @Override
        public void onDestroyView() {
            ((MainActivity) getActivity()).removeDataUpdateListener(this);
            super.onDestroyView();
        }
    

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

            for (int i = 0; i < children.length; i++) {
                notifyUpdateListener(i, children[i]);
            }
    

Обратите внимание, что нигде в коде не вызывается onNotifyDataSetChanged() в адаптере пейджера просмотра.

Демо находится на GitHub по адресу https://github.com/klarson2/View-Pager-Data-Update


Вот что вызывает прокрутку:

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

Когда вы звоните notifyDataSetChanged(), ViewPager спрашивает вас, что делать с уже имеющимися страницами.

Поэтому он позвонит getItemPosition, чтобы узнать: куда должна идти эта страница? У вас есть три варианта ответа:

  1. Вернуть индекс. Итак, если вы вернете 2 для страницы 0, то ViewPager переместит страницу с 0 на 2.

  2. Вернуть POSITION_UNCHANGED. Страница останется именно там, где она сейчас.

  3. Возврат POSITION_NONE. Это означает, что страница больше не должна отображаться.

Как только ViewPager узнает, куда перемещаются страницы, он будет вызывать getItem() для любых пробелов на страницах.

Так что, если вы не хотите, чтобы прокрутка мешалась, скажите ViewPager, куда поместить страницу, вместо того, чтобы попросить избавиться от нее и создать новую.

person kris larson    schedule 12.08.2016
comment
Поэтому, если вы не хотите, чтобы прокрутка была нарушена, сообщите ViewPager, куда поместить страницу, вместо того, чтобы сообщать ему избавиться от нее и создать новую. Я совершенно сбит с толку. ¿сообщить программе просмотра, куда поместить страницу? Если вы можете показать мне пример кода, я его пойму, но теперь я полностью запутался в вашем ответе, извините. Я просто хочу обновить содержимое фрагментов моего пейджера. Только то. Не перемещать страницы.. я вас не понимаю - person NullPointerException; 12.08.2016
comment
Обновил мой ответ. Извините за путаницу. Не вызывайте notifyDataSetChanged на PagerAdapter, сделайте это на адаптере внутри фрагмента, который вы хотите обновить. - person kris larson; 12.08.2016
comment
mmmm Вызовите notifyDataSetChanged() на адаптере внутри фрагмента, который вы хотите обновить. Хорошо, это звучит красиво, но... как мне попасть внутрь фрагмента, который я хочу обновить? - person NullPointerException; 12.08.2016
comment
Кроме того, notifyDataSetChanged() относится к классу адаптера, не может быть вызвана из фрагмента... так что я думаю, что я вас в чем-то не понимаю - person NullPointerException; 12.08.2016
comment
Вы сказали, что на вашей странице много контента и вертикальная прокрутка, поэтому я понял, что это означает некое представление списка с адаптером. Пожалуйста, выложите код из вашего фрагмента. Фрагмент, вероятно, просто проверяет данные в onCreateView. Должен быть способ обновить данные без воссоздания всего представления фрагмента. - person kris larson; 12.08.2016
comment
Опубликовано, проверьте нижнюю часть моего вопроса. Это очень простой фрагмент, просто текстовое представление. Вы можете увидеть java-код и xml-код этого фрагмента. Текстовое представление находится внутри прокрутки, потому что текст очень длинный. - person NullPointerException; 12.08.2016
comment
Хорошо, отлично. Итак, теперь я вижу, что данные заданы в аргументе фрагмента. Когда данные обновляются, меняется ли размер children или infoTitlesArray? - person kris larson; 12.08.2016
comment
нет, размер этого массива всегда одинаков, меняется только содержимое 11 элементов массива - person NullPointerException; 12.08.2016
comment
Я собираюсь поработать над образцом приложения для вас и опубликовать его на GitHub. - person kris larson; 12.08.2016
comment
Могу ли я предположить, что вы заинтересованы в сохранении положения прокрутки только в том случае, если данные не меняются, и если данные меняются, то все в порядке, если прокрутка возвращается наверх? - person kris larson; 13.08.2016
comment
Нет, свиток должен сохранять свою позицию в обоих случаях, с измененными данными и с сохранением данных. Также есть еще одно ненормальное поведение: если вы пролистываете страницы, когда вызывается функция notifyDataSet, то вы теряете движение пролистывания. Это похоже на проблему прокрутки. - person NullPointerException; 13.08.2016
comment
у вас было что-то еще? если вы хотите, чтобы вы разместили здесь вместо создания github. - person NullPointerException; 13.08.2016
comment
Большое спасибо! кажется, он работает в вашем образце. Я ценю ваши усилия и принимаю ваш ответ (сегодня ночью я постараюсь добавить вашу стратегию в свой код). Только одно думаю больше. Ваше решение работает отлично, и код кажется очень чистым, но это небольшая стратегия взлома, и я не уверен, что это лучший способ добиться этого. Есть ли другие способы добиться этого замечательного поведения обновления содержимого фрагментов внутри пейджера без нарушения прокрутки и движений смахивания? Или это единственный способ и лучший способ добиться этого? - person NullPointerException; 14.08.2016
comment
Один вопрос. Поскольку вы сохраняете свиток в методе onsavedinstance и восстанавливаете его в oncreateview, я думаю, что фрагмент уничтожается и воссоздается... но... почему? Я не вижу никакого кода для отправки сигнала уничтожения фрагменту. Метод onDataUpdated только изменяет TextView, но не уничтожает и не воссоздает фрагмент. - person NullPointerException; 14.08.2016
comment
Принцип работы ViewPager заключается в том, что он создает и уничтожает представления по мере того, как вы проводите пальцем по экрану, так что остается только три вида: тот, который вы видите, тот, что слева, и тот, что справа. (с setOffscreenPageLimit(1) по умолчанию.) Когда вы проводите, скажем, со страницы 2 на страницу 3, представление на странице 1 уничтожается и создается представление на странице 4. Вот почему вы должны сохранить позицию прокрутки, поэтому, когда вы прокручиваете страницу 1, ScrollView находится в том же месте. Вы можете увидеть это в исходном коде для FragmentSatePagerAdapter. - person kris larson; 14.08.2016
comment
И поскольку представления для фрагментов создаются и уничтожаются все время, когда пользователь проводит пальцем, лучший способ справиться с частыми обновлениями данных — это стратегия публикации/подписки, при которой фрагменты подписываются и отписываются от активности по мере того, как их представления создается и уничтожается. Затем, когда действие получает изменение данных, оно публикует это изменение для всех фрагментов, на которые подписано в это время. Таким образом, хотя сам код может быть немного хакерским, он показывает правильные принципы проектирования. - person kris larson; 14.08.2016
comment
Замечательное объяснение и пример кода. Большое спасибо! - person NullPointerException; 14.08.2016