我正在尝试从ViewPager动态添加和删除片段,添加工作没有任何问题,但删除不能按预期工作 .
每次我想删除当前项目时,最后一项将被删除 .
我还尝试使用FragmentStatePagerAdapter或在适配器的getItemPosition方法中返回POSITION_NONE .
我究竟做错了什么?
这是一个基本的例子:
MainActivity.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
<?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 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>
14 回答
我在
FragmentStatePagerAdapter
遇到了一些问题 . 删除项目后:还有另一个用于某个位置的项目(一个不属于该位置但位于该位置旁边的位置的项目)
或某些片段未加载(该页面上只显示空白背景)
经过大量实验,我想出了以下解决方案 .
此解决方案仅在删除项目时使用hack . 否则(例如,在添加项目时),它保留了原始代码的清洁度和性能 .
当然,从适配器的外部,你只调用addItem / removeItem,不需要调用notifyDataSetChanged() .
我只想将源代码从
android.support.v4.app.FragmentPagerAdpater
复制到名为CustumFragmentPagerAdapter
的自定义类中 . 这使我有机会修改instantiateItem(...)
,以便每次调用它时,它会在添加从getItem()
方法接收的新片段之前删除/销毁当前附加的片段 .只需按以下方式修改
instantiateItem(...)
:你可以将两者结合起来更好:
Louth的解决方案不足以让我的工作正常,因为现有的碎片没有被破坏 . 由this answer激发,我发现解决方案是覆盖
FragmentPagerAdapter
的getItemId(int position)
方法,以便在片段的预期位置发生变化时提供新的唯一ID .源代码:
现在,例如,如果删除单个选项卡或对订单进行一些更改,则应在调用
notifyDataSetChanged()
之前调用notifyChangeInPosition(1)
,这将确保将重新创建所有片段 .为什么这个解决方案有效
Overriding getItemPosition():
调用
notifyDataSetChanged()
时,适配器调用它所连接的ViewPager
的notifyChanged()
方法 . 然后ViewPager
检查适配器的getItemPosition()
为每个项返回的值,删除那些返回POSITION_NONE
的项(参见source code),然后重新填充 .Overriding getItemId():
当
ViewPager
重新填充时,这是防止适配器重新加载旧片段所必需的 . 您可以通过在FragmentPagerAdapter
中查看the source code for instantiateItem()来轻松理解其原因 .如您所见,只有在片段管理器找不到具有相同Id的现有片段时,才会调用
getItem()
方法 . 对我而言,即使在调用notifyDataSetChanged()
之后,旧片段仍然附着似乎是一个错误,但ViewPager
的文档确实明确指出:因此,希望未来版本的支持库中没有必要使用此处提供的解决方法 .
劳斯的回答很好 . 但我不认为总是返回POSITION_NONE是个好主意 . 因为POSITION_NONE意味着应该销毁片段并创建一个新片段 . 您可以在ViewPager的源代码中检查dataSetChanged函数 .
所以我认为你最好使用weakReference的arraylist来保存你创建的所有片段 . 当您添加或删除某些页面时,您可以从自己的arraylist中获得正确的位置 .
根据注释,当主机视图尝试确定项目的位置是否已更改时,将调用getItemPosition . 返回值意味着它的新位置 .
但这不是必须的 . 我们仍然迈出重要的一步 . 在FragmentStatePagerAdapter的源代码中,有一个名为“mFragments”的数组缓存未被销毁的片段 . 并在instantiateItem函数中 .
它在发现缓存片段不为null时直接返回缓存片段 . 所以有一个问题 . 例如,让我们删除位置2处的一个页面 . 首先,我们从我们自己的参考arraylist中删除该片段 . 所以在getItemPosition中它将返回该片段的POSITION_NONE,然后该片段将被销毁并从“mFragments”中删除 .
现在位置3的片段将位于位置2.并且将调用具有参数位置3的instantiatedItem . 此时,“mFramgents”中的第三项不为null,因此它将直接返回 . 但实际上它返回的是位置2处的片段 . 所以当我们转到第3页时,我们会找到一个空页面那里 .
要解决这个问题 . 我的建议是你可以将FragmentStatePagerAdapter的源代码复制到你自己的项目中,当你添加或删除操作时,你应该添加和删除“mFragments”arraylist中的元素 .
如果您只使用PagerAdapter而不是FragmentStatePagerAdapter,事情就会变得更简单 . 祝好运 .
我的工作解决方案从视图寻呼机中删除片段页面
当我需要通过索引删除一些页面时,我会这样做
这是我的适配器初始化
该片段必须已被删除,但问题是viewpager保存状态
尝试
什么都没有用,但这解决了这个问题!
干杯!
我的最终版本代码,修复了所有错误 . 我花了3天时间
https://github.com/lin1987www/FragmentBuilder/blob/master/app/src/main/java/android/support/v4/app/FragmentStatePagerAdapterFix.java
我希望这可以帮助你想要的东西 .
尝试此解决方案 . 我已经使用数据绑定来绑定视图 . 您可以使用常见的“findViewById()”函数 .
ViewPager不会使用上面的代码删除您的片段,因为它会将多个视图(或您的案例中的片段)加载到内存中 . 除了可见视图外,它还将视图加载到可见视图的任一侧 . 这提供了从视图到视图的平滑滚动,使ViewPager非常酷 .
要达到您想要的效果,您需要做一些事情 .
将FragmentPagerAdapter更改为FragmentStatePagerAdapter . 这样做的原因是FragmentPagerAdapter将永久保存它加载到内存中的所有视图 . FragmentStatePagerAdapter处理位于当前和可遍历视图之外的视图 .
重写适配器方法getItemPosition(如下所示) . 当我们调用
mAdapter.notifyDataSetChanged();
时,ViewPager会询问适配器以确定在定位方面发生了哪些变化 . 我们使用此方法表示所有内容都已更改,因此重新处理所有视图定位 .这是代码......
我添加了一个函数“clearFragments”,我在设置新片段之前使用该函数清除了适配器 . 这称为片段的正确删除操作 . 我的pagerAdapter类:
您可以覆盖
destroyItem
方法