ChatGPT解决这个技术问题 Extra ChatGPT

动态更新 ViewPager?

我无法更新 ViewPager 中的内容。

FragmentPagerAdapter 类中方法 instantiateItem() 和 getItem() 的正确用法是什么?

我只使用 getItem() 来实例化并返回我的片段:

@Override
public Fragment getItem(int position) {
    return new MyFragment(context, paramters);
}

这运作良好。除了我不能改变内容。

所以我发现了这个:ViewPager PagerAdapter not updating the View

“我的方法是对 instanceItem() 方法中的任何实例化视图使用 setTag() 方法”

现在我想实现 instantiateItem() 来做到这一点。但我不知道我必须返回什么(类型是 Object)以及与 getItem(int position) 的关系是什么?

我阅读了reference

public abstract Fragment getItem(int position) 返回与指定位置关联的 Fragment。 public Object instantiateItem (ViewGroup container, int position) 为给定位置创建页面。适配器负责将视图添加到此处给出的容器中,尽管它只必须确保在它从 finishUpdate(ViewGroup) 返回时完成此操作。参数 container 将在其中显示页面的包含视图。 position 要实例化的页面位置。返回 返回一个表示新页面的对象。这不需要是视图,但可以是页面的其他容器。

但我还是不明白。

这是我的代码。我正在使用支持包 v4。

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);
    }
}

我的片段适配器

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;
    }
}

我的片段

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;
    }
}

这里也有人有类似问题,没有答案http://www.mail-archive.com/android-developers@googlegroups.com/msg200477.html

为了更好地理解所涉及的组件,阅读我关于带有单独后退导航历史的选项卡式 ViewPager 的教程可能会有所帮助。它附带 GitHub 上的一个工作示例和其他资源:medium.com/@nilan/…
您可以检查此example in GitHub。我希望这个例子对你有所帮助。
很好的简单解决方案:stackoverflow.com/a/35450575/2162226
Google 最近推出了 ViewPager2,它简化了动态 Fragment/View 更新。您可以在 github.com/googlesamples/android-viewpager2 上查看示例或在 stackoverflow.com/a/57393414/1333448 上查看此答案

H
H. Saul

使用 FragmentPagerAdapter 或 FragmentStatePagerAdapter 时,最好只处理 getItem() 而根本不接触 instantiateItem()。 PagerAdapter 上的 instantiateItem()-destroyItem()-isViewFromObject() 接口是一个较低级别的接口,FragmentPagerAdapter 使用它来实现更简单的 getItem() 接口。

在进入这个之前,我应该澄清一下

如果要切换出正在显示的实际片段,则需要避免使用 FragmentPagerAdapter 并使用 FragmentStatePagerAdapter。

该答案的早期版本错误地将 FragmentPagerAdapter 用作其示例 - 这将不起作用,因为 FragmentPagerAdapter 在第一次显示后永远不会破坏片段。

我不推荐您链接的帖子中提供的 setTag()findViewWithTag() 解决方法。正如您所发现的,使用 setTag()findViewWithTag() 不适用于片段,因此它不是一个很好的匹配。

正确的解决方案是覆盖 getItemPosition()。调用 notifyDataSetChanged() 时,ViewPager 会在其适配器中的所有项目上调用 getItemPosition(),以查看它们是否需要移动到不同的位置或移除。

默认情况下,getItemPosition() 返回 POSITION_UNCHANGED,这意味着,“这个对象在它所在的地方很好,不要破坏或删除它。”返回 POSITION_NONE 解决了这个问题,而是说,“这个对象不再是我正在显示的项目,删除它。”因此,它具有删除和重新创建适配器中每个项目的效果。

这是一个完全合法的修复!此修复使 notifyDataSetChanged 的行为类似于常规适配器,无需视图回收。如果您实施此修复并且性能令人满意,那么您就可以参加比赛了。任务完成。

如果您需要更好的性能,可以使用更高级的 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;
        }
    }
});

使用此实现,只会显示显示新标题的片段。任何显示仍在列表中的标题的片段都将被移动到它们在列表中的新位置,并且标题不再在列表中的片段将被销毁。

如果片段尚未重新创建,但仍需要更新怎么办?对活动片段的更新最好由片段本身处理。毕竟,这就是拥有片段的优势——它是它自己的控制器。片段可以向 onCreate() 中的另一个对象添加侦听器或观察者,然后在 onDestroy() 中将其删除,从而自行管理更新。您不必像在 ListView 或其他 AdapterView 类型的适配器中那样将所有更新代码放在 getItem() 中。

最后一件事——仅仅因为 FragmentPagerAdapter 不破坏片段并不意味着 getItemPosition 在 FragmentPagerAdapter 中完全没用。您仍然可以使用此回调在 ViewPager 中重新排序您的片段。但是,它永远不会将它们完全从 FragmentManager 中删除。


不起作用,它仍然没有更新任何东西......发布了我的代码。
好的,找出问题所在 - 您需要使用 FragmentStatePagerAdapter 而不是 FragmentPagerAdapter。 FragmentPagerAdapter 永远不会从 FragmentManager 中删除片段,它只会分离和附加它们。查看文档以获取更多信息 - 通常,您只想将 FragmentPagerAdapter 用于永久片段。我已经用更正编辑了我的示例。
我将在稍后的某个时间对此进行审查...感谢您的回答。然后我接受你的回答,如果它有效。在您上次发表评论之前,我实施了每次删除和添加新的 ViewPager 并且有效。不是很好的解决方案,但我现在没有时间改变它。
没错,将适配器的类更改为 FragmentStatePagerAdapter 解决了我所有的麻烦。谢谢!
即使我在使用 FragmentStatePagerAdapter 时返回 POSITION_NONE,我也可以看到它删除了我的片段并创建了新实例。但是新实例会收到一个带有旧片段状态的 savedInstanceState Bundle。如何完全销毁片段,以便再次创建时 Bundle 为空?
M
M-Wajeeh

不要从 getItemPosition() 返回 POSITION_NONE 并重新创建完整视图,请执行以下操作:

//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);
}

您的片段应实现 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
         }
    }
}

和界面:

public interface UpdateableFragment {
   public void update(UpdateData xyzData);
}

您的数据类:

public class UpdateData {
    //whatever you want here
}

这是否意味着你的 MainActivity 也需要实现接口???请帮帮我。在我当前的实现中,我使用 MainActivity 类作为片段之间的通信器,所以我对这个解决方案感到困惑。
好的,我已经在另一个完全实现的地方回答了我动态更新 Fragment 的问题,看看:stackoverflow.com/questions/19891828/…
@M-WaJeEh 也许请举个例子!!!我有一个包含城市列表的列表视图,在选择城市后打开 viewpager(3 页),其中包含有关该城市的信息。它工作正常。但在选择另一个城市浏览器后,仅显示 3d page on refreshes the 1-st page only after swiping pages? thw 2d 页面始终为空白。谢谢
在我过去 2.5 天的工作生涯中看到的所有东西中..这是唯一对我有用的东西..各种 arigato,谢谢,spaciba,sukran .. n。
龙之母!,这就像一个魅力,唯一的解决方案对我有用......非常感谢!
R
Rahul Gaur

对于那些仍然面临与我之前遇到的相同问题的人,当我有一个包含 7 个片段的 ViewPager 时。这些片段的默认设置是从 API 服务加载英文内容,但这里的问题是我想从设置活动中更改语言,完成设置活动后,我希望主活动中的 ViewPager 刷新片段以匹配语言如果用户在这里选择阿拉伯语,则从用户中选择并加载阿拉伯语内容我从第一次开始所做的工作

1-您必须使用上面提到的 FragmentStatePagerAdapter。

2- 在 mainActivity 我覆盖 onResume 并做了以下

if (!(mPagerAdapter == null)) {
    mPagerAdapter.notifyDataSetChanged();
}

3-i 覆盖了 mPagerAdapter 中的 getItemPosition() 并使其返回 POSITION_NONE

@Override
public int getItemPosition(Object object) {
    return POSITION_NONE;
}

像魅力一样工作


除了在一种情况下工作。当您删除 viewPager 中的最后一项时。然后它不会从列表中删除。
我在 viewpager 中动态地将片段添加到当前片段的左侧和右侧。这个有效!谢谢。
J
JDJ

https://i.stack.imgur.com/COX7R.png

序列化的数据必须加载到内存中。这可以使用 CursorLoader/AsyncTask/Thread 来完成。是否自动加载取决于您的代码。如果您使用的是 CursorLoader,它会自动加载,因为有一个注册的数据观察者。在调用 viewpager.setAdapter(pageradapter) 之后,适配器的 getCount() 会不断被调用来构建片段。因此,如果正在加载数据,getCount() 可以返回 0,因此您无需为没有显示的数据创建虚拟片段。数据加载完成后,由于getCount()还是0,适配器不会自动构建分片,所以我们可以设置getCount()返回实际加载的数据个数,然后调用适配器的notifyDataSetChanged()。 ViewPager 开始通过内存中的数据创建片段(仅前 2 个片段)。它在 notifyDataSetChanged() 返回之前完成。然后 ViewPager 有你需要的正确片段。如果数据库和内存中的数据都被更新(直写),或者只是内存中的数据被更新(回写),或者只有数据库中的数据被更新。在最后两种情况下,如果数据没有自动从数据库加载到内存(如上所述)。 ViewPager 和分页适配器只处理内存中的数据。所以当内存中的数据更新时,我们只需要调用适配器的 notifyDataSetChanged() 即可。由于已经创建了fragment,所以在notifyDataSetChanged()返回之前会调用适配器的onItemPosition(),getItemPosition()不需要做任何事情,然后更新数据。


我发现更新数据(在内存中)后不需要做任何事情,只需调用 notifyDataSetChanged() 然后自动更新,因为我使用的片段(带有图表视图)会自行刷新。如果不是,就像一个静态片段,我将不得不调用 onItemPositon() 并返回 POSITION_NONE 对此感到抱歉
有谁知道用来制作该图表的工具是什么?
d
dakshbhatt21

在代码中的 notifyDataSetChanged() 之后在 ViewPager 上尝试 destroyDrawingCache()


同样在这里。很好用,谢谢。我有一个特别艰难的时期,因为我没有在我的视图寻呼机中使用 Fragments。所以谢谢!
C
Community

经过数小时的挫折,同时尝试了上述所有解决方案来克服这个问题,并尝试了许多其他类似问题的解决方案,例如 thisthisthis,但我都无法解决这个问题并做出 {4 } 来销毁旧的 Fragment 并用新的 Fragment 填充 pager。我已经解决了以下问题:

1) 使 ViewPager 类扩展 FragmentPagerAdapter,如下所示:

 public class myPagerAdapter extends FragmentPagerAdapter {

2) 为存储 titlefragmentViewPager 创建一个项目,如下所示:

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) 使 ViewPager 的构造函数将我的 FragmentManager 实例存储在我的 class 中,如下所示:

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mPagerItems = pagerItems;
}

4) 通过直接从 fragmentManager 本身删除所有以前的 fragment 来创建一个用新数据重新设置 adapter 数据的方法,以使 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) 从容器 ActivityFragment 中不要使用新数据重新初始化适配器。通过方法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();

我希望它有所帮助。


你能帮我解决我的问题吗stackoverflow.com/questions/40149039/…
你能帮我解决这个问题吗stackoverflow.com/questions/41547995/…
h
hordurh

出于某种原因,没有一个答案对我有用,所以我不得不重写 restoreState 方法而不在我的 fragmentStatePagerAdapter 中调用 super。代码:

private class MyAdapter extends FragmentStatePagerAdapter {

    // [Rest of implementation]

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {}

}

在尝试了这个 SO 答案中的所有其他内容之后,这对我有用。我正在完成()一个活动,该活动返回到承载相关 PagerAdapter 的活动。我希望在这种情况下重新创建我的所有片段,因为我刚刚完成的 Activity 可能已经改变了用于绘制片段的状态。
OMG .. 它有效.. 它有效.. 荣誉 :D 在我的情况下,我需要删除 viewpager 当前项目对象。但是在尝试了上述所有方法/步骤后,当前片段没有更新。
restoreStateFragmentStatePagerAdapter 的更高版本中被标记为最终版本。
是的,这个答案是6岁。从那时起,寻呼机的东西得到了改进。
P
Piyush

我稍微修改了比尔菲利普斯提供的解决方案以满足我的需要

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;
    } 
}

因此,当调用 getItemPosition 时,getItemPosition() 仅针对当前位于 FragmentManager 中的那些片段返回 POSITION_NONE。 (请注意,此 FragmentStatePager 和与之关联的 ViewPager 包含在 Fragment 中而不是 Activity 中)


j
jpm

我有一个类似的问题,但不想相信现有的解决方案(硬编码标签名称等),我无法让 M-WaJeEh 的解决方案为我工作。这是我的解决方案:

我将在 getItem 中创建的片段的引用保存在一个数组中。只要活动没有因配置更改或内存不足或其他原因而被破坏(-->返回活动时,片段返回到它们的最后状态而不会再次调用“getItem”,因此不会更新数组,这工作正常)。

为了避免这个问题,我实现了 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;
    }

所以,一方面我很高兴我找到了一个适合我的解决方案并想与你分享,但我也想问其他人是否尝试过类似的东西以及我是否有任何理由不应该这样做这样做吗?到目前为止,它对我来说非常好......


我在 stackoverflow.com/a/23427215/954643 中做了一些相关的事情,尽管您可能也应该在 public void destroyItem(ViewGroup container, int position, Object object) 中从 myFragments 中删除该项目,这样您就不会获取过时的片段引用。
n
nebyan

我也遇到过同样的问题,而且我搜索了太多次。在 stackoverflow 或通过谷歌给出的任何答案都不能解决我的问题。我的问题很简单。我有一个列表,我用 viewpager 显示这个列表。当我将一个新元素添加到列表的头部并刷新 viewpager 时,什么都没有改变。我的最终解决方案非常简单,任何人都可以使用。当一个新元素添加到列表中并且想要刷新列表时。首先将 viewpager 适配器设置为 null 然后重新创建适配器并将 i 设置为 viewpager。

myVPager.setAdapter(null);
myFragmentAdapter = new MyFragmentAdapter(getSupportFragmentManager(),newList);
myVPager.setAdapter(myFragmentAdapter);

确保您的适配器必须扩展 FragmentStatePagerAdapter


F
Fantasy Fang

我使用 EventBus 库来更新 ViewPager 中的 Fragment 内容。逻辑很简单,就像 document of EventBus how to do。无需控制 FragmentPagerAdapter 实例。代码在这里:

1:定义事件

定义需要更新的消息。

public class UpdateCountEvent {
    public final int count;

    public UpdateCountEvent(int count) {
        this.count = count;
    }
}

2.准备订阅者

在需要更新的片段中写入以下代码。

@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.发布活动

在需要更新参数的其他 Activity 或其他 Fragment 中编写以下代码

//input update message
EventBus.getDefault().post(new UpdateCountEvent(count));

T
Ted Hsieh

我一直在尝试很多不同的方法,但没有一个能真正解决我的问题。以下是我如何使用你们提供的各种解决方案来解决它。感谢大家。

class PagerAdapter extends FragmentPagerAdapter {

    public boolean flag_refresh=false;

    public PagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int page) {
        FragmentsMain f;
        f=new FragmentsMain();
        f.page=page;
        return f;
    }

    @Override
    public int getCount() {
        return 4;
    }

    @Override
    public int getItemPosition(Object item) {
        int page= ((FragmentsMain)item).page;

        if (page == 0 && flag_refresh) {
            flag_refresh=false;
            return POSITION_NONE;
        } else {
            return super.getItemPosition(item);
        }
    }

    @Override
    public void destroyItem(View container, int position, Object object) {

        ((ViewPager) container).removeView((View) object);
    }
}

我只想在 onResume() 之后刷新第 0 页。

 adapter=new PagerAdapter(getSupportFragmentManager());
 pager.setAdapter(adapter);

@Override
protected void onResume() {
    super.onResume();

    if (adapter!=null) {
        adapter.flag_refresh=true;
        adapter.notifyDataSetChanged();
    }
}

在我的 FragmentsMain 中,有一个公共整数“页面”,它可以告诉我是否是我要刷新的页面。

public class FragmentsMain extends Fragment {

private Cursor cursor;
private static Context context;
public int page=-1;

t
theapache64

我知道派对迟到了。我通过在 FragmentPagerAdapter#notifyDataSetChanged(); 之后调用 TabLayout#setupWithViewPager(myViewPager); 解决了这个问题


c
cgr

如果您想使用 FragmentStatePagerAdapter,请查看 https://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=37990。 FragmentStatePagerAdapter 存在可能会或可能不会给您的用例带来麻烦的问题。

此外,链接也很少有解决方案..很少有可能适合您的要求。


从那个 URL 我找到了解决方案。我在我的代码中使用了这个 gist gist.github.com/ypresto/8c13cb88a0973d071a64 中修改后的 FragmentStatePagerAdapter,它开始按预期工作。
凉爽的。是的。链接也有几个解决方案。
M
Meanman

这可能对某人有帮助 - 在我的情况下,当插入新页面时,视图寻呼机两次询问现有片段的位置,但不询问新项目的位置,导致不正确的行为和数据不显示。

复制 FragmentStatePagerAdapter 的源代码(似乎很久没有更新了)。覆盖 notifyDataSetChanged() @Override public void notifyDataSetChanged() { mFragments.clear(); super.notifyDataSetChanged();向 destroyItem() 添加完整性检查以防止崩溃: if (position < mFragments.size()) { mFragments.set(position, null); }


v
voam

这是我的实现,它结合了来自@Bill Phillips One 的信息,大多数情况下都会获取片段缓存,除非数据发生了变化。很简单,而且似乎工作正常。

MyFragmentStatePagerAdapter.java

private boolean mIsUpdating = false;
public void setIsUpdating(boolean mIsUpdating) {
        this.mIsUpdating = mIsUpdating;
    }


@Override
public int getItemPosition(@NonNull Object object) {

    if (mIsUpdating) {
        return POSITION_NONE;
    }
    else {
        return super.getItemPosition(object);
    }
}

我的活动.java

mAdapter.setIsUpdating(true);
mAdapter.notifyDataSetChanged();
mAdapter.setIsUpdating(false);

K
Kevin Grant

此解决方案不适用于所有人,但就我而言,我的 ViewPager 中的每个 Fragment 都是不同的类,并且一次只存在一个。

有了这个约束,这个解决方案是安全的,应该可以安全地在生产中使用。

private void updateFragment(Item item) {

    List<Fragment> fragments = getSupportFragmentManager().getFragments();
    for (Fragment fragment : fragments) {

        if (fragment instanceof MyItemFragment && fragment.isVisible()) {
            ((MyItemFragment) fragment).update(item);
        }

    }
}

如果您有同一个片段的多个版本,您可以使用相同的策略来调用这些片段上的方法,以确定它是否是您要更新的片段。


J
John O'Reilly

我已经浏览了上面的所有答案和其他一些帖子,但仍然找不到适合我的东西(具有不同的片段类型以及动态添加和删除选项卡)。 FWIW 以下方法对我有用(以防其他人有同样的问题)。

public class MyFragmentStatePageAdapter extends FragmentStatePagerAdapter {
    private static final String TAB1_TITLE = "Tab 1";
    private static final String TAB2_TITLE = "Tab 2";
    private static final String TAB3_TITLE = "Tab 3";

    private ArrayList<String> titles = new ArrayList<>();
    private Map<Fragment, Integer> fragmentPositions = new HashMap<>();


    public MyFragmentStatePageAdapter(FragmentManager fm) {
        super(fm);
    }


    public void update(boolean showTab1, boolean showTab2, boolean showTab3) {
        titles.clear();

        if (showTab1) {
            titles.add(TAB1_TITLE);
        }
        if (showTab2) {
            titles.add(TAB2_TITLE);
        }
        if (showTab3) {
            titles.add(TAB3_TITLE);
        }
        notifyDataSetChanged();
    }


    @Override
    public int getCount() {
        return titles.size();
    }

    @Override
    public Fragment getItem(int position) {
        Fragment fragment = null;

        String tabName = titles.get(position);
        if (tabName.equals(TAB1_TITLE)) { 
            fragment =  Tab1Fragment.newInstance();
        } else if (tabName.equals(TAB2_TITLE)) {
            fragment =  Tab2Fragment.newInstance();
        } else if (tabName.equals(TAB3_TITLE)) {
            fragment =  Tab3Fragmen.newInstance();
        } 

        ((BaseFragment)fragment).setTitle(tabName);
        fragmentPositions.put(fragment, position);
        return fragment;
    }

    @Override
    public CharSequence getPageTitle(int position) {
        return titles.get(position);
    }

    @Override
    public int getItemPosition(Object item) {
        BaseFragment fragment = (BaseFragment)item;
        String title = fragment.getTitle();
        int position = titles.indexOf(title);

        Integer fragmentPosition = fragmentPositions.get(item);
        if (fragmentPosition != null && position == fragmentPosition) {
            return POSITION_UNCHANGED;
        } else {
            return POSITION_NONE;
        }
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
        fragmentPositions.remove(object);
    }
}

H
HemangNirmal

如果要基于索引重新创建或重新加载片段,请使用 FragmentStatePagerAdapter 而不是 FragmentPagerAdapter 例如,如果要重新加载 FirstFragment 以外的片段,可以像这样检查实例并返回位置

 public int getItemPosition(Object item) {
    if(item instanceof FirstFragment){
       return 0;
    }
    return POSITION_NONE;
 }

c
changhao cui

您需要更改 instantiateItem 的 mFragments 元素 getItemPosition。

    if (mFragments.size() > position) {
        Fragment f = mFragments.get(position);

        if (f != null) {
            int newPosition = getItemPosition(f);
            if (newPosition == POSITION_UNCHANGED) {
                return f;
            } else if (newPosition == POSITION_NONE) {
                mFragments.set(position, null);
            } else {
                mFragments.set(newPosition, f);
            }
        }
    }

基于 AndroidX FragmentStatePagerAdapter.java,因为 mFragments 的元素位置在调用 notifyDataSetChanged() 时不会改变。

来源:https://github.com/cuichanghao/infivt/blob/master/library/src/main/java/cc/cuichanghao/library/FragmentStatePagerChangeableAdapter.java

示例:https://github.com/cuichanghao/infivt/blob/master/app/src/main/java/cc/cuichanghao/infivt/MainActivityChangeablePager.kt

您可以运行此项目以确认如何工作。 https://github.com/cuichanghao/infivt


j
jacoballenwood

使用 ViewPager2FragmentStateAdapter

ViewPager2 支持动态更新数据。

docs 中有一条关于如何使其工作的重要说明:

注意:DiffUtil 实用程序类依赖于通过 ID 识别项目。如果您使用 ViewPager2 对可变集合进行分页,则还必须覆盖 getItemId() 和 containsItem()。 (强调我的)

基于 ViewPager2 documentation 和 Android 的 Github sample project,我们需要采取一些步骤:

设置 FragmentStateAdapter 并覆盖以下方法:getItemCount、createFragment、getItemId 和 containsItem(注意:ViewPager2 不支持 FragmentStatePagerAdapter) 将适配器附加到 ViewPager2 使用 DiffUtil 将列表更新调度到 ViewPager2(不需要使用 DiffUtil,如示例项目)

例子:

private val items: List<Int>
    get() = viewModel.items
private val viewPager: ViewPager2 = binding.viewPager

private val adapter = object : FragmentStateAdapter(this@Fragment) {
    override fun getItemCount() = items.size
    override fun createFragment(position: Int): Fragment = MyFragment()
    override fun getItemId(position: Int): Long = items[position].id
    override fun containsItem(itemId: Long): Boolean = items.any { it.id == itemId }
}
viewPager.adapter = adapter
    
private fun onDataChanged() {
    DiffUtil
        .calculateDiff(object : DiffUtil.Callback() {
            override fun getOldListSize(): Int = viewPager.adapter.itemCount
            override fun getNewListSize(): Int = viewModel.items.size
            override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
                viewPager.adapter?.getItemId(oldItemPosition) == viewModel.items[newItemPosition].id

            override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
                areItemsTheSame(oldItemPosition, newItemPosition)
        }, false)
        .dispatchUpdatesTo(viewPager.adapter!!)
}

问题是在 Java 中为什么要在 Kotlin 中提出解决方案?