NotifyDataSetChanged 在带有 Viewpager2 的 FragmentStateAdapter 中不起作用

Posted

技术标签:

【中文标题】NotifyDataSetChanged 在带有 Viewpager2 的 FragmentStateAdapter 中不起作用【英文标题】:NotifyDataSetChanged not working in FragmentStateAdapter with Viewpager2 【发布时间】:2020-11-02 22:41:51 【问题描述】:

我正在使用带有 FragmentStateAdapter 的 viewpager2 创建一个文件选择页面。在其中一个页面中,我显示了所有已安装的存储设备,我想在这些设备上点击以查看内容,但我希望内容位于另一个片段上。

我想在 viewpager 的最后一页中用另一个片段替换片段。但是使用下面的代码,页面只是刷新并且没有创建新的 Fragment,因为 createFragment 没有被调用。

我的 PagerAdapter

public static class PagerAdapter extends FragmentStateAdapter 


        private Fragment mFragmentAtPos0;
        private final FragmentManager mFragmentManager;
        private final FirstPageListener listener = new FirstPageListener();
        public final class FirstPageListener implements
                FirstPageFragmentListener 
            public void onSwitchToNextFragment() 
                mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
                        .commit();
                if (mFragmentAtPos0 instanceof FilesandFolder_Others_MainPage)
                    mFragmentAtPos0 = new FileExplorer(listener);
                else // Instance of NextFragment
                    mFragmentAtPos0 = new FilesandFolder_Others_MainPage(listener);
                
                notifyDataSetChanged();
            
            public void onSwitchToNextFragment(Bundle bundle) 
//                mFragmentManager.beginTransaction().remove(mFragmentAtPos0)
//                        .commit();
                if (mFragmentAtPos0 instanceof FilesandFolder_Others_MainPage)
                    mFragmentAtPos0 = new FileExplorer(listener);
                    mFragmentAtPos0.setArguments(bundle);
                else // Instance of NextFragment
                    mFragmentAtPos0 = new FilesandFolder_Others_MainPage(listener);
                    mFragmentAtPos0.setArguments(bundle);
                
                notifyDataSetChanged();
//                notifyItemChanged(getItemPosition(mFragmentAtPos0));
            
        


//        @Override
//        public void onBindViewHolder(@NonNull FragmentViewHolder holder, int position, @NonNull List<Object> payloads) 
//            super.onBindViewHolder(holder, position, payloads);
//            if (position == getItemPosition(mFragmentAtPos0)) 
//
//                Fragment f = mFragmentManager.findFragmentByTag("f" + holder.getItemId());
//                if (f != null) 
//                    mFragmentManager.beginTransaction().replace(holder.getItemId(), )
//                
//            
//        

        private int getItemPosition(Fragment mFragmentAtPos0) 
            for (int i = 0; i < getItemCount(); i++)

                if (createFragment(i).equals(mFragmentAtPos0))
                    return i;
                
            
            return -1;
        


        public PagerAdapter(FragmentActivity fm) 
            super(fm);
            mFragmentManager = fm.getSupportFragmentManager();
            mFragmentAtPos0 = new FilesandFolder_Others_MainPage(listener);
        

        @NonNull
        @Override
        public Fragment createFragment(int position) 
            switch (position)
                case 0:
                    return new AppSelectionFragment();
                case 1:
                    return new Photos();
                case 2:
                    return new VideoGalleryFragment();
                case 3:
                    return new Gallery();
                default:
                    return mFragmentAtPos0;
            
        

        @Override
        public int getItemCount() 
            return 5;
        
    

这是我想替换为另一个 Fragment 的 Fragment 的构造函数:

    private static FileSelection.PagerAdapter.FirstPageListener pageFragmentListener;

    public FilesandFolder_Others_MainPage() 
    
    public FilesandFolder_Others_MainPage(FileSelection.PagerAdapter.FirstPageListener firstPageFragmentListener) 
        pageFragmentListener = firstPageFragmentListener;
    

点击视图, 应该调用以下代码:-

Bundle bundle = new Bundle();
bundle.putString("PATH", volumes.get(position).path);
pageFragmentListener.onSwitchToNextFragment(bundle);

我已经在 *** 上进行了搜索,但从来没有看到使用 viewpager 2 和 FragmentStateAdapter 的正确答案,因为每个人似乎都在使用 Viewpager

我试图实现这个:- FragmentStateAdapter for ViewPager2 notifyItemChanged not working as expected

但不明白如何编辑我的代码。请帮忙 提前致谢

【问题讨论】:

我也在尝试这样做。你解决问题了吗? 【参考方案1】:

遇到了同样的问题,这就是我发现的:

FragmentStateAdapter 有方法onBindViewHolder(holder, position, payloads),你可以覆盖它(不像onBindViewHolder(holder, position) 是最终的)。这个方法在你调用notify方法时调用,对于notifyItemChanged()notifyItemRangeChanged()方法你可以访问片段,并手动更新它。

这是访问片段的代码:

public void onBindViewHolder(@NonNull FragmentViewHolder holder, int position, @NonNull List<Object> payloads) 

    String tag = "f" + holder.getItemId();

    Fragment fragment = fragmentManager.findFragmentByTag(tag);

    if (fragment != null) 
        //manual update fragment
     else 
        // fragment might be null, if it`s call of notifyDatasetChanged() 
        // which is updates whole list, not specific fragment
        super.onBindViewHolder(holder, position, payloads);
    

FragmentStateAdapter 的片段被"f" + holder.getItemId() 标记,这就是它起作用的原因。

您也可以使用DiffUtil,这是更好的方法。解决这个问题的Here is good example,还有一些链接到底是为了更好的理解ViewPager2

【讨论】:

很高兴知道这个onBindViewHolder(...) 方法存在并且可以像这样被覆盖以更新ViewPager2 中的片段。任何使用这种方法的人都必须考虑到它依赖于FragmentStateAdapter 的实现细节,即片段被添加到带有标签"f" + holder.getItemId()FragmentManager 的事实。您应该使用一些 UI 测试覆盖您的应用程序,以便在开发时检测此实现细节是否在 FragmentStateAdapter 类的未来版本中发生更改。 是的,这不安全。也许如果手动比较片段标签而不是使用fragmentManager.findFragmentByTag("f" + holder.getItemId()) 将是安全的。 在我的情况下,我有一个 FragmentActivity,你知道我怎么还能让它工作吗?【参考方案2】:

我今天遇到了同样的问题,并想在这里分享我的解决方案。我的解决方案不是拦截onBindViewHolder(),而是简单地替换旧片段:

OldFragment.instance.containerId?.let 
    replace(it, NewFragment.instance)

更具体一点:

我的应用有一个带有两个片段的 ViewPager2。我们称他们为MainFragmentDetailFragment。在MainFragment 中单击按钮时,我想将其替换为SecondMainFragment。反之亦然:点击SecondMainFragment 中的按钮将替换为MainFragment

class CustomFragmentStateAdapter(
    private val fragmentActivity: FragmentActivity
) : FragmentStateAdapter(fragmentActivity) 

    var displaySecondMainFragment: Boolean = false
        set(value) 
            if (field != value) 
                switchFragments(replaceWithSecondMainFragment = value)
            
            field = value
        

    private fun switchFragments(replaceWithSecondMainFragment: Boolean) 
        fragmentActivity.supportFragmentManager.commit 
            if (replaceWithSecondMainFragment) 
                MainFragment.instance.containerId?.let 
                    replace(it, SecondMainFragment.instance)
                
             else 
                SecondMainFragment.instance.containerId?.let 
                    replace(it, MainFragment.instance)
                
            
        
    
        
    // Standard adapter overrides
    override fun getItemCount(): Int = 2

    override fun createFragment(position: Int): Fragment = when (position) 
        0 -> if (displaySecondMainFragment) SecondMainFragment.instance else MainFragment.instance
        else -> DetailFragment.instance
     

使用这个扩展属性来获取片段所在容器的id:

inline val Fragment.containerId: Int?
    get() = (view?.parent as? ViewGroup?)?.id

最后我只需要这样做:

button.setOnClickListener 
    customFragmentStateAdapter.displaySecondMainFragment = true // Or false for the other way

【讨论】:

【参考方案3】:

你可以覆盖这两个方法

    override fun getItemId(position: Int): Long 
        // generate new id
        return getItem(position).hashCode().toLong()
    

    override fun containsItem(itemId: Long): Boolean 
        // false if item is changed
        return dataList.find  it.hashCode().toLong() == itemId  != null
    

【讨论】:

以上是关于NotifyDataSetChanged 在带有 Viewpager2 的 FragmentStateAdapter 中不起作用的主要内容,如果未能解决你的问题,请参考以下文章

调用 notifyDataSetChanged 时未更新带有 customAdapter 的 ListView?

调用 notifydatasetchanged 后 Recyclerview 滚动到顶部

notifyDataSetChanged 不适用于 RecyclerView

使用数据作为 Map 的 notifyDataSetChanged 后 Android ListView 不刷新

带有 CheckBox 的 ListView 的奇怪行为

notifyDataSetChanged 示例