Android 动态更新 ViewPager?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10849552/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Update ViewPager dynamically?
提问by Ixx
I can't update the content in ViewPager.
我无法更新 ViewPager 中的内容。
What is the correct usage of methods instantiateItem() and getItem() in FragmentPagerAdapter class?
FragmentPagerAdapter 类中方法 instantiateItem() 和 getItem() 的正确用法是什么?
I was using only getItem() to instantiate and return my fragments:
我只使用 getItem() 来实例化并返回我的片段:
@Override
public Fragment getItem(int position) {
return new MyFragment(context, paramters);
}
This worked well. Except I can't change the content.
这工作得很好。除了我不能改变内容。
So I found this: ViewPager PagerAdapter not updating the View
所以我发现了这个:ViewPager PagerAdapter not updates the View
"My approach is to use the setTag() method for any instantiated view in the instantiateItem() method"
“我的方法是对 instantiateItem() 方法中的任何实例化视图使用 setTag() 方法”
Now I want to implement instantiateItem() to do that. But I don't know what I have to return (the type is Object) and what is the relation with getItem(int position)?
现在我想实现 instantiateItem() 来做到这一点。但是我不知道我必须返回什么(类型是Object)以及与getItem(int position)的关系是什么?
I read the reference:
我阅读了参考资料:
public abstract Fragment getItem (int position)
Return the Fragment associated with a specified position.
public Object instantiateItem (ViewGroup container, int position)
Create the page for the given position. The adapter is responsible for adding the view to the container given here, although it only must ensure this is done by the time it returns from finishUpdate(ViewGroup). Parameters
container The containing View in which the page will be shown. position The page position to be instantiated.
Returns
Returns an Object representing the new page. This does not need to be a View, but can be some other container of the page.
公共抽象片段getItem(int位置)
返回与指定位置关联的 Fragment。
公共对象实例化项(ViewGroup 容器,int 位置)
创建给定位置的页面。适配器负责将视图添加到此处给出的容器中,尽管它只必须确保在从完成更新(ViewGroup)返回时完成。参数
容器 将在其中显示页面的包含视图。position 要实例化的页面位置。
退货
返回一个代表新页面的对象。这不需要是一个视图,但可以是页面的其他容器。
but I still don't get it.
但我还是不明白。
Here's my code. I'm using support package v4.
这是我的代码。我正在使用支持包 v4。
ViewPagerTest
ViewPagerTest
public class ViewPagerTest extends FragmentActivity {
private ViewPager pager;
private MyFragmentAdapter adapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.pager1);
pager = (ViewPager)findViewById(R.id.slider);
String[] data = {"page1", "page2", "page3", "page4", "page5", "page6"};
adapter = new MyFragmentAdapter(getSupportFragmentManager(), 6, this, data);
pager.setAdapter(adapter);
((Button)findViewById(R.id.button)).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
reload();
}
});
}
private void reload() {
String[] data = {"changed1", "changed2", "changed3", "changed4", "changed5", "changed6"};
//adapter = new MyFragmentAdapter(getSupportFragmentManager(), 6, this, data);
adapter.setData(data);
adapter.notifyDataSetChanged();
pager.invalidate();
//pager.setCurrentItem(0);
}
}
MyFragmentAdapter
我的片段适配器
class MyFragmentAdapter extends FragmentPagerAdapter {
private int slideCount;
private Context context;
private String[] data;
public MyFragmentAdapter(FragmentManager fm, int slideCount, Context context, String[] data) {
super(fm);
this.slideCount = slideCount;
this.context = context;
this.data = data;
}
@Override
public Fragment getItem(int position) {
return new MyFragment(data[position], context);
}
@Override
public int getCount() {
return slideCount;
}
public void setData(String[] data) {
this.data = data;
}
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
MyFragment
我的片段
public final class MyFragment extends Fragment {
private String text;
public MyFragment(String text, Context context) {
this.text = text;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.slide, null);
((TextView)view.findViewById(R.id.text)).setText(text);
return view;
}
}
Here is also somebody with a similar problem, no answers http://www.mail-archive.com/[email protected]/msg200477.html
这里也有人有类似的问题,没有答案 http://www.mail-archive.com/[email protected]/msg200477.html
回答by Bill Phillips
When using FragmentPagerAdapter or FragmentStatePagerAdapter, it is best to deal solely with getItem()
and not touch instantiateItem()
at all. The instantiateItem()
-destroyItem()
-isViewFromObject()
interface on PagerAdapter is a lower-level interface that FragmentPagerAdapter uses to implement the much simpler getItem()
interface.
使用 FragmentPagerAdapter 或 FragmentStatePagerAdapter 时,最好单独处理,完全getItem()
不接触instantiateItem()
。本instantiateItem()
- destroyItem()
-isViewFromObject()
在PagerAdapter接口是较低级别的接口,FragmentPagerAdapter用来实现更简单的getItem()
界面。
Before getting into this, I should clarify that
在进入这个之前,我应该澄清一下
if you want to switch out the actual fragments that are being displayed, you need to avoid FragmentPagerAdapter and use FragmentStatePagerAdapter.
如果要切换出正在显示的实际片段,则需要避免 FragmentPagerAdapter 并使用 FragmentStatePagerAdapter。
An earlier version of this answer made the mistake of using FragmentPagerAdapter for its example - that won't work because FragmentPagerAdapter never destroys a fragment after it's been displayed the first time.
此答案的早期版本错误地将 FragmentPagerAdapter 用作其示例 - 这将不起作用,因为 FragmentPagerAdapter 在第一次显示后永远不会销毁片段。
I don't recommend the setTag()
and findViewWithTag()
workaround provided in the post you linked. As you've discovered, using setTag()
and findViewWithTag()
doesn't work with fragments, so it's not a good match.
我不建议setTag()
,并findViewWithTag()
在您联系后提供解决方法。正如您所发现的,使用setTag()
和findViewWithTag()
不适用于片段,因此它不是一个很好的匹配。
The right solution is to override getItemPosition()
. When notifyDataSetChanged()
is called, ViewPager calls getItemPosition()
on all the items in its adapter to see whether they need to be moved to a different position or removed.
正确的解决方案是覆盖getItemPosition()
. 当notifyDataSetChanged()
被调用时,ViewPager呼吁getItemPosition()
在其适配器的所有项目,看他们是否需要移动到不同的位置或删除。
By default, getItemPosition()
returns POSITION_UNCHANGED
, which means, "This object is fine where it is, don't destroy or remove it." Returning POSITION_NONE
fixes the problem by instead saying, "This object is no longer an item I'm displaying, remove it." So it has the effect of removing and recreating every single item in your adapter.
默认情况下,getItemPosition()
返回POSITION_UNCHANGED
,这意味着,“这个对象在它所在的地方很好,不要破坏或删除它。” 返回POSITION_NONE
通过说“此对象不再是我正在显示的项目,将其删除”来解决问题。因此,它具有删除和重新创建适配器中每个项目的效果。
This is a completely legitimate fix! This fix makes notifyDataSetChanged behave like a regular Adapter without view recycling. If you implement this fix and performance is satisfactory, you're off to the races. Job done.
这是一个完全合法的修复!此修复程序使 notifyDataSetChanged 表现得像一个没有视图回收的常规适配器。如果您实施此修复并且性能令人满意,那么您就可以参加比赛了。任务完成。
If you need better performance, you can use a fancier getItemPosition()
implementation. Here's an example for a pager creating fragments off of a list of strings:
如果您需要更好的性能,您可以使用更高级的getItemPosition()
实现。这是一个寻呼机从字符串列表中创建片段的示例:
ViewPager pager = /* get my ViewPager */;
// assume this actually has stuff in it
final ArrayList<String> titles = new ArrayList<String>();
FragmentManager fm = getSupportFragmentManager();
pager.setAdapter(new FragmentStatePagerAdapter(fm) {
public int getCount() {
return titles.size();
}
public Fragment getItem(int position) {
MyFragment fragment = new MyFragment();
fragment.setTitle(titles.get(position));
return fragment;
}
public int getItemPosition(Object item) {
MyFragment fragment = (MyFragment)item;
String title = fragment.getTitle();
int position = titles.indexOf(title);
if (position >= 0) {
return position;
} else {
return POSITION_NONE;
}
}
});
With this implementation, only fragments displaying new titles will get displayed. Any fragments displaying titles that are still in the list will instead be moved around to their new position in the list, and fragments with titles that are no longer in the list at all will be destroyed.
使用此实现,只会显示显示新标题的片段。任何显示标题仍在列表中的片段将被移动到列表中的新位置,而标题根本不再在列表中的片段将被销毁。
What if the fragment has not been recreated, but needs to be updated anyway? Updates to a living fragment are best handled by the fragment itself. That's the advantage of having a fragment, after all - it is its own controller. A fragment can add a listener or an observer to another object in onCreate()
, and then remove it in onDestroy()
, thus managing the updates itself. You don't have to put all the update code inside getItem()
like you do in an adapter for a ListView or other AdapterView types.
如果片段尚未重新创建,但无论如何都需要更新怎么办?对活动片段的更新最好由片段本身处理。毕竟,这是拥有片段的优势——它是它自己的控制器。片段可以向 中的另一个对象添加侦听器或观察者onCreate()
,然后在 中将其删除onDestroy()
,从而自行管理更新。您不必getItem()
像在 ListView 或其他 AdapterView 类型的适配器中那样将所有更新代码放入其中。
One last thing - just because FragmentPagerAdapter doesn't destroy a fragment doesn't mean that getItemPosition is completely useless in a FragmentPagerAdapter. You can still use this callback to reorder your fragments in the ViewPager. It will never remove them completely from the FragmentManager, though.
最后一件事 - 仅仅因为 FragmentPagerAdapter 不销毁片段并不意味着 getItemPosition 在 FragmentPagerAdapter 中完全无用。您仍然可以使用此回调在 ViewPager 中重新排序您的片段。但是,它永远不会从 FragmentManager 中完全删除它们。
回答by M-WaJeEh
Instead of returning POSITION_NONE
from getItemPosition()
and causing full view recreation, do this:
而不是返回的POSITION_NONE
距离getItemPosition()
,造成众目睽睽娱乐,这样做:
//call this method to update fragments in ViewPager dynamically
public void update(UpdateData xyzData) {
this.updateData = xyzData;
notifyDataSetChanged();
}
@Override
public int getItemPosition(Object object) {
if (object instanceof UpdateableFragment) {
((UpdateableFragment) object).update(updateData);
}
//don't return POSITION_NONE, avoid fragment recreation.
return super.getItemPosition(object);
}
Your fragments should implement UpdateableFragment
interface:
你的片段应该实现UpdateableFragment
接口:
public class SomeFragment extends Fragment implements
UpdateableFragment{
@Override
public void update(UpdateData xyzData) {
// this method will be called for every fragment in viewpager
// so check if update is for this fragment
if(forMe(xyzData)) {
// do whatever you want to update your UI
}
}
}
and the interface:
和界面:
public interface UpdateableFragment {
public void update(UpdateData xyzData);
}
Your data class:
您的数据类:
public class UpdateData {
//whatever you want here
}
回答by Ziad Gholmish
for those who still face the same problem which i faced before when i have a ViewPager
with 7 fragments. the default for these fragments to load the English content from API
service but the problem here that i want to change the language from settings activity and after finish
settings activity i want ViewPager
in main activity to refresh the fragments to match the language selection from the user and load the Arabic content if user chooses Arabic here what i did to work from the first time
对于那些仍然面临着我之前遇到的相同问题的人,当我有ViewPager
7 个片段时。这些片段的默认设置是从API
服务加载英语内容,但这里的问题是我想从设置活动更改语言,在完成设置活动后,我希望ViewPager
在主活动中刷新片段以匹配用户的语言选择并加载阿拉伯语内容,如果用户在这里选择阿拉伯语,我从第一次开始工作
1- You must use FragmentStatePagerAdapter as mentioned above.
1- 您必须如上所述使用 FragmentStatePagerAdapter。
2- on mainActivity i override the onResume and did the following
2-在 mainActivity 上,我覆盖了 onResume 并执行了以下操作
if (!(mPagerAdapter == null)) {
mPagerAdapter.notifyDataSetChanged();
}
3-i overrided the getItemPosition()
in mPagerAdapter and make it return POSITION_NONE
.
3-i 覆盖了getItemPosition()
mPagerAdapter 并使其返回POSITION_NONE
。
@Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
works like charm
像魅力一样工作
回答by oscarthecat
I have encountered this problem and finally solved it today, so I write down what I have learned and I hope it is helpful for someone who is new to Android's ViewPager
and update as I do. I'm using FragmentStatePagerAdapter
in API level 17 and currently have just 2 fragments. I think there must be something not correct, please correct me, thanks.
我遇到了这个问题,今天终于解决了,所以我把我学到的东西写下来,希望对刚接触AndroidViewPager
并像我一样更新的人有所帮助。我FragmentStatePagerAdapter
在 API 级别 17 中使用,目前只有 2 个片段。我觉得一定有什么不对的地方,请指正,谢谢。
Serialized data has to be loaded into memory. This can be done using a
CursorLoader
/AsyncTask
/Thread
. Whether it's automatically loaded depends on your code. If you are using aCursorLoader
, it's auto-loaded since there is a registered data observer.After you call
viewpager.setAdapter(pageradapter)
, the adapter'sgetCount()
is constantly called to build fragments. So if data is being loaded,getCount()
can return 0, thus you don't need to create dummy fragments for no data shown.After the data is loaded, the adapter will not build fragments automatically since
getCount()
is still 0, so we can set the actually loaded data number to be returned bygetCount()
, then call the adapter'snotifyDataSetChanged()
.ViewPager
begin to create fragments (just the first 2 fragments) by data in memory. It's done beforenotifyDataSetChanged()
is returned. Then theViewPager
has the right fragments you need.If the data in the database and memory are both updated (write through), or just data in memory is updated (write back), or only data in the database is updated. In the last two cases if data is not automatically loaded from the database to memory (as mentioned above). The
ViewPager
and pager adapter just deal with data in memory.So when data in memory is updated, we just need to call the adapter's
notifyDataSetChanged()
. Since the fragment is already created, the adapter'sonItemPosition()
will be called beforenotifyDataSetChanged()
returns. Nothing needs to be done ingetItemPosition()
. Then the data is updated.
序列化数据必须加载到内存中。这可以用做
CursorLoader
/AsyncTask
/Thread
。是否自动加载取决于您的代码。如果您使用的是CursorLoader
,它会自动加载,因为有一个注册的数据观察者。在您调用 之后
viewpager.setAdapter(pageradapter)
,getCount()
会不断调用适配器来构建片段。因此,如果正在加载数据,则getCount()
可以返回 0,因此您无需为未显示数据创建虚拟片段。数据加载完成后,由于
getCount()
仍然为0,因此适配器不会自动构建片段,因此我们可以设置返回的实际加载数据编号getCount()
,然后调用适配器的notifyDataSetChanged()
.ViewPager
开始通过内存中的数据创建片段(仅前 2 个片段)。它在notifyDataSetChanged()
返回之前完成。然后ViewPager
有你需要的正确片段。如果数据库和内存中的数据都更新了(write through),或者只更新了内存中的数据(write back),或者只更新了数据库中的数据。在后两种情况下,如果数据没有自动从数据库加载到内存(如上所述)。在
ViewPager
和寻呼机转接器只是处理内存中的数据。所以当内存中的数据更新时,我们只需要调用适配器的
notifyDataSetChanged()
. 由于已经创建了片段,因此onItemPosition()
将在notifyDataSetChanged()
返回之前调用适配器。中什么都不需要做getItemPosition()
。然后更新数据。
回答by czaku
Try destroyDrawingCache()
on ViewPager after notifyDataSetChanged()
in your code.
在您的代码中尝试destroyDrawingCache()
使用 ViewPager notifyDataSetChanged()
。
回答by Sami Eltamawy
After hours of frustration while trying all the above solutions to overcome this problem and also trying many solutions on other similar questions like this, thisand thiswhich all FAILED with me to solve this problem and to make the ViewPager
to destroy the old Fragment
and fill the pager
with the new Fragment
s. I have solved the problem as following:
经过挫折小时,尝试所有上述解决方案来克服这个问题,也试图像其他类似的问题,很多解决方案这个,这个和这个,所有与我没能解决这个问题,使ViewPager
摧毁旧的Fragment
,并填补了pager
用新的Fragment
s。我已经解决了以下问题:
1)Make the ViewPager
class to extends FragmentPagerAdapter
as following:
1)使ViewPager
类扩展FragmentPagerAdapter
如下:
public class myPagerAdapter extends FragmentPagerAdapter {
2)Create an Item for the ViewPager
that store the title
and the fragment
as following:
2)为ViewPager
存储title
和创建一个项目,fragment
如下所示:
public class PagerItem {
private String mTitle;
private Fragment mFragment;
public PagerItem(String mTitle, Fragment mFragment) {
this.mTitle = mTitle;
this.mFragment = mFragment;
}
public String getTitle() {
return mTitle;
}
public Fragment getFragment() {
return mFragment;
}
public void setTitle(String mTitle) {
this.mTitle = mTitle;
}
public void setFragment(Fragment mFragment) {
this.mFragment = mFragment;
}
}
3)Make the constructor of the ViewPager
take my FragmentManager
instance to store it in my class
as following:
3)ViewPager
使用 myFragmentManager
实例的构造函数将其存储在 my 中class
,如下所示:
private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;
public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
super(fragmentManager);
mFragmentManager = fragmentManager;
mPagerItems = pagerItems;
}
4)Create a method to re-set the adapter
data with the new data by deleting all the previous fragment
from the fragmentManager
itself directly to make the adapter
to set the new fragment
from the new list again as following:
4)创建的方法重新设置adapter
删除所有以前用新的数据数据fragment
从fragmentManager
自身直接使adapter
设置新fragment
如下又从新的列表:
public void setPagerItems(ArrayList<PagerItem> pagerItems) {
if (mPagerItems != null)
for (int i = 0; i < mPagerItems.size(); i++) {
mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
}
mPagerItems = pagerItems;
}
5)From the container Activity
or Fragment
do not re-initialize the adapter with the new data. Set the new data through the method setPagerItems
with the new data as following:
5)从容器Activity
或Fragment
不要用新数据重新初始化适配器。通过使用新数据的方法setPagerItems
设置新数据如下:
ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));
mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();
I hope it helps.
我希望它有帮助。
回答by hordurh
For some reason none of the answers worked for me so I had to override the restoreState method without calling super in my fragmentStatePagerAdapter. Code:
出于某种原因,没有一个答案对我有用,所以我不得不覆盖 restoreState 方法而不在我的 fragmentStatePagerAdapter 中调用 super。代码:
private class MyAdapter extends FragmentStatePagerAdapter {
// [Rest of implementation]
@Override
public void restoreState(Parcelable state, ClassLoader loader) {}
}
回答by adwait
I slightly modified the solution provided by Bill Phillips to suit my needs
我稍微修改了 Bill Phillips 提供的解决方案以满足我的需求
private class PagerAdapter extends FragmentStatePagerAdapter{
Bundle oBundle;
FragmentManager oFragmentManager;
ArrayList<Fragment> oPooledFragments;
public PagerAdapter(FragmentManager fm) {
super(fm);
oFragmentManager=fm;
}
@Override
public int getItemPosition(Object object) {
Fragment oFragment=(Fragment)object;
oPooledFragments=new ArrayList<>(oFragmentManager.getFragments());
if(oPooledFragments.contains(oFragment))
return POSITION_NONE;
else
return POSITION_UNCHANGED;
}
}
so that the getItemPosition()
returns POSITION_NONE
only for those fragments which are currently in the FragmentManager
when getItemPosition
is called.
(Note that this FragmentStatePager
and the ViewPager
associated with it are contained in a Fragment not in a Activity)
以便仅getItemPosition()
返回POSITION_NONE
当前在FragmentManager
when中的那些片段getItemPosition
。(请注意, thisFragmentStatePager
和ViewPager
与之相关的都包含在 Fragment 中而不是 Activity 中)
回答by jpm
I had a similar problem but don't want to trust on the existing solutions (hard coded tag names etc.) and I couldn't make M-WaJeEh's solution work for me. Here is my solution:
我有一个类似的问题,但不想相信现有的解决方案(硬编码标签名称等),我无法让 M-WaJeEh 的解决方案对我有用。这是我的解决方案:
I keep references to the fragments created in getItem in an array. This works fine as long as the activity is not destroyed due to configurationChange or lack of memory or whatever (--> when coming back to the activity, fragments return to their last state without 'getItem' being called again and thus without updating the array).
我将在 getItem 中创建的片段的引用保留在数组中。只要活动没有因配置更改或内存不足或其他原因而被破坏(--> 当返回活动时,片段返回到它们的最后状态,而不会再次调用“getItem”,因此不会更新数组)。
To avoid this problem I implemented instantiateItem(ViewGroup, int) and update my array there, like this:
为了避免这个问题,我实现了 instantiateItem(ViewGroup, int) 并在那里更新我的数组,如下所示:
@Override
public Object instantiateItem(ViewGroup container, int position) {
Object o = super.instantiateItem(container, position);
if(o instanceof FragmentX){
myFragments[0] = (FragmentX)o;
}else if(o instanceof FragmentY){
myFragments[1] = (FragmentY)o;
}else if(o instanceof FragmentZ){
myFragments[2] = (FragmentZ)o;
}
return o;
}
So, on the one hand I'm happy that I found a solution that works for me and wanted to share it with you, but I also wanted to ask whether somebody else tried something similar and whether there is any reason why I shouldn't do it like that? So far it works very good for me...
因此,一方面我很高兴我找到了一个适合我的解决方案并想与您分享,但我也想问问其他人是否尝试过类似的方法以及是否有任何理由我不应该这样做这样做吗?到目前为止,它对我来说非常好......
回答by Fantasy Fang
I use EventBuslibrary to update Fragment
content in ViewPager
. The logic is simple, just like document of EventBus how to do. It is no need to control FragmentPagerAdapter
instance. The code is here:
我用EventBus库更新Fragment
的内容ViewPager
。逻辑很简单,就像EventBus怎么做的文档。不需要控制FragmentPagerAdapter
实例。代码在这里:
1: Define events
1:定义事件
Define which message which is needed to update.
定义需要更新的消息。
public class UpdateCountEvent {
public final int count;
public UpdateCountEvent(int count) {
this.count = count;
}
}
2.Prepare subscribers
2.准备订阅者
Write below code in the Fragment which is needed update.
在需要更新的 Fragment 中写入以下代码。
@Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
@Override
public void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}
public void onEvent(UpdateCountEvent event) {//get update message
Toast.makeText(getActivity(), event.count, Toast.LENGTH_SHORT).show();
}
3.Post events
3.发布活动
Write below code in other Activity
or other Fragment
which needs to update parameter
将下面的代码写在需要更新参数的其他Activity
或其他Fragment
中
//input update message
EventBus.getDefault().post(new UpdateCountEvent(count));