notifyDataSetChanged() 强制在 FragmentStatePagerAdapter 中向上滚动

notifyDataSetChanged() forces scroll up in FragmentStatePagerAdapter

我有一个 FragmentStatePagerAdapter,每秒用他的一些页面的新数据刷新一次。

问题是有些页面内容很多,垂直滚动,所以在调用notifyDataSetChanged()的时候,每秒钟都会强制滚动到他的上层位置。这是一种非常不正常和令人讨厌的行为。

我在 Whosebug 上找到了这个:notifyDataSetChanged() makes the list refresh and scroll jumps back to the top

问题是这些解决方案是为普通 ViewPager 或普通 Adapter 设计的,不能与 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;
    }
}

编辑:

我创建了一个演示项目。这里有一些重要的部分。

  1. 使用 FragmentStatePagerAdapter subclass.

    我们需要一个FragmentStatePagerAdapter基class来保存片段的状态。

  2. onSaveInstanceState()中保存ScrollView的滚动位置,并在(重新)创建片段视图时将滚动位置设置为保存的值。

    现在我们是 saving/restoring 片段状态,我们将滚动位置置于该状态:

        @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 的接口,由片段实现。 activity 提供了 register/unregister 方法:

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

    ... 片段注册和注销 activity:

    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. Return一个索引。因此,如果您 return 2 用于第 0 页,那么 ViewPager 会将页面从 0 移动到 2。

  2. ReturnPOSITION_UNCHANGED。该页面将保持在当前位置。

  3. ReturnPOSITION_NONE。这意味着该页面不应再显示。

一旦 ViewPager 知道页面移动到哪里,它将调用 getItem() 查找页面中的任何空白。

因此,如果您不希望滚动被打扰,请告诉 ViewPager 将页面放在哪里,而不是告诉它删除它并创建一个新页面。