在 RecyclerView 的 ViewPager 片段中获取“java.lang.IllegalArgumentException: No view found for id”

Getting “java.lang.IllegalArgumentException: No view found for id” in ViewPager's Fragment that in RecyclerView

我遇到异常:

E/MessageQueue-JNI: java.lang.IllegalArgumentException: No view found for id 0x7f0a0554 (ua.com.company.app:id/vpBanners) for fragment BannerItemFragment{30698be} (4c80b228-4303-4c80-b99d-a55b8359b8c2) id=0x7f0a0554}

我的层次结构如下所示:

我的 vpHome 适配器:

class HomeViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

    private val mFragmentList: MutableList<Fragment> = ArrayList()
    private val mFragmentTitleList: MutableList<String> = ArrayList()

    override fun getItem(position: Int): Fragment {
        return mFragmentList[position]
    }

    override fun getCount(): Int {
        return mFragmentList.size
    }

    override fun getPageTitle(position: Int): CharSequence? {
        return mFragmentTitleList[position]
    }

    fun addFragment(fragment: Fragment, title: String) {
        mFragmentList.add(fragment)
        mFragmentTitleList.add(title)
    }

}

我是这样应用的:

private fun setupViewPager(viewPager: DisableSwipeViewPager) {
    vpAdapter = HomeViewPagerAdapter(childFragmentManager).apply {
        addFragment(ForYouFragment(), "for you")
        addFragment(AnotherFragment1(), "a1")
        addFragment(AnotherFragment2(), "a2")
    }
    viewPager.adapter = vpAdapter
}

接下来,我的SnapBannersCarouselViewHolder处理里面有ViewPager的项目:

class SnapBannersCarouselViewHolder(
    private val mBinding: HomeFragmentItemSnapBannersCarouselBinding
) : RecyclerView.ViewHolder(mBinding.root) {

    companion object {
        // ...

        fun newInstance(
            inflater: LayoutInflater,
            parent: ViewGroup,
            onMoreInteractionListener: ((infoBlockId: String) -> Unit)?
        ): SnapBannersCarouselViewHolder {
            val binding =
                HomeFragmentItemSnapBannersCarouselBinding.inflate(inflater, parent, false)
            return SnapBannersCarouselViewHolder(
                binding,
                onMoreInteractionListener
            )
        }
    }

    fun bind(item: SnapBannersItem, fragmentManager: FragmentManager) {
        // ...
        val adapter = BannerPagesAdapter(fragmentManager, item.banners)
      
        with(mBinding.vpBanners) {
            // ...
            this.adapter = adapter
            offscreenPageLimit = 3
        }
    }

}

我的 RecyclerView 适配器 ForYouContentAdapter:

class ForYouContentAdapter(
    var data: List<HomeBaseItem> = emptyList(),
    var fragmentManagerRetriever: () -> FragmentManager
) : BaseRecyclerViewAdapter<HomeBaseItem>(data) {

    enum class ViewType(val value: Int) {
        // ...
        SNAP_BANNERS(6)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return when (viewType) {
            // ...
            ViewType.SNAP_BANNERS.value -> SnapBannersCarouselViewHolder.newInstance(
                mInflater!!,
                parent,
                onBannersMoreInteractionListener // todo possibly change it
            )
            else -> throw RuntimeException("Can not create view holder for undefined view type")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        when (getItemViewType(position)) {
            // ...
            ViewType.SNAP_BANNERS.value -> {
                val vHolder = holder as SnapBannersCarouselViewHolder
                vHolder.bind(
                    getItem(position) as SnapBannersItem,
                    fragmentManagerRetriever.invoke()
                )
            }
        }
    }

}

我在 ForYouFragment 中的 fragmentManagerRetriever 实现如下所示:

private val fragmentManagerRetriever: () -> FragmentManager = {
    childFragmentManager
}

我的BannerItemFragment代码:

class BannerItemFragment : Fragment() {

    private lateinit var mBinding: HomeFragmentSnapBannerItemBinding

    companion object {
        // ...

        fun newInstance(
            item: RectWebViewItem
        ): BannerItemFragment {
            return BannerItemFragment()
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        mBinding = HomeFragmentSnapBannerItemBinding.inflate(inflater, container, false)
        return mBinding.root
    }

    // ...

}

在我需要在其他片段中创建片段的每个地方我都在使用 childFragmentManager

而当我第一次打开ForYouFragment时,它工作正常。我的带有 ViewPager 的项目正常工作。但是当我在 Activity 的容器中替换片段(添加到返回堆栈)时 ForYouFragment 然后 return 返回(返回 HomeFragment 因为 ForYouFragment 里面HomeFragment),我收到错误消息。

为了替换片段,我在 ForYouFragment:

中使用了这个方法
private fun showAnotherFragment() {
    ActivityUtils.replaceFragmentToActivity(
        requireActivity(),
        SomeFragment.newInstance(),
        true
    )
}

ActivityUtils代码:

object ActivityUtils {

    fun replaceFragmentToActivity(
            activity: FragmentActivity,
            fragment: Fragment,
            addToBackStack: Boolean
    ) {
        replaceFragmentToActivity(activity, fragment, addToBackStack, containerId = R.id.fragmentContainer)
    }

    fun replaceFragmentToActivity(
            activity: FragmentActivity,
            fragment: Fragment,
            addToBackStack: Boolean,
            containerId: Int
    ) {
        val fragmentManager = activity.supportFragmentManager
        val transaction = fragmentManager.beginTransaction()
        transaction.replace(containerId, fragment)
        if (addToBackStack) {
            transaction.addToBackStack(null)
        }
        transaction.commitAllowingStateLoss()
    }

}

请帮我理解为什么会出现此异常?

UPD

RecyclerView 中 ViewPager 的适配器:

class BannerPagesAdapter(
    fragmentManager: FragmentManager,
    var data: List<RectWebViewItem>
) : FragmentStatePagerAdapter(
    fragmentManager,
    BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {

    private var fragments: MutableList<BannerItemFragment> = mutableListOf()

    init {
        // initial empty fragments
        for (i in data.indices) {
            fragments.add(BannerItemFragment())
        }
    }

    override fun getItem(position: Int): Fragment {
        return BannerItemFragment.newInstance(data[position])
    }

    override fun getCount(): Int {
        return fragments.size
    }

    override fun instantiateItem(container: ViewGroup, position: Int): Any {
        val f = super.instantiateItem(container, position)
        fragments[position] = f as BannerItemFragment
        return f
    }

}

已解决

问题是片段想要在 ViewPager 附加到其父级之前附加到 ViewPager。这个问题概述了 here

所以,为了解决这个问题,我创建了自定义 ViewPager:

/**
 * Use this ViewPager when you need to place ViewPager inside RecyclerView.
 * [LazyViewPager] allows you to set [PagerAdapter] in lazy way. This prevents IllegalStateException
 * in case when the fragments want to be attached to the viewpager before the viewpager is
 * attached to its parent
 */
class LazyViewPager
@JvmOverloads
constructor(
    context: Context,
    attrs: AttributeSet? = null
) : ViewPager(context, attrs) {

    private var mPagerAdapter: PagerAdapter? = null

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        if (mPagerAdapter != null) {
            super.setAdapter(mPagerAdapter)
        }
    }

    override fun onDetachedFromWindow() {
        super.onDetachedFromWindow()
        super.setAdapter(null)
    }

    @Deprecated("Do not use this method to set adapter. Use setAdapterLazy() instead.")
    override fun setAdapter(adapter: PagerAdapter?) {}

    fun setAdapterLazy(adapter: PagerAdapter?) {
        mPagerAdapter = adapter
    }

}

然后,我没有使用 setAdapter(),而是使用 setAdapterLazy()

此外,在 onDetachedFromWindow() 中将适配器重置为 null 很重要。