首页 文章

动态更新ViewPager?

提问于
浏览
280

我无法更新ViewPager中的内容 .

一个问题:FragmentPagerAdapter类中的方法instantiateItem()和getItem()的关系和正确用法是什么?

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

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

这很好用(但据说我不能改变内容) .

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

特别是在其中讨论了方法instantiateItem():

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

所以现在我想实现instantiateItem()才能做到这一点 . 但我不知道我必须返回那里(return是Object)和getItem(int position)的关系是什么?

目前我正在使用getItem来实例化Fragment,这是错的吗?但是,我是否必须将片段放在实例变量中,或者根本不实现getItem()?我只是不明白 .

我试过阅读reference

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

......但我仍然不明白它们是如何相关的以及我必须做些什么 .

这是我的代码 . 我正在使用支持包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);
    }
}

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

这也是有类似问题的人,没有答案......

http://www.mail-archive.com/android-developers@googlegroups.com/msg200477.html

18 回答

  • 0

    如果您想使用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存在问题,可能会或可能不会对您的用例造成影响 .

    此外,链接也没有什么解决方案..这可能适合您的要求 .

  • 0

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

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

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

  • 10

    使用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 ,这意味着,"This object is fine where it is, don't destroy or remove it."返回 POSITION_NONE 修复问题,而不是说,"This object is no longer an item I'm displaying, remove it."因此它具有删除和重新创建适配器中每个项目的效果 .

    这是一个完全合法的修复!此修复使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;
            }
        }
    });
    

    通过此实现,将仅显示显示新 Headers 的片段 . 显示仍在列表中的 Headers 的任何片段将被移动到列表中的新位置,并且将销毁具有不再在列表中的 Headers 的片段 .

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

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

  • 7

    而不是从 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
    }
    
  • 12

    对于那些仍然面对的人当我有一个包含7个片段的ViewPager时,我遇到的同样的问题 . 这些片段的默认值是从api服务加载英语内容但这里的问题是我想从设置活动更改语言,在完成设置活动之后我希望主活动中的ViewPager刷新片段以匹配来自用户的语言选择如果用户在这里选择了阿拉伯语,我会在第一次工作时加载阿拉伯语内容

    1- You must use FragmentStatePagerAdapter as mentioned above.

    2-on mainActivity i覆盖onResume并执行以下操作

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

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

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

    像魅力一样工作

  • 3

    我遇到了这个问题,今天终于解决了,所以我写下了我所学到的东西,希望对于那些刚接触Android的 ViewPager 并像我一样更新的人有所帮助 . 我在API级别17中使用 FragmentStatePagerAdapter ,目前只有2个片段 . 我认为肯定有些事情不对,请纠正我,谢谢 .
    enter image description here

    • 必须将序列化数据加载到内存中 . 这可以使用 CursorLoader / AsyncTask / Thread 来完成 . 它是否自动加载取决于您的代码 . 如果您使用的是 CursorLoader ,则会自动加载,因为有一个已注册的数据观察者 .

    • 调用 viewpager.setAdapter(pageradapter) 后,将不断调用适配器的 getCount() 来构建片段 . 因此,如果正在加载数据, getCount() 可以返回0,因此您不需要为没有显示的数据创建虚拟片段 .

    • 加载数据后,由于 getCount() 仍为0,适配器不会自动构建片段,因此我们可以设置 getCount() 返回的实际加载的数据编号,然后调用适配器的 notifyDataSetChanged() . ViewPager 开始按内存中的数据创建片段(只是前2个片段) . 它在 notifyDataSetChanged() 返回之前完成 . 然后 ViewPager 拥有您需要的正确片段 .

    • 如果数据库和内存中的数据都已更新(直写),或者内存中的数据已更新(写回),或者只更新数据库中的数据 . 在最后两种情况下,如果数据没有自动从数据库加载到内存(如上所述) . ViewPager 和寻呼机适配器只处理内存中的数据 .

    • 因此,当内存中的数据更新时,我们只需要调用适配器的 notifyDataSetChanged() . 由于片段已经创建,因此在 notifyDataSetChanged() 返回之前将调用适配器的 onItemPosition() . 在 getItemPosition() 中无需做任何事情 . 然后更新数据 .

  • 14

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

  • 1

    经过几个小时的挫折,同时尝试所有上述解决方案,以解决这个问题,并尝试许多其他类似问题的解决方案,如thisthisthis这些都与我失败解决这个问题,并使 ViewPager 摧毁旧的 Fragment 并填写 pager 与新的 Fragment . 我已经解决了以下问题:

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

    public class myPagerAdapter extends FragmentPagerAdapter {
    

    2)ViewPager 创建一个项目,用于存储 titlefragment ,如下所示:

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

    我希望它有所帮助 .

  • 1

    由于某些原因,没有一个答案对我有用,所以我必须覆盖restoreState方法而不在我的fragmentStatePagerAdapter中调用super . 码:

    private class MyAdapter extends FragmentStatePagerAdapter {
    
        // [Rest of implementation]
    
        @Override
        public void restoreState(Parcelable state, ClassLoader loader) {}
    
    }
    
  • 0

    我稍微修改了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;
        } 
    }
    

    这样 getItemPosition() 只返回当调用 getItemPosition 时当前在 FragmentManager 中的那些片段的 POSITION_NONE . (请注意,此 FragmentStatePager 和与之关联的 ViewPager 包含在不在活动中的片段中)

  • 0

    我有类似的问题,但不想信任现有的解决方案(硬编码标签名称等),我不能让M-WaJeEh的解决方案适合我 . 这是我的解决方案:

    我保留了对数组中getItem中创建的片段的引用 . 只要活动没有因为configurationChange或缺少内存或其他原因而被破坏( - >当返回活动时,片段返回到它们的最后状态而没有再次调用'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;
        }
    

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

  • 17

    我使用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.Prepare订阅者

    在片段中写下需要更新的代码 .

    @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事件

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

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

    我一直在尝试这么多不同的方法,没有一个能真正解决我的问题 . 以下是我通过大家提供的各种解决方案解决问题的方法 . 感谢大家 .

    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;
    
  • 564

    我知道党迟到了 . 我在 FragmentPagerAdapter#notifyDataSetChanged(); 之后调用 TabLayout#setupWithViewPager(myViewPager); 修复了问题

  • 2

    这个解决方案对每个人都不起作用,但就我而言,我的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);
            }
    
        }
    }
    

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

  • 0

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

    • 复制FragmentStatePagerAdapter的源代码(好像很久没有更新) .

    • 覆盖notifyDataSetChanged()

    @Override
    public void notifyDataSetChanged() {
        mFragments.clear();
        super.notifyDataSetChanged();
    }
    
    • 为destroyItem()添加一个完整性检查以防止崩溃:
    if (position < mFragments.size()) {
        mFragments.set(position, null);
    }
    
  • 0

    我已经完成了上面的所有答案和其他一些帖子,但仍然找不到适合我的东西(使用不同的片段类型以及动态添加和删除标签) . 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);
        }
    }
    
  • 130

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

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

相关问题