从 Android 中的 ViewPager 中删除 Fragment Page
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10396321/
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
Remove Fragment Page from ViewPager in Android
提问by astuetz
I'm trying to dynamically add and remove Fragments from a ViewPager, adding works without any problems, but removing doesn't work as expected.
我正在尝试从 ViewPager 动态添加和删除 Fragments,添加工作没有任何问题,但删除无法按预期工作。
Everytime I want to remove the current item, the last one gets removed.
每次我想删除当前项目时,最后一个都会被删除。
I also tried to use an FragmentStatePagerAdapter or return POSITION_NONE in the adapter's getItemPosition method.
我还尝试在适配器的 getItemPosition 方法中使用 FragmentStatePagerAdapter 或返回 POSITION_NONE 。
What am I doing wrong?
我究竟做错了什么?
Here's a basic example:
这是一个基本示例:
MainActivity.java
主活动.java
public class MainActivity extends FragmentActivity implements TextProvider {
private Button mAdd;
private Button mRemove;
private ViewPager mPager;
private MyPagerAdapter mAdapter;
private ArrayList<String> mEntries = new ArrayList<String>();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mEntries.add("pos 1");
mEntries.add("pos 2");
mEntries.add("pos 3");
mEntries.add("pos 4");
mEntries.add("pos 5");
mAdd = (Button) findViewById(R.id.add);
mRemove = (Button) findViewById(R.id.remove);
mPager = (ViewPager) findViewById(R.id.pager);
mAdd.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
addNewItem();
}
});
mRemove.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
removeCurrentItem();
}
});
mAdapter = new MyPagerAdapter(this.getSupportFragmentManager(), this);
mPager.setAdapter(mAdapter);
}
private void addNewItem() {
mEntries.add("new item");
mAdapter.notifyDataSetChanged();
}
private void removeCurrentItem() {
int position = mPager.getCurrentItem();
mEntries.remove(position);
mAdapter.notifyDataSetChanged();
}
@Override
public String getTextForPosition(int position) {
return mEntries.get(position);
}
@Override
public int getCount() {
return mEntries.size();
}
private class MyPagerAdapter extends FragmentPagerAdapter {
private TextProvider mProvider;
public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
super(fm);
this.mProvider = provider;
}
@Override
public Fragment getItem(int position) {
return MyFragment.newInstance(mProvider.getTextForPosition(position));
}
@Override
public int getCount() {
return mProvider.getCount();
}
}
}
TextProvider.java
文本提供程序
public interface TextProvider {
public String getTextForPosition(int position);
public int getCount();
}
MyFragment.java
我的片段
public class MyFragment extends Fragment {
private String mText;
public static MyFragment newInstance(String text) {
MyFragment f = new MyFragment(text);
return f;
}
public MyFragment() {
}
public MyFragment(String text) {
this.mText = text;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment, container, false);
((TextView) root.findViewById(R.id.position)).setText(mText);
return root;
}
}
activity_main.xml
活动_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<Button
android:id="@+id/add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="add new item" />
<Button
android:id="@+id/remove"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="remove current item" />
<android.support.v4.view.ViewPager
android:id="@+id/pager"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_weight="1" />
</LinearLayout>
fragment.xml
片段.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:id="@+id/position"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textSize="35sp" />
</LinearLayout>
采纳答案by Tim Rae
The solution by Louth was not enough to get things working for me, as the existing fragments were not getting destroyed. Motivated by this answer, I found that the solution is to override the getItemId(int position)
method of FragmentPagerAdapter
to give a new unique ID whenever there has been a change in the expected position of a Fragment.
Louth 的解决方案不足以让事情为我工作,因为现有的碎片没有被破坏。受此答案的启发,我发现解决方案是在 Fragment 的预期位置发生变化时覆盖提供新的唯一 ID的getItemId(int position)
方法FragmentPagerAdapter
。
Source Code:
源代码:
private class MyPagerAdapter extends FragmentPagerAdapter {
private TextProvider mProvider;
private long baseId = 0;
public MyPagerAdapter(FragmentManager fm, TextProvider provider) {
super(fm);
this.mProvider = provider;
}
@Override
public Fragment getItem(int position) {
return MyFragment.newInstance(mProvider.getTextForPosition(position));
}
@Override
public int getCount() {
return mProvider.getCount();
}
//this is called when notifyDataSetChanged() is called
@Override
public int getItemPosition(Object object) {
// refresh all fragments when data set changed
return PagerAdapter.POSITION_NONE;
}
@Override
public long getItemId(int position) {
// give an ID different from position when position has been changed
return baseId + position;
}
/**
* Notify that the position of a fragment has been changed.
* Create a new ID for each position to force recreation of the fragment
* @param n number of items which have been changed
*/
public void notifyChangeInPosition(int n) {
// shift the ID returned by getItemId outside the range of all previous fragments
baseId += getCount() + n;
}
}
Now, for example if you delete a single tab or make some change to the order, you should call notifyChangeInPosition(1)
before calling notifyDataSetChanged()
, which will ensure that all the Fragments will be recreated.
现在,例如,如果您删除单个选项卡或对顺序进行一些更改,则应在调用notifyChangeInPosition(1)
之前调用notifyDataSetChanged()
,这将确保所有 Fragment 都将被重新创建。
Why this solution works
为什么这个解决方案有效
Overriding getItemPosition():
覆盖 getItemPosition():
When notifyDataSetChanged()
is called, the adapter calls the notifyChanged()
method of the ViewPager
which it is attached to. The ViewPager
then checks the value returned by the adapter's getItemPosition()
for each item, removing those items which return POSITION_NONE
(see the source code) and then repopulating.
当notifyDataSetChanged()
被调用时,适配器调用notifyChanged()
的方法,ViewPager
它是联系在一起的。在ViewPager
随后检查返回值由适配器的getItemPosition()
每个项目,除去这些项目,其回报率POSITION_NONE
(参见源代码),然后重新填充。
Overriding getItemId():
覆盖 getItemId():
This is necessary to prevent the adapter from reloading the old fragment when the ViewPager
is repopulating. You can easily understand why this works by looking at the source codefor instantiateItem() in FragmentPagerAdapter
.
这对于防止适配器在重新ViewPager
填充时重新加载旧片段是必要的。你可以很容易理解为什么这个工程通过查看源代码的instantiateItem()的FragmentPagerAdapter
。
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
As you can see, the getItem()
method is only called if the fragment manager finds no existing fragments with the same Id. To me it seems like a bug that the old fragments are still attached even after notifyDataSetChanged()
is called, but the documentation for ViewPager
does clearly state that:
如您所见,getItem()
只有在片段管理器没有发现具有相同 Id 的现有片段时才会调用该方法。对我来说,即使在notifyDataSetChanged()
调用后仍然附加旧片段似乎是一个错误,但文档ViewPager
确实明确指出:
Note this class is currently under early design and development. The API will likely change in later updates of the compatibility library, requiring changes to the source code of apps when they are compiled against the newer version.
请注意,此类目前处于早期设计和开发阶段。API 可能会在兼容性库的后续更新中发生变化,需要在针对较新版本编译应用程序时更改应用程序的源代码。
So hopefully the workaround given here will not be necessary in a future version of the support library.
因此,希望在未来版本的支持库中不需要这里给出的解决方法。
回答by Louth
The ViewPager doesn't remove your fragments with the code above because it loads several views (or fragments in your case) into memory. In addition to the visible view, it also loads the view to either side of the visible one. This provides the smooth scrolling from view to view that makes the ViewPager so cool.
ViewPager 不会使用上面的代码删除您的片段,因为它会将多个视图(或您的情况下的片段)加载到内存中。除了可见视图之外,它还将视图加载到可见视图的任一侧。这提供了从一个视图到另一个视图的平滑滚动,使 ViewPager 如此酷。
To achieve the effect you want, you need to do a couple of things.
为了达到你想要的效果,你需要做几件事。
Change the FragmentPagerAdapter to a FragmentStatePagerAdapter. The reason for this is that the FragmentPagerAdapter will keep all the views that it loads into memory forever. Where the FragmentStatePagerAdapter disposes of views that fall outside the current and traversable views.
Override the adapter method getItemPosition (shown below). When we call
mAdapter.notifyDataSetChanged();
the ViewPager interrogates the adapter to determine what has changed in terms of positioning. We use this method to say that everything has changed so reprocess all your view positioning.
将 FragmentPagerAdapter 更改为 FragmentStatePagerAdapter。这样做的原因是 FragmentPagerAdapter 将永远保留它加载到内存中的所有视图。FragmentStatePagerAdapter 处理当前视图和可遍历视图之外的视图。
覆盖适配器方法 getItemPosition(如下所示)。当我们调用
mAdapter.notifyDataSetChanged();
ViewPager 时,它会询问适配器以确定在定位方面发生了什么变化。我们使用这种方法来说明一切都发生了变化,因此重新处理所有视图定位。
And here's the code...
这是代码......
private class MyPagerAdapter extends FragmentStatePagerAdapter {
//... your existing code
@Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
}
回答by Vasil Valchev
my working solution to remove fragment page from view pager
我从视图寻呼机中删除片段页面的工作解决方案
public class MyFragmentAdapter extends FragmentStatePagerAdapter {
private ArrayList<ItemFragment> pages;
public MyFragmentAdapter(FragmentManager fragmentManager, ArrayList<ItemFragment> pages) {
super(fragmentManager);
this.pages = pages;
}
@Override
public Fragment getItem(int index) {
return pages.get(index);
}
@Override
public int getCount() {
return pages.size();
}
@Override
public int getItemPosition(Object object) {
int index = pages.indexOf (object);
if (index == -1)
return POSITION_NONE;
else
return index;
}
}
And when i need to remove some page by index i do this
当我需要按索引删除某些页面时,我会这样做
pages.remove(position); // ArrayList<ItemFragment>
adapter.notifyDataSetChanged(); // MyFragmentAdapter
Here it is my adapter initialization
这是我的适配器初始化
MyFragmentAdapter adapter = new MyFragmentAdapter(getSupportFragmentManager(), pages);
viewPager.setAdapter(adapter);
回答by Kathan Shah
The fragment must be already removed but the issue was viewpager save state
该片段必须已被删除,但问题是 viewpager 保存状态
Try
尝试
myViewPager.setSaveFromParentEnabled(false);
Nothing worked but this solved the issue !
没有任何效果,但这解决了问题!
Cheers !
干杯!
回答by Sven
I had the idea of simply copy the source code from android.support.v4.app.FragmentPagerAdpater
into a custom class named
CustumFragmentPagerAdapter
. This gave me the chance to modify the instantiateItem(...)
so that every time it is called, it removes / destroys the currently attached fragment before it adds the new fragment received from getItem()
method.
我的想法是简单地将源代码复制android.support.v4.app.FragmentPagerAdpater
到名为
CustumFragmentPagerAdapter
. 这让我有机会修改它,instantiateItem(...)
以便每次调用它时,它都会在添加从getItem()
方法接收到的新片段之前删除/销毁当前附加的片段。
Simply modify the instantiateItem(...)
in the following way:
只需instantiateItem(...)
按以下方式修改:
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
// remove / destroy current fragment
if (fragment != null) {
mCurTransaction.remove(fragment);
}
// get new fragment and add it
fragment = getItem(position);
mCurTransaction.add(container.getId(), fragment, makeFragmentName(container.getId(), itemId));
if (fragment != mCurrentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
回答by user158
For future readers!
对于未来的读者!
Now you can use ViewPager2for dynamically adding, removing fragment from the viewpager.
现在您可以使用ViewPager2从 viewpager 中动态添加、删除片段。
Quoting form API reference
报价单API 参考
ViewPager2 replaces ViewPager, addressing most of its predecessor's pain-points, including right-to-left layout support, vertical orientation, modifiable Fragment collections, etc.
ViewPager2 取代了 ViewPager,解决了其前身的大部分痛点,包括从右到左的布局支持、垂直方向、可修改的 Fragment 集合等。
Take look at MutableCollectionFragmentActivity.kt
in googlesample/android-viewpager2for an example of adding, removing fragments dynamically from the viewpager.
采取看看MutableCollectionFragmentActivity.kt
在googlesample /机器人-viewpager2用于添加,从viewpager动态除去碎片的一个例子。
For your information:
供您参考:
Articles:
文章:
Samples Repo: https://github.com/googlesamples/android-viewpager2
回答by MSaudi
You can combine both for better :
您可以将两者结合使用以获得更好的效果:
private class MyPagerAdapter extends FragmentStatePagerAdapter {
//... your existing code
@Override
public int getItemPosition(Object object){
if(Any_Reason_You_WantTo_Update_Positions) //this includes deleting or adding pages
return PagerAdapter.POSITION_NONE;
}
else
return PagerAdapter.POSITION_UNCHANGED; //this ensures high performance in other operations such as editing list items.
}
回答by passerbywhu
Louth's answer works fine. But I don't think always return POSITION_NONE is a good idea. Because POSITION_NONE means that fragment should be destroyed and a new fragment will be created. You can check that in dataSetChanged function in the source code of ViewPager.
劳斯的回答工作正常。但我不认为总是返回 POSITION_NONE 是个好主意。因为 POSITION_NONE 意味着应该销毁片段并创建一个新片段。您可以在 ViewPager 源代码中的 dataSetChanged 函数中查看。
if (newPos == PagerAdapter.POSITION_NONE) {
mItems.remove(i);
i--;
... not related code
mAdapter.destroyItem(this, ii.position, ii.object);
So I think you'd better use an arraylist of weakReference to save all the fragments you have created. And when you add or remove some page, you can get the right position from your own arraylist.
所以我认为你最好使用一个weakReference的arraylist来保存你创建的所有片段。当您添加或删除某个页面时,您可以从您自己的数组列表中获取正确的位置。
public int getItemPosition(Object object) {
for (int i = 0; i < mFragmentsReferences.size(); i ++) {
WeakReference<Fragment> reference = mFragmentsReferences.get(i);
if (reference != null && reference.get() != null) {
Fragment fragment = reference.get();
if (fragment == object) {
return i;
}
}
}
return POSITION_NONE;
}
According to the comments, getItemPosition is Called when the host view is attempting to determine if an item's position has changed. And the return value means its new position.
根据评论,当主机视图尝试确定项目的位置是否已更改时,会调用 getItemPosition。返回值意味着它的新位置。
But this is not enought. We still have an important step to take. In the source code of FragmentStatePagerAdapter, there is an array named "mFragments" caches the fragments which are not destroyed. And in instantiateItem function.
但这还不够。我们仍然需要迈出重要的一步。在 FragmentStatePagerAdapter 的源代码中,有一个名为“mFragments”的数组缓存未销毁的片段。并在instantiateItem函数中。
if (mFragments.size() > position) {
Fragment f = mFragments.get(position);
if (f != null) {
return f;
}
}
It returned the cached fragment directly when it find that cached fragment is not null. So there is a problem. From example, let's delete one page at position 2, Firstly, We remove that fragment from our own reference arraylist. so in getItemPosition it will return POSITION_NONE for that fragment, and then that fragment will be destroyed and removed from "mFragments".
当发现缓存的片段不为空时,直接返回缓存的片段。所以有一个问题。例如,让我们删除位置 2 处的一页,首先,我们从我们自己的引用数组列表中删除该片段。因此,在 getItemPosition 中,它将为该片段返回 POSITION_NONE,然后该片段将被销毁并从“mFragments”中删除。
mFragments.set(position, null);
Now the fragment at position 3 will be at position 2. And instantiatedItem with param position 3 will be called. At this time, the third item in "mFramgents" is not null, so it will return directly. But actually what it returned is the fragment at position 2. So when we turn into page 3, we will find an empty page there.
现在位置 3 处的片段将位于位置 2。并且将调用具有参数位置 3 的实例化项。此时,“mFramgents”中的第三项不为空,直接返回。但实际上它返回的是位置2的片段。所以当我们翻到第3页时,我们会发现那里有一个空页。
To work around this problem. My advise is that you can copy the source code of FragmentStatePagerAdapter into your own project, and when you do add or remove operations, you should add and remove elements in the "mFragments" arraylist.
要解决此问题。我的建议是,您可以将 FragmentStatePagerAdapter 的源代码复制到您自己的项目中,当您进行添加或删除操作时,您应该添加和删除“mFragments”数组列表中的元素。
Things will be simpler if you just use PagerAdapter instead of FragmentStatePagerAdapter. Good Luck.
如果您只使用 PagerAdapter 而不是 FragmentStatePagerAdapter,事情会更简单。祝你好运。
回答by Riad Hasan
add or remove fragment in viewpager dynamically.
Call setupViewPager(viewPager) on activity start. To load different fragment call setupViewPagerCustom(viewPager). e.g. on button click call: setupViewPagerCustom(viewPager);
动态添加或删除 viewpager 中的片段。
在活动开始时调用 setupViewPager(viewPager)。要加载不同的片段调用 setupViewPagerCustom(viewPager)。例如按钮点击调用:setupViewPagerCustom(viewPager);
private void setupViewPager(ViewPager viewPager)
{
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFrag(new fragmnet1(), "HOME");
adapter.addFrag(new fragmnet2(), "SERVICES");
viewPager.setAdapter(adapter);
}
private void setupViewPagerCustom(ViewPager viewPager)
{
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFrag(new fragmnet3(), "Contact us");
adapter.addFrag(new fragmnet4(), "ABOUT US");
viewPager.setAdapter(adapter);
}
//Viewpageradapter, handles the views
static class ViewPagerAdapter extends FragmentStatePagerAdapter
{
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager){
super(manager);
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
@Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
public void addFrag(Fragment fragment, String title){
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position){
return mFragmentTitleList.get(position);
}
}
回答by Milan Laslop
I had some problems with FragmentStatePagerAdapter
.
After removing an item:
我有一些问题FragmentStatePagerAdapter
。删除项目后:
- there was another item used for a position (an item which did not belong to the position but to a position next to it)
- or some fragment was not loaded (there was only blank background visible on that page)
- 有另一个用于位置的项目(不属于该位置但属于它旁边位置的项目)
- 或某些片段未加载(该页面上只有空白背景可见)
After lots of experiments, I came up with the following solution.
经过大量实验,我想出了以下解决方案。
public class SomeAdapter extends FragmentStatePagerAdapter {
private List<Item> items = new ArrayList<Item>();
private boolean removing;
@Override
public Fragment getItem(int position) {
ItemFragment fragment = new ItemFragment();
Bundle args = new Bundle();
// use items.get(position) to configure fragment
fragment.setArguments(args);
return fragment;
}
@Override
public int getCount() {
return items.size();
}
@Override
public int getItemPosition(Object object) {
if (removing) {
return PagerAdapter.POSITION_NONE;
}
Item item = getItemOfFragment(object);
int index = items.indexOf(item);
if (index == -1) {
return POSITION_NONE;
} else {
return index;
}
}
public void addItem(Item item) {
items.add(item);
notifyDataSetChanged();
}
public void removeItem(int position) {
items.remove(position);
removing = true;
notifyDataSetChanged();
removing = false;
}
}
This solution only uses a hack in case of removing an item. Otherwise (e.g. when adding an item) it retains the cleanliness and performance of an original code.
此解决方案仅在删除项目时使用 hack。否则(例如,在添加项目时)它会保留原始代码的清洁度和性能。
Of course, from the outside of the adapter, you call only addItem/removeItem, no need to call notifyDataSetChanged().
当然,从适配器外部,你只调用addItem/removeItem,不需要调用notifyDataSetChanged()。