ViewPager2设置Adapter报错IllegalArgumentExceptionyichang

ViewPager2设置Adapter报错IllegalArgumentException

1.问题出现场景

首页是由ViewPager2+Fragment实现,而第二个Fragment中又嵌套了ViewPager2+Fragment,当在首页跳转到其他页面后,再按返回键,则程序抛出异常,位置是第二个Fragment在设置adapter的时候报:

java.lang.IllegalArgumentException
        at androidx.core.util.Preconditions.checkArgument(Preconditions.java:36)
        at androidx.viewpager2.adapter.FragmentStateAdapter.onAttachedToRecyclerView(FragmentStateAdapter.java:132)
        at androidx.recyclerview.widget.RecyclerView.setAdapterInternal(RecyclerView.java:1209)
        at androidx.recyclerview.widget.RecyclerView.setAdapter(RecyclerView.java:1161)
        at androidx.viewpager2.widget.ViewPager2.setAdapter(ViewPager2.java:461)
        at com.lihao.wanandroid.ui.navigation.NavigationFragment.initView(NavigationFragment.kt:63)
        at com.lihao.jetpackcore.base.BaseVmFragment.onViewCreated(BaseVmFragment.kt:51)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:332)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1187)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1356)
        at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1434)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1497)
        at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2625)
        at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:2577)
        at androidx.fragment.app.Fragment.performActivityCreated(Fragment.java:2722)
        at androidx.fragment.app.FragmentStateManager.activityCreated(FragmentStateManager.java:346)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1188)
        at androidx.fragment.app.FragmentManager.addAddedFragments(FragmentManager.java:2224)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1997)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1953)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1849)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:214)
        at android.app.ActivityThread.main(ActivityThread.java:7356)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

2.解决方案

  • 原因: 原因是adapter是一个成员变量,在Fragment销毁viewPager时,adapter并没有被释放,依旧持有之前的ViewPager中的RecyclerView对象,当Fragment重新创建时,新的viewPager不能和adapter进行绑定,所以抛出异常。
  • 解决办法:在调用viewpager.setAdapter()之前将adapter重新赋值即可,也可以设置成局部变量。

3. 问题分析

既然问题是在viewPager.setAdapter()时报的错,我们就从setAdapter()函数开始分析,以下是setAdapter()中的代码:

    public void setAdapter(@Nullable @SuppressWarnings("rawtypes") Adapter adapter) {
        final Adapter<?> currentAdapter = mRecyclerView.getAdapter();
        mAccessibilityProvider.onDetachAdapter(currentAdapter);
        unregisterCurrentItemDataSetTracker(currentAdapter);
        mRecyclerView.setAdapter(adapter);
        mCurrentItem = 0;
        restorePendingState();
        mAccessibilityProvider.onAttachAdapter(adapter);
        registerCurrentItemDataSetTracker(adapter);
    }

从异常信息中可以看到报错的位置是RecyclerView.setAdapter(),然后又到了RecyclerView.setAdapterInternal(),这是因为RecyclerView.setAdapter()有调用了RecyclerView.setAdapterInternal()函数,通过注释我们知道这个函数是用来重新设置adapter并添加监听器,然后在调用onAttachedToRecyclerView()时抛出异常,那我们直接看setAdapterInternal()函数:

    private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            removeAndRecycleViews();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
    }

函数中首先判断了adapter是否为空,然后调用onAttachedToRecyclerView(),将recyclerView自身作为参数传递,然后进到onAttachedToRecyclerView中看到是一个空函数,这是因为我们看到的是Adapter基类下的实现,而我们传进来的是子类FragmentStateAdapter,那我们进到FragmentStateAdapter中查看onAttachedToRecyclerView()的实现:

    @CallSuper
    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        checkArgument(mFragmentMaxLifecycleEnforcer == null);
        mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
        mFragmentMaxLifecycleEnforcer.register(recyclerView);
    }

方法只有三行,首先是调用了checkArgument()函数,然后创建FragmentMaxLifecycleEnforcer对象,最后调用register(),而异常信息中最顶端报错的函数就是checkArgument(),也就是这个函数抛出了IllegalArgumentException异常,越来越接近真相了,那我们进去看一看:

    public static void checkArgument(boolean expression) {
        if (!expression) {
            throw new IllegalArgumentException();
        }
    }

🤯,没想到这里面更简单,只是判断了一下expression,如果是false则跑出异常,而这个值是由刚才的方法传进来的mFragmentMaxLifecycleEnforcer == null,也就是说我的mFragmentMaxLifecycleEnforcer对象不为空,那么这个FragmentMaxLifecycleEnforcer类到底是个什么东西呢?然后我找到了对他的注释:

Pauses (STARTED) all Fragments that are attached and not a primary item.
Keeps primary item Fragment RESUMED.
翻译过来为:暂停(启动)所有附加的而不是主要项的片段。保持主项目片段恢复。

看的不是太懂,但是大致可以看出应该是一个管理生命周期的,那我们就不管他了,还是分析一下他为什么不为空吧。
回想了一下报错的场景,因为使用了Navigation管理Fragment的跳转,而Navigation存在一个问题就是当从AFragment跳转到BFragment时,AFragment会被销毁,当从BFragment返回到AFragment会重新走onCreateView(),也就是说viewpager会重新设置setAdapter(),而我的adapter设置的是成员变量,也就是虽然AFragment走了onDestoryView(),但是对象并没有被销毁,那么adapter也没有被重新创建,所以setAdapter()设置的还是之前的adapter,那么也找到原因所在了,也就是说将adapter设置为局部变量,在调用viewPager.setAdapter()重新赋值即可,运行了一下果然没有问题了。

不过你可能会产生疑问🤔️,为什么设置之前的adapter就会报错呢,为了找到这个原因我又查看了FragmentStateAdapter的源码,发现和onAttachedToRecyclerView对应的还有一个方法onDetachedFromRecyclerView(),没错这个方法就是用来释放mFragmentMaxLifecycleEnforcer对象的,将其赋为null的,可以看他的源码:

    @CallSuper
    @Override
    public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
        mFragmentMaxLifecycleEnforcer.unregister(recyclerView);
        mFragmentMaxLifecycleEnforcer = null;
    }

那么这个方法又是在哪里被调用的呢,在setAdapterInternal(),也就是上面我们分析的那个函数,这个函数中通过判断mAdapter是否为空,然后进行重制mAdapter,如果非空,则会进行释放,然后重新将传进来的adapter赋给mAdapter,那为什么我们的没有释放呢,那是因为它本来就是null,不需要释放。这所以会产生这样的问题,是因为Navigation的机制,在打开AFragment的时候,设置adapter时通过onAttachedToRecyclerView进行了绑定,而在AFragment跳转到BFragment的时候,AFragment会走onDestoryView(),所以viewPager已经被释放了,当BFragment返回到AFragment,viewpager已经不再是之前的不是一个对象了,所以此时新的Viewpager并没有设置Adapter,所以没有能释放FramengStateFragment中的mFragmentMaxLifecycleEnforcer对象,导致viewpager和FragemntStateFragment绑定失败抛出异常。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 可以通过在适配器中重写 onBindViewHolder 方法,在其中为 itemView 设置点击事件。例如: @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position) { holder.itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 点击事件处理逻辑 } }); } 注意,MyViewHolder 是自定义的 ViewHolder 类,itemViewViewHolder 中的 View 对象。 ### 回答2: ViewPager2 是一个支持左右滑动切换不同页面的控件,它是 Android Support Library 中 ViewPager 的新版替代品。相较于以前的版本,ViewPager2 提供了更多的功能和更加灵活的用法。 要设置 ViewPager2 的点击事件,可以通过以下步骤实现: 1. 首先,确保在项目中引入了 ViewPager2 的依赖库,在 build.gradle 文件中添加相应的依赖。 2. 在布局文件中,添加 ViewPager2 的声明。例如: ```xml <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 3. 在代码中找到 ViewPager2 对象,并设置一个适配器,用于管理页面的内容。例如: ```java ViewPager2 viewPager = findViewById(R.id.viewPager); MyAdapter adapter = new MyAdapter(); viewPager.setAdapter(adapter); ``` 这里的 MyAdapter 是自定义的适配器类,根据自己的需求来实现。 4. 如果要给 ViewPager2 设置点击事件,可以在适配器的 onBindViewHolder 方法中为每个页面的根布局设置点击监听器。例如: ```java @Override public void onBindViewHolder(@NonNull MyViewHolder holder, int position){ // 页面的根布局 View itemView = holder.itemView; // 设置点击事件监听器 itemView.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v){ // 处理点击事件的逻辑 } }); } ``` 在这个点击事件监听器中,可以编写处理点击事件的逻辑,根据具体需求来实现。 通过以上步骤,我们就可以为 ViewPager2 设置点击事件了。注意,点击事件的具体处理逻辑需要根据实际情况来实现,以上只是一个示例。 ### 回答3: ViewPager2AndroidX库中的一个控件,它是用于实现滑动页面的功能。要为 ViewPager2 设置点击事件,可以按照以下步骤进行操作: 1. 首先在 XML 布局文件中添加 ViewPager2 控件,例如: ``` <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewPager" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 2. 在 Java 或 Kotlin 代码中,在获取到 ViewPager2 对象后,可以通过调用它的 `setOnClickListener` 方法来设置点击事件。例如: Java 代码示例: ``` ViewPager2 viewPager = findViewById(R.id.viewPager); viewPager.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 点击事件处理逻辑 } }); ``` Kotlin 代码示例: ``` val viewPager: ViewPager2 = findViewById(R.id.viewPager) viewPager.setOnClickListener { // 点击事件处理逻辑 } ``` 在点击事件处理逻辑中,您可以根据需要执行相关操作,例如跳转到指定页面、显示或隐藏其他视图等等。 需要注意的是,ViewPager2 控件也可以通过 `addOnPageChangeListener` 方法来监听页面切换事件,您可以根据这个方法实现不同页面的点击操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值