ViewPager PagerAdapterがビューを更新しない


597

互換性ライブラリのViewPagerを使用しています。私はそれがいくつかのビューを表示できるように成功しました。

しかし、新しいビューのセットでViewPagerを更新する方法を理解するのに苦労しています。

新しいデータリストを使用するたびに、新しいアダプタを作成するなど、の呼び出しなどmAdapter.notifyDataSetChanged()、あらゆる種類のことを試しましたmViewPager.invalidate()

テキストビューは元のデータから変更されないままです。

更新: 小さなテストプロジェクトを作成しましたが、ほとんどビューを更新できました。以下のクラスを貼り付けます。

更新されていないように見えるのは2番目のビューです。「B」は残り、更新ボタンを押すと「Y」が表示されます。

public class ViewPagerBugActivity extends Activity {

    private ViewPager myViewPager;
    private List<String> data;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        data = new ArrayList<String>();
        data.add("A");
        data.add("B");
        data.add("C");

        myViewPager = (ViewPager) findViewById(R.id.my_view_pager);
        myViewPager.setAdapter(new MyViewPagerAdapter(this, data));

        Button updateButton = (Button) findViewById(R.id.update_button);
        updateButton.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View v) {
                updateViewPager();
            }
        });
    }

    private void updateViewPager() {
        data.clear();
        data.add("X");
        data.add("Y");
        data.add("Z");
        myViewPager.getAdapter().notifyDataSetChanged();
    }

    private class MyViewPagerAdapter extends PagerAdapter {

        private List<String> data;
        private Context ctx;

        public MyViewPagerAdapter(Context ctx, List<String> data) {
            this.ctx = ctx;
            this.data = data;
        }

        @Override
        public int getCount() {
            return data.size();
        }

        @Override
        public Object instantiateItem(View collection, int position) {
            TextView view = new TextView(ctx);
            view.setText(data.get(position));
            ((ViewPager)collection).addView(view);
            return view;
        }

        @Override
        public void destroyItem(View collection, int position, Object view) {
             ((ViewPager) collection).removeView((View) view);
        }

        @Override
        public boolean isViewFromObject(View view, Object object) {
            return view == object;
        }

        @Override
        public Parcelable saveState() {
            return null;
        }

        @Override
        public void restoreState(Parcelable arg0, ClassLoader arg1) {
        }

        @Override
        public void startUpdate(View arg0) {
        }

        @Override
        public void finishUpdate(View arg0) {
        }
    }
}

1
FragmentStatePagerAdapterの潜在的なバグについては、stackoverflow.com / questions / 12510404 /…を参照してください。
samis 2014年

2
これをかわしてごめんなさい。あなたの質問は本当に私を助けてくれました!PagerAdapter内のデータへの参照が変更されますどのように私は取得しない一つのことは、あなたが活動中でそれを更新したときである
Ewanw

お使いのアダプタがBaseAdapterを拡張してください、この参照してください: stackoverflow.com/questions/13664155/...

1
どうやら、アダプターをビューページャーに再度設定すると、リセットされます。
EpicPandaForce 2016

私はポケットベルでは問題にしていstackoverflow.com/questions/41217237/...
クリス・

回答:


856

これを実現するにはいくつかの方法があります。

最初のオプションは簡単ですが、少し非効率的です。

次のようにオーバーライドgetItemPositionしてくださいPagerAdapter

public int getItemPosition(Object object) {
    return POSITION_NONE;
}

このようにして、を呼び出すnotifyDataSetChanged()と、ビューページャーはすべてのビューを削除してすべて再読み込みします。そのため、リロード効果が得られます。

第二の選択肢、アルバロ・ルイス・ブスタマンテ(以前alvarolb)によって提案されたが、にあるsetTag()方法instantiateItem()新しいビューをインスタンス化するとき。次にnotifyDataSetChanged()、を使用findViewWithTag()する代わりに、を使用して、更新するビューを見つけることができます。

2番目のアプローチは、非常に柔軟で高性能です。元の研究のためのalvarolbへの称賛。


4
私の回避策よりも良いですが、副作用がないようです、ありがとう。
C0deAttack

3
同様の問題があります。私のviewPagerは新しい検索結果を表示しますが、デフォルトではカーソルの最初のエントリに戻りません。データセットを更新するときに位置をリセットする方法を知っている人はいますか?
mAndroid

1
@mAndroidでは、詳細を記載した別の質問を提出してください。
rui.araujo 2011

1
POSITION_NONEを返すと、Androidがクエリされた位置に関連付けられたビューを削除するため、アプリケーション全体に明らかに非効率性が追加されます。次に、表示する前にビューを再作成する必要があります。ビューが更新されない問題を解決しますが、複雑なレイアウトの場合はおそらく最善の解決策ではありません。
wufoo 2012年

11
より良い答え(すべての項目を削除せずに)-> stackoverflow.com/a/10852046/898056
yhpark

484

私はどんな種類のバグもないと思います PagerAdapter。問題は、それがどのように機能するかを理解することが少し複雑であることです。ここで説明されている解決策を見ると、誤解があるため、私の観点からは、インスタンス化されたビューの使用法が不十分です。

最後の数日間は、私が働いているPagerAdapterViewPager、私は以下のが見つかりました:

notifyDataSetChanged()メソッドは、基になるページが変更されたPagerAdapterことのみを通知しViewPagerます。たとえば、ページを動的に作成/削除した場合(リストにアイテムを追加または削除した場合)は、それを処理するViewPager必要があります。この場合ViewPagergetItemPosition()およびgetCount()メソッドを使用して、新しいビューを削除またはインスタンス化する必要があるかどうかが決定されると思います。

私はそれを考えてViewPager後、notifyDataSetChanged()コールはそれの子ビューをチェックして自分の位置をとりますgetItemPosition()。子ビューの場合、このメソッドがを返すPOSITION_NONEと、ViewPagerはビューが削除されたことを理解し、を呼び出してdestroyItem()、このビューを削除します。

このように、以前に作成されたビューが破棄され、を呼び出すたびに新しいビューが作成されるため、ページのコンテンツのみを更新する場合getItemPosition()、常に戻るようにオーバーライドすることPOSITION_NONEは完全に間違っていますnotifyDatasetChanged()。数TextView秒間だけ問題はないように見えるかもしれませんが、データベースから読み込まれたListViewsのような複雑なビューがある場合、これは実際の問題であり、リソースの浪費になる可能性があります。

したがって、ビューを削除して再度インスタンス化することなく、ビューのコンテンツを効率的に変更する方法はいくつかあります。それはあなたが解決したい問題に依存します。私のアプローチは、setTag()メソッド内のインスタンス化されたビューにメソッドを使用することですinstantiateItem()。したがって、データを変更したり、必要なビューを無効にしたりする場合は、のfindViewWithTag()メソッドを呼び出してViewPager、以前にインスタンス化されたビューを取得し、必要に応じて新しいビューを削除/作成する必要なく、必要に応じて変更/使用できます。いくつかの値を更新します。

たとえば、100 TextView秒のページが100 あり、定期的に1つの値のみを更新したいとします。前に説明したアプローチでは、これは、TextView更新ごとに100 秒を削除してインスタンス化することを意味します。それは意味がありません...


11
+1-あなたのソリューションは、リストビュー、テキストビュー、画像を含むタブを含むページを更新しなければならなかったときに、私がここで説明した問題で実行した魅力のように機能するだけではありません。(OutOfErrorExceptionsなど...)ありがとうございます!
シュリンゲル2011

3
@alvarolb:こんにちは、setTagmethod onInstantiateItemでどういう意味なのか本当にわかりません。この回答をいくつかのコーディングで更新しますか?ありがとう。
ホイタワー

35
あなたが例を書けばそれは完璧です。
Sami Eltamawy

13
誰かが言うように:例は高く評価されます!
Jey10

15
今から6年、例を示すことはできません。
Oussaki 2017

80

変更FragmentPagerAdapterFragmentStatePagerAdapterます。

getItemPosition()メソッドをオーバーライドして返すPOSITION_NONEます。

最終的にはnotifyDataSetChanged()、ビューページャーをリッスンします。


getItemPosition全体を適切に実装し、フラグメントが見つからない場合はPOSITION_NONEを返すと思います。
tkhduracell 2018

46

答え alvarolbによって与えられたが、間違いなくそれを行うための最善の方法です。彼の答えに基づいて、これを実装する簡単な方法は、アクティブなビューを位置別に格納することです。

SparseArray<View> views = new SparseArray<View>();

@Override
public Object instantiateItem(View container, int position) {
    View root = <build your view here>;
    ((ViewPager) container).addView(root);
    views.put(position, root);
    return root;
}

@Override
public void destroyItem(View collection, int position, Object o) {
    View view = (View)o;
    ((ViewPager) collection).removeView(view);
    views.remove(position);
    view = null;
}

次に、notifyDataSetChangedメソッドをオーバーライドすることで、ビューを更新できます...

@Override
public void notifyDataSetChanged() {
    int key = 0;
    for(int i = 0; i < views.size(); i++) {
       key = views.keyAt(i);
       View view = views.get(key);
       <refresh view with new data>
    }
    super.notifyDataSetChanged();
}

あなたが実際に同様のコードを使用することができますinstantiateItemし、notifyDataSetChangedあなたのビューを更新します。私のコードでは、まったく同じ方法を使用しています。


8
完全なアダプターの例を提供できますか?これはフラグメントを使用して行われているのですか?

9
<新しいデータでビューを更新> ?? ここに彼の見解を追加する必要があるか、何もないというのはどういう意味ですか?
Akarsh M 2013

このメソッドは、すべてのビューを更新します。1つのビューのみを更新する場合は、次のように独自のnotifyDataItemChangedメソッドを記述できます。tv.setText(list.get(pos)); super.notifyDataSetChanged(); }
Kai Wang

新しいページャーのサイズが古いサイズより小さい場合、このコードはクラッシュします
Anton Duzenko

@AntonDuzenko正解、ビューの削除はかなり難しい。
MLProgrammer-CiM 2016

28

同じ問題があった。私にとっては、FragmentStatePagerAdapterを拡張し、以下のメソッドをオーバーライドすることができました。

@Override
public Parcelable saveState() {
    return null;
}

@Override
public void restoreState(Parcelable state, ClassLoader loader) {

}

これも私にとって有効な唯一の解決策です。私の場合はフラグメントを更新するのではなく、フラグメントを動的に削除または追加します。ViewPagerはFragmentStatePagerAdapterを拡張しないと、キャッシュされたフラグメントを返しますが、これは誤った位置です。ありがとう!
Sira Lam

サポートライブラリ28.0.0で適切に動作します
Oleksii K.

オーバーライドして何をしますか?アダプタに関連付けられた状態オブジェクトを作成する方法は?
Phani Rithvij

ありがとう。このソリューションのみが機能します
パルティ

20

この問題を克服するために上記のすべてのソリューションをしようとし、また、のような他の同様の質問に多くのソリューションをしようとしているときにフラストレーションの時間後、このこのこのすべては、この問題を解決し、作るために私と一緒に失敗したViewPager古いものを破壊することFragmentと記入pagerして新しいFragments。私は次のように問題を解決しました:

1)次のようViewPagerに拡張するクラスを作成FragmentPagerAdapterします。

 public class myPagerAdapter extends FragmentPagerAdapter {

2)次のようにとのViewPagerストアのアイテムを作成します。titlefragment

public class PagerItem {
private String mTitle;
private Fragment mFragment;


public PagerItem(String mTitle, Fragment mFragment) {
    this.mTitle = mTitle;
    this.mFragment = mFragment;
}
public String getTitle() {
    return mTitle;
}
public Fragment getFragment() {
    return mFragment;
}
public void setTitle(String mTitle) {
    this.mTitle = mTitle;
}

public void setFragment(Fragment mFragment) {
    this.mFragment = mFragment;
}

}

3)次ViewPagerようにFragmentManagerインスタンスのコンストラクターを作成して、インスタンスをmy に格納classします。

private FragmentManager mFragmentManager;
private ArrayList<PagerItem> mPagerItems;

public MyPagerAdapter(FragmentManager fragmentManager, ArrayList<PagerItem> pagerItems) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mPagerItems = pagerItems;
}

4)再設定する方法を作成しadapter、以前のすべて削除することによって、新たなデータを有するデータfragmentからfragmentManager直接作ること自体をadapter新たに設定しfragment、次のように再び新しいリストから。

public void setPagerItems(ArrayList<PagerItem> pagerItems) {
    if (mPagerItems != null)
        for (int i = 0; i < mPagerItems.size(); i++) {
            mFragmentManager.beginTransaction().remove(mPagerItems.get(i).getFragment()).commit();
        }
    mPagerItems = pagerItems;
}

5)コンテナから、ActivityまたはFragment新しいデータでアダプタを再初期化しないでください。メソッドを使用setPagerItemsして、次のように新しいデータで新しいデータを設定します。

ArrayList<PagerItem> pagerItems = new ArrayList<PagerItem>();
pagerItems.add(new PagerItem("Fragment1", new MyFragment1()));
pagerItems.add(new PagerItem("Fragment2", new MyFragment2()));

mPagerAdapter.setPagerItems(pagerItems);
mPagerAdapter.notifyDataSetChanged();

お役に立てば幸いです。


@Tomerあなたが直面した問題は何でしたか?
Sami Eltamawy、2018


9

私は同じ問題を抱えていて、私の解決策はFragmentPagerAdapterオーバーライドして使用していますFragmentPagerAdapter#getItemId(int position)

@Override
public long getItemId(int position) {
    return mPages.get(position).getId();
}

デフォルトでは、このメソッドはアイテムの位置を返します。変更されたViewPagerかどうかを確認し、itemId変更された場合のみページを再作成するとします。ただし、オーバーライドされていないバージョンは、itemIdページが実際に異なる場合と同じ位置を返します。また、ViewPagerは、ページが1つ置き換えられ、再作成する必要があることを定義していません。

これを使うには long idは、各ページにが必要です。通常は一意であることが期待されますが、この場合、同じページの以前の値とは異なる必要があることをお勧めします。そのため、ここではアダプターまたはランダムな整数(広い分布を持つ)で連続カウンターを使用できます。

このトピックでソリューションとして言及されているビューのタグを使用するよりも、より一貫した方法だと思います。しかし、おそらくすべての場合ではありません。


1
そうです。私はそれをチェックアウトしました:POSITION_NONEだけでは十分ではありません。素敵な回答ありがとうございます!:)
スラバ

この機能はにも存在しませんPagerAdapter。これはどんなクラスですか?
Saket

@Saket、あなたは正しい、クラスはFragmentPagerAdapter
Atlascoder、

6

私はこの問題について非常に興味深い決定を見つけました。すべてのフラグメントをメモリに保持するFragmentPagerAdapterを使用する代わりに、フラグメントを選択するたびにフラグメントをリロードするFragmentStatePagerAdapterandroid.support.v4.app.FragmentStatePagerAdapter)を使用できます。

両方のアダプターの実現は同じです。したがって、「extend FragmentStatePagerAdapter」で「extend FragmentPagerAdapter」を変更するだけです。


良い点は、フラグメントに関連するviewPagerのメモリの問題を他の人が解決するのに役立つ
Kumar

すばらしい答えです。これは私を大いに助けました。
セルジオマラニ

5

この問題を何度も検索した後、私はこれに対処するための正しい方法だと思う本当に良い解決策を見つけました。基本的に、instantiateItemはビューがインスタンス化されたときにのみ呼び出され、ビューが破棄されない限り呼び出されません(これは、getItemPosition関数をオーバーライドしてPOSITION_NONEを返すときに発生します)。代わりに、あなたがしたいことは保存です作成したビューをして、アダプターで更新するか、他のユーザーが更新できるようにget関数を生成するか、アダプターを更新するセット関数(私のお気に入り)を実行します。

したがって、MyViewPagerAdapterに次のような変数を追加します。

private View updatableView;

instantiateItem内の:

 public Object instantiateItem(View collection, int position) {
        updatableView = new TextView(ctx); //My change is here
        view.setText(data.get(position));
        ((ViewPager)collection).addView(view);
        return view;
    }

したがって、このようにして、ビューを更新する関数を作成できます。

public updateText(String txt)
{
    ((TextView)updatableView).setText(txt);
}

お役に立てれば!


データを設定してinstantiateItemいないので、ビューが更新されないことに気付きました。あなたの答えから私はそれを実現しました。+1
Phani Rithvij

5

OPが質問した2年半後、この問題はまだ問題です。これに対するGoogleの優先度は特に高いわけではないことは明らかなので、修正を見つけるのではなく、回避策を見つけました。私にとって大きな進歩は、問題の本当の原因が何であるかを見つけることでした(この投稿で)。問題がアクティブなページが適切に更新されていないことが明らかになったら、私の回避策は明白でした。

私のフラグメント(ページ):

  • onCreateViewからフォームにデータを入力するすべてのコードを取り、それをPopulateFormと呼ばれる関数に配置しました。これは、フレームワークではなく、どこからでも呼び出すことができます。この関数は、getViewを使用して現在のビューを取得しようとします。それがnullの場合は、単に戻ります。PopulateFormに表示されるコードのみが含まれていることが重要です-FocusChangeリスナーなどを作成する他のすべてのコードはまだOnCreateにあります
  • フォームをリロードする必要があることを示すフラグとして使用できるブール値を作成します。私のはmbReloadForm
  • OnResume()をオーバーライドして、mbReloadFormが設定されている場合にPopulateForm()を呼び出します。

私のアクティビティでは、ページの読み込みを行います:

  • 変更する前に0ページに移動してください。私はFragmentStatePagerAdapterを使用しているので、最大で2〜3ページが影響を受けることがわかります。ページ0に変更すると、ページ0、1、2でのみ問題が発生します。
  • 古いリストをクリアする前に、それをsize()にしてください。これにより、バグの影響を受けるページの数を知ることができます。> 3の場合は、3に減らします。別のPagerAdapterを使用している場合は、処理する必要があるページの数を確認する必要があります(おそらくすべてですか)。
  • データをリロードし、pageAdapter.notifyDataSetChanged()を呼び出します
  • 次に、影響を受ける各ページについて、pager.getChildAt(i)を使用してページがアクティブかどうかを確認します。これにより、ビューがあるかどうかがわかります。その場合は、pager.PopulateView()を呼び出します。そうでない場合は、ReloadFormフラグを設定します。

この後、ページの2番目のセットをリロードすると、バグにより依然として古いデータが表示されます。ただし、これらは更新され、新しいデータが表示されます。この更新はページが表示される前に行われるため、ユーザーはページが正しくないことを知ることはありません。

これが誰かを助けることを願っています!


5

これらすべての解決策は私を助けませんでした。したがって、私は実用的なソリューションを見つけました:いつでもできますがsetAdapter、それだけでは十分ではありません。アダプターを変更する前に、次のことを行う必要があります。

FragmentManager fragmentManager = slideShowPagerAdapter.getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
List<Fragment> fragments = fragmentManager.getFragments();
for (Fragment f : fragments) {
    transaction.remove(f);
}
transaction.commit();

そしてこの後:

viewPager.setAdapter(adapter);

ありがとう!私の場合ViewPager、フラグメント内で使用したので、に置き換えslideShowPagerAdapter.getFragmentManager()ましたgetChildFragmentManager()。多分getFragmentManager()あなたの場合に役立ちます。使用しましたがFragmentPagerAdapter、使用していませんFragmentStatePagerAdapter。ハックな方法については、stackoverflow.com / a / 25994654/2914140 も参照してください。
CoolMind 2018

5

より簡単な方法:を使用しFragmentPagerAdapter、ページビューをフラグメントにラップします。彼らは更新されます


5

rui.araujoとAlvaro Luis Bustamanteに感謝します。最初は簡単なのでrui.araujoの方法を使おうとします。動作しますが、データが変更されると、ページは明らかに再描画されます。悪いのでAlvaro Luis Bustamanteのやり方を試してみます。それは完璧だ。これがコードです:

@Override
protected void onStart() {
    super.onStart();
}

private class TabPagerAdapter extends PagerAdapter {
    @Override
    public int getCount() {
        return 4;
    }

    @Override
    public boolean isViewFromObject(final View view, final Object object) {
        return view.equals(object);
    }

    @Override
    public void destroyItem(final View container, final int position, final Object object) {
        ((ViewPager) container).removeView((View) object);
    }

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View view = LayoutInflater.from(
                getBaseContext()).inflate(R.layout.activity_approval, null, false);
        container.addView(view);
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        view.setTag(position);
        new ShowContentListTask(listView, position).execute();
        return view;
    }
}

そして、データが変化したとき:

for (int i = 0; i < 4; i++) {
    View view = contentViewPager.findViewWithTag(i);
    if (view != null) {
        ListView listView = (ListView) view.findViewById(R.id.list_view);
        new ShowContentListTask(listView, i).execute();
    }
}

4

誰かがFragmentStatePagerAdapterを使用している場合に備えてベースのアダプター(これにより、ViewPagerが表示目的に必要な最小限のページを作成できるようになります(私の場合は最大2つ))、@ rui.araujoのアダプターのgetItemPositionの上書きの回答は大きな無駄を引き起こしませんが、それでもまだ改善することができます。

疑似コード:

public int getItemPosition(Object object) {
    YourFragment f = (YourFragment) object;
    YourData d = f.data;
    logger.info("validate item position on page index: " + d.pageNo);

    int dataObjIdx = this.dataPages.indexOf(d);

    if (dataObjIdx < 0 || dataObjIdx != d.pageNo) {
        logger.info("data changed, discard this fragment.");
        return POSITION_NONE;
    }

    return POSITION_UNCHANGED;
}

ViewPager内では、アイテムの過去の位置を知るのは非常に簡単でした。最も完璧な実装はgetItemPosition()、データセットが変更されたときに新しい位置を返すだけであり、ViewPagerはそれらが変更されたかどうかを認識します。悲しいことに、ViewPagerはそれをしませんでした。
VinceStyling 2014

3

同様の問題があり、4ページあり、そのうちの1ページが他の3ページのビューを更新していました。現在のページに隣接するページのウィジェット(SeekBars、TextViewsなど)を更新できました。最後の2つのページには、を呼び出すときに初期化されていないウィジェットがありますmTabsAdapter.getItem(position)

私の問題を解決するために、私はsetSelectedPage(index)電話する前に使用しましたgetItem(position)。これによりページがインスタンス化され、各ページの値とウィジェットを変更できるようになります。

すべての更新の後、私はを使用setSelectedPage(position)notifyDataSetChanged()ます。

メインの更新ページのリストビューにわずかなちらつきが見られますが、目立つものはありません。私はそれを徹底的にテストしていませんが、それは私の差し迫った問題を解決します。


こんにちは、私はあなたのコードに興味があります。同様の問題に直面しています。隣接するフラグメントを更新できますが、表示されている現在のフラグメントは更新されません。私はここでのすべての答えに非常に混乱しています。VIewPagerアダプターによって表示される現在のフラグメントのビューを更新する方法
Pankaj Nimgade 2015年

3

他の誰かが役に立つと思う場合に備えて、私はこの回答を投稿しています。まったく同じことをするために、ViewPagerとPagerAdapterのソースコードを互換性ライブラリから取得し、コード内でコンパイルしました(すべてのエラーを整理して自分でインポートする必要がありますが、間違いなく実行できます)。

次に、CustomViewPagerでupdateViewAt(int position)というメソッドを作成します。ビュー自体は、ViewPagerクラスで定義されたArrayList mItemsから取得できます(インスタンス化アイテムでビューのIDを設定し、このIDをupdateViewAt()メソッドの位置と比較する必要があります)。その後、必要に応じてビューを更新できます。


2

おそらく、ViewPagerのロジックを持っています。

ページのセットを更新して、新しいデータセットに基づいて表示する必要がある場合は、notifyDataSetChanged()を呼び出します。次に、ViewPagerはgetItemPosition()を何度も呼び出し、Fragmentをオブジェクトとして渡します。このフラグメントは、古いデータセット(破棄したい)からでも、新しいデータセット(表示したい)からでも構いません。したがって、私はgetItemPosition()をオーバーライドします、フラグメントが古いデータセットのものか、新しいデータセットのものかを何らかの方法で判断する必要があります。

私の場合、左側のペインに上位のアイテムのリストがあり、右側にスワイプビュー(ViewPager)がある2ペインのレイアウトがあります。そこで、PagerAdapter内と、インスタンス化された各ページフラグメント内に、現在のトップアイテムへのリンクを格納します。リストで選択されたトップアイテムが変更されたら、新しいトップアイテムをPagerAdapterに保存し、notifyDataSetChanged()を呼び出します。オーバーライドされた getItemPosition()で、アダプターのトップアイテムとフラグメントのトップアイテムを比較します。 そして、それらが等しくない場合にのみ、私はPOSITION_NONEを返します。 次に、PagerAdapterは、POSITION_NONEを返したすべてのフラグメントを再インスタンス化します。

注意。参照の代わりに上位のアイテムIDを格納する方がよいかもしれません。

以下のコードスニペットは少し概略的なものですが、実際に機能するコードからそれを採用しました。

public class SomeFragment extends Fragment {
  private TopItem topItem;
}

public class SomePagerAdapter extends FragmentStatePagerAdapter {
  private TopItem topItem;

  public void changeTopItem(TopItem newTopItem) {
    topItem = newTopItem;
    notifyDataSetChanged();
  }

  @Override
  public int getItemPosition(Object object) {
    if (((SomeFragment) object).getTopItemId() != topItem.getId()) {
      return POSITION_NONE;
    }
    return super.getItemPosition(object);
  }
}

以前のすべての研究者に感謝します!


2

以下のコードは私のために働いた。

以下のようにFragmentPagerAdapterクラスを拡張するクラスを作成します。

public class Adapter extends FragmentPagerAdapter {

private int tabCount;
private Activity mActivity;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;
private int container_id;
private ViewGroup container;
private List<Object> object;

public Adapter(FragmentManager fm) {
    super(fm);
}

public Adapter(FragmentManager fm, int numberOfTabs , Activity mA) {
    super(fm);
    mActivity = mA;
    mFragmentManager = fm;
    object = new ArrayList<>();
    mFragmentTags = new HashMap<Integer, String>();
    this.tabCount = numberOfTabs;
}

@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return Fragment0.newInstance(mActivity);
        case 1:
            return Fragment1.newInstance(mActivity);
        case 2:
            return Fragment2.newInstance(mActivity);
        default:
            return null;
    }}


@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Log.e("Already defined","Yes");
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        Log.e("Fragment Tag","" + position + ", " + tag);
        mFragmentTags.put(position, tag);
    }else{
        Log.e("Already defined","No");
    }
    container_id = container.getId();
    this.container = container;
    if(position == 0){
        this.object.add(0,object);
    }else if(position == 1){
        this.object.add(1,object);
    }else if(position == 2){
        this.object.add(2,object);
    }
    return object;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    super.destroyItem(container, position, object);
    if (object instanceof Fragment) {
        Log.e("Removed" , String.valueOf(position));
    }
}

@Override
public int getItemPosition (Object object)
{   int index = 0;
    if(this.object.get(0) == object){
        index = 0;
    }else if(this.object.get(1) == object){
        index = 1;
    }else if(this.object.get(2) == object){
        index = 2;
    }else{
        index = -1;
    }
    Log.e("Index" , "..................." + String.valueOf(index));
    if (index == -1)
        return POSITION_NONE;
    else
        return index;
}

public String getFragmentTag(int pos){
    return "android:switcher:"+R.id.pager+":"+pos;
}

public void NotifyDataChange(){
    this.notifyDataSetChanged();
}

public int getcontainerId(){
    return container_id;
}

public ViewGroup getContainer(){
    return this.container;
}

public List<Object> getObject(){
    return this.object;
}

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

次に、作成した各Fragment内にupdateFragmentメソッドを作成します。このメソッドでは、フラグメントで変更する必要があるものを変更します。たとえば、私の場合、Fragment0には.plyファイルへのパスに基づいて3Dオブジェクトを表示するGLSurfaceViewが含まれていたため、updateFragmentメソッド内でこのplyファイルへのパスを変更します。

次に、ViewPagerインスタンスを作成します。

viewPager = (ViewPager) findViewById(R.id.pager);

そしてAdpaterインスタンス

adapter = new Adapter(getSupportFragmentManager(), 3, this);

次にこれを行います

viewPager.setAdapter(adapter);
viewPager.setOffscreenPageLimit(1);

次に、クラス内で上記のAdapterクラスを初期化し、viewPagerを作成しました。フラグメントの1つ(この場合はFragment0)を更新するたびに、以下を使用します。

adapter.NotifyDataChange();

adapter.destroyItem(adapter.getContainer(), 0, adapter.getObject().get(0)); // destroys page 0 in the viewPager.

fragment0 = (Fragment0) getSupportFragmentManager().findFragmentByTag(adapter.getFragmentTag(0)); // Gets fragment instance used on page 0.

fragment0.updateFragment() method which include the updates on this fragment

adapter.instantiateItem(adapter.getContainer(), 0); // re-initialize page 0.

このソリューションは、Alvaro Luis Bustamanteによって提案された手法に基づいていました。


1

1.最初にPageradapterクラスでgetItempositionメソッドを設定する必要があります2.ビューページャーの正確な位置を読み取る必要があります3.次に、その位置を新しい位置のデータの場所として送信します4. setonPageChange内の更新ボタンonclickリスナーを書き込みますリスナー

そのプログラムコードは、特定の位置要素のみを設定するように少し変更されています

public class MyActivity extends Activity {

private ViewPager myViewPager;
private List<String> data;
public int location=0;
public Button updateButton;
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    data = new ArrayList<String>();
    data.add("A");
    data.add("B");
    data.add("C");
    data.add("D");
    data.add("E");
    data.add("F");

    myViewPager = (ViewPager) findViewById(R.id.pager);
    myViewPager.setAdapter(new MyViewPagerAdapter(this, data));

      updateButton = (Button) findViewById(R.id.update);

    myViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int i, float v, int i2) {
             //Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onPageSelected( int i) {
          // here you will get the position of selected page
            final int k = i;
             updateViewPager(k);

        }

        @Override
        public void onPageScrollStateChanged(int i) {

        }
    });
}

private void updateViewPager(final int i) {  
    updateButton.setOnClickListener(new OnClickListener() {

        @Override
        public void onClick(View v) {

            Toast.makeText(MyActivity.this, i+"  Is Selected  "+data.size(), Toast.LENGTH_SHORT).show();
            data.set(i, "Replaced "+i);         
            myViewPager.getAdapter().notifyDataSetChanged();
        }
    });

}

private class MyViewPagerAdapter extends PagerAdapter {

    private List<String> data;
    private Context ctx;

    public MyViewPagerAdapter(Context ctx, List<String> data) {
        this.ctx = ctx;
        this.data = data;
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public int getItemPosition(Object object) {
        return POSITION_NONE;
    }

    @Override
    public Object instantiateItem(View collection, int position) {          

        TextView view = new TextView(ctx);
        view.setText(data.get(position));
        ((ViewPager)collection).addView(view);            
        return view;
    }

    @Override
    public void destroyItem(View collection, int position, Object view) {
         ((ViewPager) collection).removeView((View) view);
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public Parcelable saveState() {
        return null;
    }

    @Override
    public void restoreState(Parcelable arg0, ClassLoader arg1) {
    }

    @Override
    public void startUpdate(View arg0) {
    }

    @Override
    public void finishUpdate(View arg0) {
    }
}
}

1

私のために働いたのは viewPager.getAdapter().notifyDataSetChanged();

アダプタのビュー内部を更新するためにコードを置くgetItemPositionようにように

@Override
public int getItemPosition(Object object) {

    if (object instanceof YourViewInViewPagerClass) { 
        YourViewInViewPagerClass view = (YourViewInViewPagerClass)object;
        view.setData(data);
    }

    return super.getItemPosition(object);
}

return POSITION_NONEそれを回避するための最も正しい方法ではないかもしれませんが、それはうまくいきました(トリックが私にクラッシュを引き起こしたので、オプションではありませんでした)


1

すべてのフラグメントを動的に更新できます。3つのステップで確認できます。

アダプターで:

public class MyPagerAdapter extends FragmentPagerAdapter {
private static int NUM_ITEMS = 3;
private Map<Integer, String> mFragmentTags;
private FragmentManager mFragmentManager;

public MyPagerAdapter(FragmentManager fragmentManager) {
    super(fragmentManager);
    mFragmentManager = fragmentManager;
    mFragmentTags = new HashMap<Integer, String>();
}

// Returns total number of pages
@Override
public int getCount() {
    return NUM_ITEMS;
}

// Returns the fragment to display for that page
@Override
public Fragment getItem(int position) {
    switch (position) {
        case 0:
            return FirstFragment.newInstance();
        case 1:
            return SecondFragment.newInstance();
        case 2:
            return ThirdFragment.newInstance();
        default:
            return null;
    }
}

// Returns the page title for the top indicator
@Override
public CharSequence getPageTitle(int position) {
    return "Page " + position;
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object object = super.instantiateItem(container, position);
    if (object instanceof Fragment) {
        Fragment fragment = (Fragment) object;
        String tag = fragment.getTag();
        mFragmentTags.put(position, tag);
    }
    return object;
}

public Fragment getFragment(int position) {
    Fragment fragment = null;
    String tag = mFragmentTags.get(position);
    if (tag != null) {
        fragment = mFragmentManager.findFragmentByTag(tag);
    }
    return fragment;
}}

今あなたの活動の中で:

public class MainActivity extends AppCompatActivity implements ViewPager.OnPageChangeListener{

MyPagerAdapter mAdapterViewPager;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPager viewPager = (ViewPager) findViewById(R.id.vpPager);
    mAdapterViewPager = new MyPagerAdapter(getSupportFragmentManager());
    viewPager.setAdapter(mAdapterViewPager);
    viewPager.addOnPageChangeListener(this);
}

@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {

    Fragment fragment = mAdapterViewPager.getFragment(position);
    if (fragment != null) {
        fragment.onResume();
    }
}

@Override
public void onPageScrollStateChanged(int state) {

}}

最後にあなたのフラグメントでは、そのようなもの:

public class YourFragment extends Fragment {

// newInstance constructor for creating fragment with arguments
public static YourFragment newInstance() {

    return new YourFragment();
}

// Store instance variables based on arguments passed
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
}

// Inflate the view for the fragment based on layout XML
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment, container, false);
}


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

    //to refresh your view
    refresh();

}}

ここで完全なコードを見ることができます

Alvaro Luis Bustamanteに感謝します。


1

常に戻るのPOSITION_NONEは簡単ですが、すでにインスタンス化されているすべてのページのインスタンス化を呼び起こすので、少し非効率的な方法です。

ライブラリArrayPagerAdapterを作成して、PagerAdaptersの項目を動的に変更しました。

内部的には、このライブラリーのアダプターは、必要な場合POSITION_NONEgetItemPosiition()のみ戻ります。

このライブラリを使用すると、次のように動的にアイテムを変更できます。

@Override
protected void onCreate(Bundle savedInstanceState) {
        /** ... **/
    adapter = new MyStatePagerAdapter(getSupportFragmentManager()
                            , new String[]{"1", "2", "3"});
    ((ViewPager)findViewById(R.id.view_pager)).setAdapter(adapter);
     adapter.add("4");
     adapter.remove(0);
}

class MyPagerAdapter extends ArrayViewPagerAdapter<String> {

    public MyPagerAdapter(String[] data) {
        super(data);
    }

    @Override
    public View getView(LayoutInflater inflater, ViewGroup container, String item, int position) {
        View v = inflater.inflate(R.layout.item_page, container, false);
        ((TextView) v.findViewById(R.id.item_txt)).setText(item);
        return v;
    }
}

ThilsライブラリはFragmentsによって作成されたページもサポートします。


1

これは、サービス(または他のバックグラウンドスレッド)からViewpagerを更新する必要があり、どの提案も機能していない、私のようなすべての人のためのものです。getItemPosition(Object object)は、それ以上の処理なしでそこで呼び出されます。次に、親のPagerAdapterクラスのドキュメント(サブクラスのドキュメントにはありません)で、「データセットの変更はメインスレッドで発生し、notifyDataSetChanged()への呼び出しで終了する必要がある」とわかりました。したがって、この場合の解決策は(FragmentStatePagerAdapterとgetItemPosition(Objectオブジェクトを使用して)POSITION_NONEを返すように設定されている)です。

そして、notifyDataSetChanged()への呼び出し:

runOnUiThread(new Runnable() {
         @Override
         public void run() {
             pager.getAdapter().notifyDataSetChanged();
         }
     });

1

このようにViewpagerにページャートランスフォームを追加できます

myPager.setPageTransformer(true, new MapPagerTransform());

以下のコードでは、ページャーのスクロール時にランタイムでビューの色を変更しました

public class MapPagerTransform implements ViewPager.PageTransformer {


    public void transformPage(View view, float position) {
     LinearLayout  showSelectionLL=(LinearLayout)view.findViewById(R.id.showSelectionLL);

     if (position < 0) {

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else if (position >0){

                showSelectionLL.setBackgroundColor(Color.WHITE);
     }else {
                showSelectionLL.setBackgroundColor(Color.RED);
     }
   }
}

1

私は遅れてイムダムを知っていますが、それでも誰かを助けることができます。私はちょうどその答えを拡張し、コメントも追加しました。

上手、

答え自体は非効率的です

だから、必要なときにだけそれをリフレッシュするために、これを行うことができます

private boolean refresh;

public void refreshAdapter(){
    refresh = true;
    notifyDataSetChanged();
}

@Override
public int getItemPosition(@NonNull Object object) {
    if(refresh){
        refresh = false;
        return POSITION_NONE;
    }else{
        return super.getItemPosition(object);
    }
}

1

ViewPagerは、動的なビュー変更をサポートするように設計されていません。

これに関連する別のバグを探している間に、これを確認しました。https://issuetracker.google.com/issues/36956111、特にhttps://issuetracker.google.com/issues/36956111#comment56

この質問は少し古いですが、Googleは最近この問題をViewPager2で解決しました。手作りの(メンテナンスされておらず、バグの可能性がある)ソリューションを標準のソリューションに置き換えることができます。また、一部の回答のように、ビューを不必要に再作成することも防ぎます。

ViewPager2の例については、https://github.com/googlesamples/android-viewpager2を確認できます

ViewPager2を使用する場合は、build.gradleファイルに次の依存関係を追加する必要があります。

  dependencies {
     implementation 'androidx.viewpager2:viewpager2:1.0.0-beta02'
  }

次に、xmlファイルのViewPagerを次のように置き換えます。

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

その後、アクティビティでViewPagerをViewPager2に置き換える必要があります

ViewPager2には、RecyclerView.AdapterまたはFragmentStateAdapterのいずれかが必要です。

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {

    private Context context;
    private ArrayList<String> arrayList = new ArrayList<>();

    public MyAdapter(Context context, ArrayList<String> arrayList) {
        this.context = context;
        this.arrayList = arrayList;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.tvName.setText(arrayList.get(position));
    }

    @Override
    public int getItemCount() {
        return arrayList.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        TextView tvName;

        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            tvName = itemView.findViewById(R.id.tvName);
        }
    }
} 

TabLayoutを使用していた場合は、TabLayoutMediatorを使用できます。

        TabLayoutMediator tabLayoutMediator = new TabLayoutMediator(tabLayout, viewPager, true, new TabLayoutMediator.OnConfigureTabCallback() {
            @Override
            public void onConfigureTab(@NotNull TabLayout.Tab tab, int position) {
                // configure your tab here
                tab.setText(tabs.get(position).getTitle());
            }
        });

        tabLayoutMediator.attach();

次に、アダプタのデータを変更し、notifyDataSetChangedメソッドを呼び出すことにより、ビューを更新できます。



0

私はデータセットの変更を通知する簡単な方法を作ったと思います:

まず、instantiateItem関数の動作を少し変更します。

    @Override
    public Object instantiateItem(final ViewGroup container, final int position) {
        final View rootView = mInflater.inflate(...,container, false);
        rootView.setTag(position);
        updateView(rootView, position);
        container.addView(rootView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        mViewPager.setObjectForPosition(rootView, position);
        return rootView;
    }

"updateView"の場合、入力するすべてのデータ(setText、setBitmapImage、...)をビューに入力します。

destroyViewが次のように機能することを確認します。

    @Override
    public void destroyItem(final ViewGroup container, final int position, final Object obj) {
        final View viewToRemove = (View) obj;
        mViewPager.removeView(viewToRemove);
    }

ここで、データを変更して変更し、PagerAdapterで次の関数を呼び出す必要があるとします。

    public void notifyDataSetChanged(final ViewPager viewPager, final NotifyLocation fromPos,
            final NotifyLocation toPos) {
        final int offscreenPageLimit = viewPager.getOffscreenPageLimit();
        final int fromPosInt = fromPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : fromPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        final int toPosInt = toPos == NotifyLocation.CENTER ? mSelectedPhotoIndex
                : toPos == NotifyLocation.MOST_LEFT ? mSelectedPhotoIndex - offscreenPageLimit
                        : mSelectedPhotoIndex + offscreenPageLimit;
        if (fromPosInt <= toPosInt) {
            notifyDataSetChanged();
            for (int i = fromPosInt; i <= toPosInt; ++i) {
                final View pageView = viewPager.findViewWithTag(i);
                mPagerAdapter.updateView(pageView, i);
            }
        }
    }

public enum NotifyLocation {
    MOST_LEFT, CENTER, MOST_RIGHT
}

たとえば、何かが変更されたことをviewPagerによって表示されているすべてのビューに通知したい場合は、次を呼び出すことができます。

notifyDataSetChanged(mViewPager,NotifyLocation.MOST_LEFT,NotifyLocation.MOST_RIGHT);

それでおしまい。


0

それだけの価値があるのですが、KitKat +ではadapter.notifyDataSetChanged()setOffscreenPageLimit十分に高ければ、新しいビューを表示するのに十分だと思われます。することで、思い通りの行動がとれますviewPager.setOffscreenPageLimit(2)


0

私は実際に使用notifyDataSetChanged()ViewPagerCirclePageIndicatorその後、電話をかけdestroyDrawingCache()ViewPagerきました。他の解決策はどれもうまくいきませんでした。


元の質問は2年近く前ですが、ここまでの答えがAndroid APIのすべてのバージョンに当てはまるとは限らないため、Android APIには多くの違いがあると思います。
C0deAttack 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.