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;
}
}
编辑:
我创建了一个演示项目。这里有一些重要的部分。
使用 FragmentStatePagerAdapter
subclass.
我们需要一个FragmentStatePagerAdapter
基class来保存片段的状态。
在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);
}
});
}
为更新设置侦听器通知系统。
我们有一个名为 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
来找出:这个页面应该去哪里?您有三种回复选项:
Return一个索引。因此,如果您 return 2 用于第 0 页,那么 ViewPager
会将页面从 0 移动到 2。
ReturnPOSITION_UNCHANGED。该页面将保持在当前位置。
ReturnPOSITION_NONE。这意味着该页面不应再显示。
一旦 ViewPager
知道页面移动到哪里,它将调用 getItem()
查找页面中的任何空白。
因此,如果您不希望滚动被打扰,请告诉 ViewPager
将页面放在哪里,而不是告诉它删除它并创建一个新页面。
我有一个 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;
}
}
编辑:
我创建了一个演示项目。这里有一些重要的部分。
使用
FragmentStatePagerAdapter
subclass.我们需要一个
FragmentStatePagerAdapter
基class来保存片段的状态。在
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); } }); }
为更新设置侦听器通知系统。
我们有一个名为
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
来找出:这个页面应该去哪里?您有三种回复选项:
Return一个索引。因此,如果您 return 2 用于第 0 页,那么
ViewPager
会将页面从 0 移动到 2。ReturnPOSITION_UNCHANGED。该页面将保持在当前位置。
ReturnPOSITION_NONE。这意味着该页面不应再显示。
一旦 ViewPager
知道页面移动到哪里,它将调用 getItem()
查找页面中的任何空白。
因此,如果您不希望滚动被打扰,请告诉 ViewPager
将页面放在哪里,而不是告诉它删除它并创建一个新页面。