Android ViewPager setCurrentItem 在 onResume 后不起作用

Posted

技术标签:

【中文标题】Android ViewPager setCurrentItem 在 onResume 后不起作用【英文标题】:Android ViewPager setCurrentItem not working after onResume 【发布时间】:2013-10-19 10:48:46 【问题描述】:

我遇到了这个奇怪的问题,ViewPagersetCurrentItem(position, false) 工作得很好,然后我切换到另一个活动,在我回到第一个活动后,ViewPager 总是在第一项。即使我已将 setCurrentItem 添加到 onResume 方法,它仍然会忽略它。当我尝试将项目设置为越界索引时,它甚至没有抛出任何异常。 虽然稍后当我调用此方法时,当点击“下一步”按钮时,它会像预期的那样工作。 检查我的代码 10 次是否有任何可能调用 setCurrentItem(0) 或其他东西,但它根本不存在。

【问题讨论】:

任何sn-p都会更好 我遇到了类似的问题,我只是将设置当前项目的代码移到了设置适配器的下方。我的意思是确保先设置适配器,然后设置位置。 【参考方案1】:

我无法真正回答为什么会发生这种情况,但是如果您将 setCurrentItem 调用延迟几毫秒,它应该可以工作。我的猜测是因为在 onResume 期间还没有渲染通道,而 ViewPager 需要一个或类似的东西。

private ViewPager viewPager;

@Override
public void onResume() 
    final int pos = 3;
    viewPager.postDelayed(new Runnable() 

        @Override
        public void run() 
            viewPager.setCurrentItem(pos);
        
    , 100);

更新:故事时间

所以今天我遇到了 viewpager 忽略了我的 setCurrentItem 操作的问题,我在 *** 中搜索了解决方案。我发现有人有同样的问题并解决了;我实施了修复,但没有奏效。哇!回到 *** 以否决那个虚假修复提供者,然后......

是我。我实施了自己的错误非修复,这是我第一次偶然发现问题时提出的(后来被遗忘了)。我现在不得不对自己提供不良信息投反对票。


我最初的“修复”工作的原因不是因为“渲染通道”;问题是寻呼机的内容是由微调器控制的。微调器和寻呼机状态都在 onResume 上恢复,因此在下一个事件传播周期期间调用微调器 onItemSelected 侦听器,这确实重新填充了 viewpager - 这次使用不同的默认值。 在初始状态恢复期间移除和重置监听器解决了这个问题。

上面的修复方法第一次奏效,因为它设置了寻呼机当前位置 onItemSelected 事件触发之后。后来,由于某种原因它停止工作(可能应用程序变得太慢 - 在我的实现中我没有使用 100 毫秒,而是 10 毫秒)。然后我在清理周期中删除了 postDelayed,因为它没有改变已经有问题的行为。

更新 2: 我不能对自己的帖子投反对票。我想,尊贵的切腹是唯一的选择。

【讨论】:

你是什么意思寻呼机的内容是由微调器控制的?您是在谈论 ViewPager 的底层实现,还是说 viewpager 中每个页面的内容都包含一个微调器?即使我在 onResume() 中做的第一件事是调用 super.onResume(),我仍然有这个错误。通过这样做,应该恢复视图状态,我不应该处理你上面描述的内容 不,我的意思是:根据微调器中的选定项目,viewpager(即适配器)填充了不同的项目。在 onResume 中,微调器和寻呼机都被重新初始化,微调器用于更新 viewpager 的 onItemSelected 回调“覆盖”了正确设置的 viewpager 位置。这是我自己的错,viewpager 工作正常。 正如我所说,对我来说问题是完全不同的,即两次调用setCurrentItem()。我想同样的事情也适用于你的情况。 @user3560827 我确定你要么在之后再次调用 setCurrentItem 要么分配一个新的适配器,这要归功于一些生命周期的恶作剧或者可能是在一些异步操作之后。不要依赖 postDelayed,它是一种 hack,实际上是技术债务。你的问题出在别处! 哇,这个故事值得Netflix改编【参考方案2】:

我在 Activity 的 OnCreate 中遇到了类似的问题。 适配器设置了正确的计数,我 在将适配器设置为 然而,ViewPager 会返回超出范围的索引。我认为 ViewPager 在我设置当前项目时还没有加载我的所有片段。通过在 ViewPager 上发布一个可运行文件,我能够解决这个问题。这是一个带有一点上下文的示例。

    // Locate the viewpager in activity_main.xml
    final ViewPager viewPager = (ViewPager) findViewById(R.id.pager);

    // Set the ViewPagerAdapter into ViewPager
    viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));

    viewPager.setOffscreenPageLimit(2);

    viewPager.post(new Runnable() 
        @Override
        public void run() 
            viewPager.setCurrentItem(ViewPagerAdapter.CENTER_PAGE);
        
    );

【讨论】:

@TylerPfaff 这也对我有用。显然,ViewPager 忽略了“早期进入活动”指令,因为它还不知道要渲染什么 @TylerPfaff 我相信这与测量 ViewPager 及其子项有关。在完成之前,它似乎会忽略 setCurrentItem。发布在 ViewPager 上的 runnable 保证在该度量之后执行。【参考方案3】:

我找到了一个非常简单的解决方法:

    if (mViewPager.getAdapter() != null)
        mViewPager.setAdapter(null);
    mViewPager.setAdapter(mPagerAdapter);
    mViewPager.setCurrentItem(desiredPos);

而且,如果这不起作用,您可以将其放入处理程序中,但不需要定时延迟:

        new Handler().post(new Runnable() 
            @Override
            public void run() 
                mViewPager.setCurrentItem(desiredPos);
            
        );

【讨论】:

谢谢,第二种方法解决了我的问题。【参考方案4】:

我在代码中有类似的错误,问题是我在更改数据之前设置了位置。

解决方法就是在之后设置位置并通知数据发生变化

notifyDataSetChanged()
setCurrentItem()

【讨论】:

【参考方案5】:

ViewTreeObserver 可用于避免静态延迟。

科特林:

随意使用 Kotlin 扩展作为一个简洁的选项。

view_pager.doOnPreDraw 
    view_pager.currentItem = 1

请确保您具有 gradle 依赖项: implementation 'androidx.core:core-ktx:1.3.2' 或更高版本

Java

OneShotPreDrawListener.add(view_pager, () -> view_pager.currentItem = 1);

【讨论】:

很好的答案 - 只有在 viewpager2 中有效的解决方案!【参考方案6】:

FragmentActivity 中的现代方法是在 Dispatchers.Main 上下文中的协程中调用 ViewPager.setcurrentItem(Int) 函数:

lifecycleScope.launch(Dispatchers.Main) 
   val index = 1
   viewPager.setCurrentItem(index)

【讨论】:

【参考方案7】:

我有同样的问题,我正在编辑

@Override
public int getCount()  return NUM_PAGES; 

我将NUM_PAGES 设置为仅1。

【讨论】:

这也是我的问题... ty!【参考方案8】:

有些人在这里的论坛上写道。 https://code.i-harness.com/en/q/126bff9 为我工作

 if (mViewPager.getAdapter() != null)
    mViewPager.setAdapter(null);
mViewPager.setAdapter(mPagerAdapter);
mViewPager.setCurrentItem(desiredPos);

【讨论】:

你是最棒的。这终于奏效了。我从凌空 json 请求中获取数据。这些视图最初是空的。我尝试了很多解决方案,但都没有奏效。但你的工作就像魔术一样。谢谢。【参考方案9】:

解决方案(在带有 ViewModel 等的 Kotlin 中)针对那些试图在 ActivityonCreate 中设置当前项目而没有 hacky Runnable“解决方案”的人:

class MyActivity : AppCompatActivity() 
    lateinit var mAdapter: MyAdapter
    lateinit var mPager: ViewPager
    // ...

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.fragment_pager)
        // ...
        mainViewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        mAdapter = MyAdapter(supportFragmentManager)
        mPager = findViewById(R.id.pager)

        mainViewModel.someData.observe(this, Observer  items ->
            items?.let 
                // first give the data to the adapter
                // this is where the notifyDataSetChanged() happens
                mAdapter.setItems(it) 
                mPager.adapter = mAdapter // assign adapter to pager
                mPager.currentItem = idx // finally set the current page
            
        )

这显然会执行正确的操作顺序,而不会使用 Runnable 或延迟进行任何黑客攻击。

为了完整起见,您通常像这样实现适配器的setItems()(在本例中为FragmentStatePagerAdapter):

internal fun setItems(items: List<Item>) 
    this.items = items
    notifyDataSetChanged()

【讨论】:

【参考方案10】:

我已经使用了这里描述的 post() 方法,确实在某些情况下它工作得很好,但是因为我的数据来自服务器,所以它不是圣杯。

我的问题是我想拥有

notifyDataSetChanged

在任意时间调用,然后在我的 viewPager 上切换选项卡。所以在通知电话之后我就有了这个

ViewUtilities.waitForLayout(myViewPager, new Runnable() 
    @Override
    public void run() 
        myViewPager.setCurrentItem(tabIndex , false);
    
);

public final class ViewUtilities 
    public static void waitForLayout(final View view, final Runnable runnable) 
        view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() 
        @Override
        public void onGlobalLayout() 
            //noinspection deprecation
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);

                runnable.run();
            
        );
    

有趣的事实:末尾的 //noinspection deprecation 是因为 API 16 之后修复的 API 中存在拼写错误,所以应该改为

removeOnGlobalLayoutListener
       ^^
      ON Global

而不是

removeGlobalOnLayoutListener
            ^^
            ON Layout

这似乎涵盖了我的所有情况。

【讨论】:

【参考方案11】:

我在这个问题上工作了一个星期,我意识到这个问题的发生是因为我在视图寻呼机片段中使用家庭活动上下文,并且我们只能在片段附加到活动后在片段中使用上下文。 创建视图寻呼机时,活动仅附加到第一 (0) 和第二 (1) 页。当您打开第二页时,会附加第三页,依此类推!当您使用setCurrentItem() 方法并且参数大于1 时,它想在附加之前打开该页面,因此该页面的片段中的上下文将为空并且应用程序崩溃!这就是为什么当您延迟setCurrentItem() 时,它会起作用!一开始它会被附加,然后它会打开页面......

【讨论】:

【参考方案12】:

正如这里的几位海报所指出的,这是一个生命周期问题。但是,我发现发布Runnable 的解决方案是不可预测的并且可能容易出错。这似乎是一种通过将问题发布到未来来忽略问题的方法。

我并不是说这是最好的解决方案,但它确实可以在不使用Runnable 的情况下工作。我在具有ViewPagerFragment 中保留了一个单独的整数。 这个整数将保存我们想要设置为当前页面的页面,当接下来调用onResume 时。 整数的值可以在任何时候设置,因此可以设置在FragmentTransaction 之前或当恢复活动。另请注意,所有成员都设置在onResume(),而不是onCreateView()

public class MyFragment extends Fragment

    private ViewPager           mViewPager;
    private MyPagerAdapter      mAdapter;
    private TabLayout           mTabLayout;
    private int                 mCurrentItem = 0; // Used to keep the page we want to set in onResume().

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    
        View view = inflater.inflate(R.layout.my_layout, container, false);
        mViewPager = (ViewPager) view.findViewById(R.id.my_viewpager);
        mTabLayout = (TabLayout) view.findViewById(R.id.my_tablayout);
        return view;
    

    @Override
    public void onResume()
    
        super.onResume();

        MyActivity myActivity = (MyActivity) getActivity();
        myActivity.getSupportActionBar().setTitle(getString(R.string.my_title));

        mAdapter = new MyPagerAdapter(getChildFragmentManager(), myActivity);
        mViewPager.setAdapter(mAdapter);
        mViewPager.setOffscreenPageLimit(PagerConstants.OFFSCREEN_PAGE_LIMIT);
        mViewPager.setCurrentItem(mCurrentItem); // <-- Note the use of mCurrentItem here!
        mTabLayout.setupWithViewPager(mViewPager);
    

    /**
     * Call this at any point before needed, for example before performing a FragmentTransaction.
     */    
    public void setCurrentItem(int currentItem)
    
        mCurrentItem = currentItem;

        // This should be called in cases where onResume() is not called later,
        // for example if you only want to change the page in the ViewPager
        // when clicking a Button or whatever. Just omit if not needed.
        mViewPager.setCurrentItem(mCurrentItem); 
    



【讨论】:

就我而言,我将所有初始化行(如设置适配器、侦听器等)从 onResume() 移动到 onCreateView() 并且它起作用了【参考方案13】:

对我来说,设置适配器后设置当前项目有效

viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager()));

viewPager.setCurrentItem(idx);

pagerSlidingTabStrip.setViewPager(viewPager);// assign viewpager to tabs

【讨论】:

【参考方案14】:

我这样做是为了恢复当前项目:

@Override
protected void onSaveInstanceState(Bundle outState) 

    if (mViewPager != null) 
        outState.putInt(STATE_PAGE_NO, mViewPager.getCurrentItem());
    

    super.onSaveInstanceState(outState);


@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) 

    if (savedInstanceState != null) 
        mCurrentPage = savedInstanceState.getInt(STATE_PAGE_NO, 0);
    

    super.onRestoreInstanceState(savedInstanceState);


@Override
protected void onRestart() 
    mViewPager.setCurrentItem(mCurrentPage);
         super.onRestart();

【讨论】:

问题是我没有恢复实例状态,它只是调用了 onStart 和 onResume 方法。我已经正确存储了位置,并且我在调试模式下查看了我的 Viewpager 对象,它的 mCurItem 设置正确,但它仍然显示第一个元素 我记不清了,但我想我有类似的问题,我使用了上面的代码。但可能并不完全相同。【参考方案15】:

当我打电话给setCurrentItem() 时,视图即将被重新创建。所以实际上我为 viewpager 调用 setCurrentItem(),然后系统调用 onCreateView() 并因此创建一个新的 viewpager。

这就是我看不到任何变化的原因。这就是postDelayed() 可能会有所帮助的原因。

理论解决方案:推迟setCurrentItem() 调用,直到重新创建视图。

实用的解决方案:我不知道一个稳定而简单的解决方案。我们应该能够检查该类是否即将重新创建它的视图,如果是这种情况,请将 setCurrentItem() 的调用推迟到 onCreateView() 的末尾

【讨论】:

【参考方案16】:

我使用dsalaj 代码作为参考。如有必要,我会与完整的解决方案共享代码。

我也强烈推荐使用ViewPager2

解决方案

这两种情况都必须在Observer 内:

    第一种情况:仅当我们拥有第一个数据集而不是之前才初始化适配器,因为这会在分页中产生不一致。 我们必须将它作为适配器的参数传递给第一个数据集

    1234563具有第一个数据集的适配器。

GL

【讨论】:

这继续发生在 viewPager2 上。很烂 我记得很清楚,这对我兄弟有用。尝试调试关键点。然后它评估如果 observable 有数据,适配器是如何初始化的,以及当 observable 更新时适配器如何设置新数据【参考方案17】:

我对 onActivityCreated() 被不相关的标签调用感到困惑 @Mahdi Arabpour 让我大开眼界 :)

对我来说,问题是当我单击第二个选项卡等时,第三页(如上面@Mahdi Arabpour 所述)正在重建,并且它丢失了它的数据适配器,在 onActivityCreted 中再次设置它可以解决我的问题:

    if (myXXRecyclerAdapter != null) 
        myXXRecyclerAdapter = new MyXXRecyclerAdapter(myStoredData);
        mRecyclerView.setAdapter(myXXRecyclerAdapter );
        return;
    

【讨论】:

【参考方案18】:

您需要在 pager.setAdapter(buildAdapter()) 之后立即调用 pager.setCurrentItem(activePage)

@Override
public void onResume() 
    if (pager.getAdapter() != null) 
        activePage=pager.getCurrentItem();
        Log.w(getClass().getSimpleName(), "pager.getAdapter()!=null");
        pager.setAdapter(null);

    

    pager.setAdapter(buildAdapter());            
    pager.setCurrentItem(activePage);

【讨论】:

以上是关于Android ViewPager setCurrentItem 在 onResume 后不起作用的主要内容,如果未能解决你的问题,请参考以下文章

Android-ViewPager2

Android - ViewPager

android viewpager滑动与slidingpanelayout冲突怎么解决

android之ViewPager介绍

Android 中ViewPager练习

android,viewpager 怎么设置当前默认页