web-dev-qa-db-ja.com

Android onResume後にViewPager setCurrentItemが機能しない

私はこの奇妙な問題を抱えており、ViewPagerのsetCurrentItem(position、false)は完全に正常に機能し、別のアクティビティに切り替え、最初のアクティビティに戻った後、ViewPagerは常に最初のアイテムになります。 iveはsetCurrentItemをonResumeメソッドに追加しましたが、それでも無視します。アイテムを範囲外のインデックスに設定しようとすると、例外もスローされません。後でこのメソッドを呼び出すと、「次へ」ボタンをクリックすると、期待どおりに機能します。 setCurrentItem(0)またはsmthの呼び出しについてコードを10回チェックしましたが、まったくありません。

27
Maciej Boguta

私は実際に正確にこれが起こる理由に答えることはできませんが、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);
}

更新:ストーリー時間

そのため、今日は、ビューページャーがsetCurrentItemアクションを無視するという問題があり、stackoverflowで解決策を探しました。同じ問題と修正のある人を見つけました。修正を実装しましたが、機能しませんでした。おっ! stackauxflowに戻って、そのfaux-fix-providerに投票し、...

それは私。私は自分の障害のある非修正プログラムを実装しましたが、問題に最初に出会ったときに思いついたものです(後で忘れられました)。私は今、悪い情報を提供するために自分自身に投票する必要があります。


最初の「修正」が機能した理由は、「レンダリングパス」のためではありません。問題は、ページャーのコンテンツがスピナーによって制御されていたことです。スピナーとページャーの両方の状態がonResumeに復元されたため、スピナーのonItemSelectedリスナーが次のイベント伝播サイクル中に呼び出され、ビューページャーが再作成されました。
初期状態の復元中にリスナーを削除してリセットすると、問題が修正されました。

上記の修正は、ページャーの現在位置afteronItemSelectedイベントが発生したため、最初に機能しました。その後、何らかの理由で動作しなくなりました(おそらくアプリが遅くなりすぎた-私の実装では100ミリ秒ではなく、10ミリ秒を使用しました)。それから、クリーンアップサイクルでpostDelayedを削除しました。これは、すでに障害のある動作を変更しなかったためです。

更新2:私は自分の投稿にダウン投票できません。私は、名誉あるseppukuが残っている唯一のオプションだと思います。

60
stefs

アクティビティのOnCreateでも同様の問題がありました。アダプターは正しいカウントでセットアップされ、アダプターをViewPagerに設定した後にsetCurrentItemを適用しましたが、範囲外のインデックスを返します。 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);
        }
    });
22
Martin Price

これに対する非常に簡単な回避策を見つけました。

    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);
            }
        });
11

コードに同様のバグがありました。問題は、データを変更する前に位置を設定していたことです。

解決策は、後で位置を設定し、データが変更されたことを通知することでした

notifyDataSetChanged()
setCurrentItem()
3
petrumo

私は同じ問題を抱えて編集しています

@Override
public int getCount() { return NUM_PAGES; }

NUM_PAGES 1だけに間違いがあります。

3
Abdulaziz Noor

ここで説明した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にスペルミスがあるためです。 remove On removeGlobalではなくGlobalLayoutListener On LayoutListener

これは私にとってすべてのケースをカバーしているようです。

2

私にとっては、これはアダプタを設定した後に現在の項目を設定するのに役立ちました

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

viewPager.setCurrentItem(idx);

pagerSlidingTabStrip.setViewPager(viewPager);// assign viewpager to tabs
1
arun-r

onCreateActivityに現在のアイテムをハックRunnable "solutions"なしで設定しようとする人のためのソリューション(ViewModelなどを使用したKotlin):

_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()
}
_
1
dsalaj

何人かの男がここのフォーラムに書いた。 https://code.i-harness.com/en/q/126bff9 私のために働いた

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

これは、いくつかのポスターで指摘されているように、ライフサイクルの問題です。しかし、Runnableをポストするソリューションは予測不能であり、おそらくエラーが発生しやすいことがわかりました。問題を将来に投稿することで、問題を無視する方法のようです。

これが最善の解決策であると言っているわけではありませんが、Runnableを使用しなくても確実に機能します。 Fragmentを持つViewPager内に別の整数を保持します。 この整数は、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); 
    }


}
0
Krøllebølle

setCurrentItem()を呼び出すまでに、ビューは再作成されます。そのため、実際にはビューページャーに対してsetCurrentItem()を呼び出し、その後システムはonCreateView()を呼び出して新しいビューページャーを作成します。

これが、変更が表示されない理由です。そして、これがpostDelayed()が役立つ理由です。

理論的解決策:ビューが再作成されるまで、setCurrentItem()呼び出しを延期します。

実用的な解決策:安定したシンプルな解決策の手がかりがありません。クラスがそのビューを再作成しようとしているかどうかを確認でき、そうであれば、setCurrentItem()の呼び出しをonCreateView()の最後まで延期します。

0
mikes

私はこの問題に1週間取り組んでいましたが、ページャーフラグメントの表示でホームアクティビティコンテキストを使用していたため、この問題が発生することに気付きました。
ビューページャーが作成されると、アクティビティは最初の(0)ページと2番目の(1)ページにのみアタッチされます。 2ページ目を開くと、3ページ目が添付されます。 setCorrentItem()メソッドを使用し、引数が1より大きい場合、アタッチされる前にそのページを開きたいため、そのページのフラグメントのコンテキストはnullになり、アプリケーションがクラッシュします!だからsetCorrentItem()を遅らせるとうまくいきます!最初に添付されてから、ページが開きます...

0
Mahdi Arabpour

CLEAN AND SIMPLE notifyDataSetChanged()を呼び出した後にsetCurrentItemだけを追加する必要はありません。

0
Vikash Sharma

私は現在のアイテムを復元するためにこのようにしました:

@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();
}
0
Szymon