FragmentStatePagerAdapter with ListView - 如何在不向上滚动的情况下更新列表视图的内容?
FragmentStatePagerAdapter with ListView - how to update content of the listview without scroll up?
我在每个页面中都有一个带有 Listview 的 FragmentStatePagerAdapter。 FragmentStatePagerAdapter 每秒用一些页面的新数据刷新一次(记住每个页面都是一个列表视图)。
我用 setAdapter 设置列表视图的内容:
StableArrayAdapter adapter = new StableArrayAdapter((MainActivity) getActivity(), R.layout.list_view_item, ((MainActivity) getActivity()).getData(mPage));
listView.setAdapter(adapter);
问题是有些页面的内容很多,垂直滚动,所以在调用listView.setAdapter(adapter);
的时候每一秒,listview都会强制滚动到他上面的位置。这是一种非常不正常和令人讨厌的行为。
我在 Stack Overflow 上找到了这个:notifyDataSetChanged() makes the list refresh and scroll jumps back to the top
问题是这些解决方案是为普通 ViewPager 或普通 Adapter 设计的,不能与 FragmentStatePageAdapter 或其他东西一起使用,因为在尝试它们之后我仍然遇到同样的问题。
这是我的 FragmentStatePagerAdapter 适配器:
public class CollectionPagerAdapter extends FragmentStatePagerAdapter {
public CollectionPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return ObjectFragment.newInstance(position);
}
@Override
public int getCount() {
return infoTitlesArray.length;
}
@Override
public CharSequence getPageTitle(int position) {
return infoTitlesArray[position];
}
}
出现问题的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>
片段布局:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/listview"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ListView>
片段的Java代码:
public static class ObjectFragment extends Fragment implements DataUpdateListener {
private int mPage;
private ListView listView;
public static ObjectFragment newInstance(int page) {
ObjectFragment objectFragment = new ObjectFragment();
Bundle args = new Bundle();
args.putInt("page", page);
objectFragment.setArguments(args);
return objectFragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPage = getArguments().getInt("page");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_collection_object, container, false);
((MainActivity) getActivity()).addDataUpdateListener(this);
listView = (ListView) rootView;
StableArrayAdapter adapter = new StableArrayAdapter((MainActivity) getActivity(), R.layout.list_view_item, ((MainActivity) getActivity()).getData(mPage));
listView.setAdapter(adapter);
return rootView;
}
@Override
public void onDestroyView() {
((MainActivity) getActivity()).removeDataUpdateListener(this);
super.onDestroyView();
}
//DataUpdateListener interface methods
@Override
public int getPage() {
return mPage;
}
@Override
public void onDataUpdated(ArrayList<String> data) {
StableArrayAdapter adapter = new StableArrayAdapter((MainActivity) getActivity(), R.layout.list_view_item, data);
listView.setAdapter(adapter);
}
}
当需要更新片段(列表视图)的内容时,使用参数中接收到的新内容调用方法onDataUpdated。
编辑
使用 Budius 中的代码示例添加生成崩溃的适配器:
private static class StableArrayAdapter extends ArrayAdapter<String> {
HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();
public StableArrayAdapter(Context context, int textViewResourceId, List<String> objects) {
super(context, textViewResourceId, objects);
for (int i = 0; i < objects.size(); ++i) {
mIdMap.put(objects.get(i), i);
}
}
public void setData(List<String> objects){
mIdMap.clear();
for (int i = 0; i < objects.size(); ++i) {
mIdMap.put(objects.get(i), i);
}
}
@Override
public long getItemId(int position) {
String item = getItem(position);
return mIdMap.get(item);
}
@Override
public boolean hasStableIds() {
return true;
}
}
编辑 2
尝试使用 Budius 解决方案的新适配器。但它不起作用。内容未刷新。
private static class StableArrayAdapter extends ArrayAdapter<String> {
List<String> objects;
public StableArrayAdapter(Context context, int textViewResourceId, List<String> objects) {
super(context, textViewResourceId, objects);
this.objects = new ArrayList<>();
this.objects.addAll(objects);
}
public void setData(List<String> objects){
this.objects.clear();
this.objects.addAll(objects);
}
@Override
public long getItemId(int position) {
return Math.abs(objects.get(position).hashCode());
}
@Override
public boolean hasStableIds() {
return true;
}
}
调用 setAdapter(adapter)
并将列表 return 调到顶部是正常的 "expected" 行为。毕竟是新适配器,框架不应该假设新适配器与旧适配器有任何关系。
这么说,你的解决方案应该还是围绕notifyDataSetChanged()
和stableIds
的适当用法。考虑到这一点,您应该做的只是简单地切换 ArrayList<String> data
而不是创建一个新的。像这样:
@Override
public void onDataUpdated(ArrayList<String> data) {
adapter.setData(data); // internally replaces the old data with this new one
adapter.notifyDataSetChanged();
}
然后在您实现 StableArrayAdapter 时,您必须将 hasStableIds
覆盖为 return true 并将 getItemId
覆盖为 return a meaningfull 值。考虑到您的数据只是字符串,这有点棘手,但像 Math.abs(value.hashCode)
应该足够了。
PS.: 我强烈建议使用 RecyclerView
而不是 ListView
。我个人不明白为什么 Google 还没有弃用 ListView。
编辑:
根据评论,有些像这样:
public class StableArrayAdapter extends /* whatever you're extending */ {
... your code as usual
@Override public boolean hasStableIds() { return true; }
@Override public long getItemId(int position) {
return Math.abs(data.get(position).hashCode());
}
}
您告诉系统 ID
与您获得的数据直接相关,并且您 returning ID 连接到您的数据(即使连接松散)。也就是说,一个数据一个ID,另一个数据一个ID。
这样通知 ListView 知道根据 ID 保持位置。
编辑 2:
我发现了你的错误。您的适配器扩展自 ArrayAdapter
。这在内部已经有一个 List<String>
,当您调用 getItem(position);
时,数据来自该内部列表。
修复很简单。
- 删除
List<String> objects;
并使用内部列表。
要更新您从 ArrayAdapter (https://developer.android.com/reference/android/widget/ArrayAdapter.html) 调用 add
、addAll
和 clear
方法的数据:
类似于:
@Override
public void onDataUpdated(ArrayList<String> data) {
adapter.clear();
adapter.addAll(data);
adapter.notifyDataSetChanged();
}
我在每个页面中都有一个带有 Listview 的 FragmentStatePagerAdapter。 FragmentStatePagerAdapter 每秒用一些页面的新数据刷新一次(记住每个页面都是一个列表视图)。
我用 setAdapter 设置列表视图的内容:
StableArrayAdapter adapter = new StableArrayAdapter((MainActivity) getActivity(), R.layout.list_view_item, ((MainActivity) getActivity()).getData(mPage));
listView.setAdapter(adapter);
问题是有些页面的内容很多,垂直滚动,所以在调用listView.setAdapter(adapter);
的时候每一秒,listview都会强制滚动到他上面的位置。这是一种非常不正常和令人讨厌的行为。
我在 Stack Overflow 上找到了这个:notifyDataSetChanged() makes the list refresh and scroll jumps back to the top
问题是这些解决方案是为普通 ViewPager 或普通 Adapter 设计的,不能与 FragmentStatePageAdapter 或其他东西一起使用,因为在尝试它们之后我仍然遇到同样的问题。
这是我的 FragmentStatePagerAdapter 适配器:
public class CollectionPagerAdapter extends FragmentStatePagerAdapter {
public CollectionPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
return ObjectFragment.newInstance(position);
}
@Override
public int getCount() {
return infoTitlesArray.length;
}
@Override
public CharSequence getPageTitle(int position) {
return infoTitlesArray[position];
}
}
出现问题的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>
片段布局:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/listview"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</ListView>
片段的Java代码:
public static class ObjectFragment extends Fragment implements DataUpdateListener {
private int mPage;
private ListView listView;
public static ObjectFragment newInstance(int page) {
ObjectFragment objectFragment = new ObjectFragment();
Bundle args = new Bundle();
args.putInt("page", page);
objectFragment.setArguments(args);
return objectFragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPage = getArguments().getInt("page");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_collection_object, container, false);
((MainActivity) getActivity()).addDataUpdateListener(this);
listView = (ListView) rootView;
StableArrayAdapter adapter = new StableArrayAdapter((MainActivity) getActivity(), R.layout.list_view_item, ((MainActivity) getActivity()).getData(mPage));
listView.setAdapter(adapter);
return rootView;
}
@Override
public void onDestroyView() {
((MainActivity) getActivity()).removeDataUpdateListener(this);
super.onDestroyView();
}
//DataUpdateListener interface methods
@Override
public int getPage() {
return mPage;
}
@Override
public void onDataUpdated(ArrayList<String> data) {
StableArrayAdapter adapter = new StableArrayAdapter((MainActivity) getActivity(), R.layout.list_view_item, data);
listView.setAdapter(adapter);
}
}
当需要更新片段(列表视图)的内容时,使用参数中接收到的新内容调用方法onDataUpdated。
编辑
使用 Budius 中的代码示例添加生成崩溃的适配器:
private static class StableArrayAdapter extends ArrayAdapter<String> {
HashMap<String, Integer> mIdMap = new HashMap<String, Integer>();
public StableArrayAdapter(Context context, int textViewResourceId, List<String> objects) {
super(context, textViewResourceId, objects);
for (int i = 0; i < objects.size(); ++i) {
mIdMap.put(objects.get(i), i);
}
}
public void setData(List<String> objects){
mIdMap.clear();
for (int i = 0; i < objects.size(); ++i) {
mIdMap.put(objects.get(i), i);
}
}
@Override
public long getItemId(int position) {
String item = getItem(position);
return mIdMap.get(item);
}
@Override
public boolean hasStableIds() {
return true;
}
}
编辑 2
尝试使用 Budius 解决方案的新适配器。但它不起作用。内容未刷新。
private static class StableArrayAdapter extends ArrayAdapter<String> {
List<String> objects;
public StableArrayAdapter(Context context, int textViewResourceId, List<String> objects) {
super(context, textViewResourceId, objects);
this.objects = new ArrayList<>();
this.objects.addAll(objects);
}
public void setData(List<String> objects){
this.objects.clear();
this.objects.addAll(objects);
}
@Override
public long getItemId(int position) {
return Math.abs(objects.get(position).hashCode());
}
@Override
public boolean hasStableIds() {
return true;
}
}
调用 setAdapter(adapter)
并将列表 return 调到顶部是正常的 "expected" 行为。毕竟是新适配器,框架不应该假设新适配器与旧适配器有任何关系。
这么说,你的解决方案应该还是围绕notifyDataSetChanged()
和stableIds
的适当用法。考虑到这一点,您应该做的只是简单地切换 ArrayList<String> data
而不是创建一个新的。像这样:
@Override
public void onDataUpdated(ArrayList<String> data) {
adapter.setData(data); // internally replaces the old data with this new one
adapter.notifyDataSetChanged();
}
然后在您实现 StableArrayAdapter 时,您必须将 hasStableIds
覆盖为 return true 并将 getItemId
覆盖为 return a meaningfull 值。考虑到您的数据只是字符串,这有点棘手,但像 Math.abs(value.hashCode)
应该足够了。
PS.: 我强烈建议使用 RecyclerView
而不是 ListView
。我个人不明白为什么 Google 还没有弃用 ListView。
编辑:
根据评论,有些像这样:
public class StableArrayAdapter extends /* whatever you're extending */ {
... your code as usual
@Override public boolean hasStableIds() { return true; }
@Override public long getItemId(int position) {
return Math.abs(data.get(position).hashCode());
}
}
您告诉系统 ID
与您获得的数据直接相关,并且您 returning ID 连接到您的数据(即使连接松散)。也就是说,一个数据一个ID,另一个数据一个ID。
这样通知 ListView 知道根据 ID 保持位置。
编辑 2:
我发现了你的错误。您的适配器扩展自 ArrayAdapter
。这在内部已经有一个 List<String>
,当您调用 getItem(position);
时,数据来自该内部列表。
修复很简单。
- 删除
List<String> objects;
并使用内部列表。
要更新您从 ArrayAdapter (https://developer.android.com/reference/android/widget/ArrayAdapter.html) 调用 add
、addAll
和 clear
方法的数据:
类似于:
@Override
public void onDataUpdated(ArrayList<String> data) {
adapter.clear();
adapter.addAll(data);
adapter.notifyDataSetChanged();
}