问题描述
我想动态添加和删除从ViewPager碎片,没有任何问题,增加工程,但除去没有按预期工作。
每当我想删除目前的项目,最后一个被删除。
我也试过使用FragmentStatePagerAdapter或适配器的getItemPosition方法返回POSITION_NONE。
我是什么做错了吗?
下面是一个基本的例子:
MainActivity.java
公共类MainActivity扩展FragmentActivity实现TextProvider {
私人按钮MADD;
私人按钮mRemove;
私人ViewPager mPager;
私人MyPagerAdapter mAdapter;
私人的ArrayList<字符串> mEntries =新的ArrayList<字符串>();
@覆盖
公共无效的onCreate(包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 =(按钮)findViewById(R.id.add);
mRemove =(按钮)findViewById(R.id.remove);
mPager =(ViewPager)findViewById(R.id.pager);
mAdd.setOnClickListener(新OnClickListener(){
@覆盖
公共无效的onClick(视图查看){
addNewItem();
}
});
mRemove.setOnClickListener(新OnClickListener(){
@覆盖
公共无效的onClick(视图查看){
removeCurrentItem();
}
});
mAdapter =新MyPagerAdapter(this.getSupportFragmentManager(),这一点);
mPager.setAdapter(mAdapter);
}
私人无效addNewItem(){
mEntries.add(新项目);
mAdapter.notifyDataSetChanged();
}
私人无效removeCurrentItem(){
INT位置= mPager.getCurrentItem();
mEntries.remove(位置);
mAdapter.notifyDataSetChanged();
}
@覆盖
公共字符串getTextForPosition(INT位置){
返回mEntries.get(位置);
}
@覆盖
公众诠释getCount将(){
返回mEntries.size();
}
私有类MyPagerAdapter扩展FragmentPagerAdapter {
私人TextProvider mProvider;
公共MyPagerAdapter(FragmentManager FM,TextProvider提供商){
超(FM);
this.mProvider =提供商;
}
@覆盖
公共片段的getItem(INT位置){
返回MyFragment.newInstance(mProvider.getTextForPosition(位置));
}
@覆盖
公众诠释getCount将(){
返回mProvider.getCount();
}
}
}
TextProvider.java
公共接口TextProvider {
公共字符串getTextForPosition(INT位);
公众诠释getCount将();
}
MyFragment.java
公共类MyFragment扩展片段{
私人字符串行文字;
公共静态MyFragment的newInstance(字符串文本){
MyFragment F =新MyFragment(文本);
返回F;
}
公共MyFragment(){
}
公共MyFragment(字符串文本){
this.mText =文本;
}
@覆盖
公共查看onCreateView(LayoutInflater充气,容器的ViewGroup,
捆绑savedInstanceState){
查看根= inflater.inflate(R.layout.fragment,集装箱,假);
((TextView中)root.findViewById(R.id.position))的setText(多行文字)。
返回根;
}
}
activity_main.xml
< XML版本=1.0编码=UTF-8&GT?;
< LinearLayout中的xmlns:机器人=http://schemas.android.com/apk/res/android
机器人:layout_width =match_parent
机器人:layout_height =match_parent
机器人:方向=垂直>
<按钮
机器人:ID =@ + ID /加
机器人:layout_width =match_parent
机器人:layout_height =WRAP_CONTENT
机器人:文本=添加新项/>
<按钮
机器人:ID =@ + ID /删除
机器人:layout_width =match_parent
机器人:layout_height =WRAP_CONTENT
机器人:文本=删除当前项目/>
< android.support.v4.view.ViewPager
机器人:ID =@ + ID /寻呼机
机器人:layout_width =match_parent
机器人:layout_height =0dip
机器人:layout_weight =1/>
< / LinearLayout中>
fragment.xml之
< XML版本=1.0编码=UTF-8&GT?;
< LinearLayout中的xmlns:机器人=http://schemas.android.com/apk/res/android
机器人:layout_width =match_parent
机器人:layout_height =match_parent
机器人:方向=垂直>
<的TextView
机器人:ID =@ + ID /位置
机器人:layout_width =match_parent
机器人:layout_height =match_parent
机器人:重力=中心
机器人:TEXTSIZE =35SP/>
< / LinearLayout中>
由劳斯的解决方案是不够的,得到的东西为我工作,因为现有的片段不被破坏掉了。通过这个答案的启发,我发现,解决的办法是覆盖 getItemId(INT位置)
FragmentPagerAdapter 赋予了新的唯一ID时出现了一个片段的预期位置的变化。
来源$ C $ C:
私有类MyPagerAdapter扩展FragmentPagerAdapter {
私人TextProvider mProvider;
私人长了baseid = 0;
公共MyPagerAdapter(FragmentManager FM,TextProvider提供商){
超(FM);
this.mProvider =提供商;
}
@覆盖
公共片段的getItem(INT位置){
返回MyFragment.newInstance(mProvider.getTextForPosition(位置));
}
@覆盖
公众诠释getCount将(){
返回mProvider.getCount();
}
//当notifyDataSetChanged(这就是所谓的)被称为
@覆盖
公众诠释getItemPosition(Object对象){
//刷新当数据集改变了所有的碎片
返回PagerAdapter.POSITION_NONE;
}
@覆盖
众长getItemId(INT位置){
//给一个ID从不同位置时的位置已经改变
返回baseid之后+位置;
}
/ **
*通知片段的位置已经改变。
*创建一个新的ID为每个位置强制片段休闲
项目已改变* @参数n个
* /
公共无效notifyChangeInPosition(INT N){
//所有previous片段的范围之外后移getItemId返回的ID
baseid之后+ = getCount将()+ N;
}
}
现在,例如,如果你删除一个选项卡,或做出一些改变的顺序,你应该叫
notifyChangeInPosition(1)
之前调用 notifyDataSetChanged ()
,这将确保所有的片段将被重新创建。
为什么这个解决方案的工作原理
重写getItemPosition():
在
,它连接到。该 notifyDataSetChanged()
被调用时,适配器调用<$ C $的 notifyChanged()
方法C> ViewPager ViewPager
然后检查由适配器的 getItemPosition()
为每个项目,除去那些返回项目<$返回的值C $ C> POSITION_NONE (见source code ),然后重新填充。
重写getItemId():
这是必要的,以prevent从重装时, ViewPager
被重新填充旧片段适配器。你可以很容易理解为什么这个工程通过看the对于instantiateItem()的源代码code FragmentPagerAdapter
。
最后长的itemId = getItemId(位置);
//我们是否已经有这样的片段?
字符串名称= makeFragmentName(container.getId()的itemId);
片段片段= mFragmentManager.findFragmentByTag(名称);
如果(片段!= NULL){
如果(调试)Log.v(TAG,附加项#+的itemId +:F =+片段);
mCurTransaction.attach(片段);
} 其他 {
片段=的getItem(位置);
如果(调试)Log.v(TAG,添加项目#+的itemId +:F =+片段);
mCurTransaction.add(container.getId(),片段,
makeFragmentName(container.getId()的itemId));
}
正如你所看到的,的getItem()
方法仅在片段管理器发现不存在碎片用相同的ID叫。对我来说,它看起来像旧片段仍连接,即使 notifyDataSetChanged()
被称为一个错误,但对于 ViewPager 并明确指出:
所以希望这里给出的解决方法将不再需要在支持库的未来版本。
I'm trying to dynamically add and remove Fragments from a ViewPager, adding works without any problems, but removing doesn't work as expected.
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.
What am I doing wrong?
Here's a basic example:
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>
解决方案
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.
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.
Why this solution works
Overriding 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.
Overriding 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 code for instantiateItem() in 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:
So hopefully the workaround given here will not be necessary in a future version of the support library.
这篇关于从ViewPager Android中删除片段页的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!