首页 > 解决方案 > 在 RecyclerView 中的 ViewPager 片段中获取“java.lang.IllegalArgumentException: No view found for id”

问题描述

我得到了例外:

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

}

我的fragmentManagerRetriever实现ForYouFragment看起来像这样:

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然后返回(HomeFragment因为ForYouFragmentinside而返回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

ViewPagerRecyclerView 内部的适配器:

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
    }

}

标签: androidandroid-fragmentsandroid-viewpagerfragmentmanager

解决方案


解决了

问题是片段希望在附加到其父级ViewPager之前附加到。这个问题在这里ViewPager概述。

所以,为了解决这个问题,我创建了自定义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().

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


推荐阅读