web-dev-qa-db-ja.com

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

互換ライブラリのViewPagerを使用しています。ページをめくることができるいくつかのビューを表示することに成功しました。

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

私はmAdapter.notifyDataSetChanged()mViewPager.invalidate()を呼び出すなど、あらゆる種類のことを試してみました。新しいList of dataを使いたいときには、毎回、まったく新しいアダプタを作成しています。

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

更新: 私はちょっとしたテストプロジェクトを作り、ほとんどビューを更新することができました。以下のクラスを貼り付けます。

更新されていないように見えるのは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) {
        }
    }
}
560
C0deAttack

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

最初の方法は簡単ですが、少し非効率的です。

このようにあなたのgetItemPositionPagerAdapterをオーバーライドしてください:

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

このように、notifyDataSetChanged()を呼び出すと、ビューポケットベルはすべてのビューを削除し、それらすべてをリロードします。そのため、リロード効果が得られます。

2番目のオプション、 Alvaro Luis Bustamante(以前のalvarolb)によって推奨された は、新しいビューをインスタンス化するときにsetTag()instantiateItem()メソッドを使うことです。 notifyDataSetChanged()を使う代わりに、更新したいビューを見つけるためにfindViewWithTag()を使うことができます。

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

791
rui.araujo

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

ここ数日、私はPagerAdapterViewPagerを使って仕事をしてきましたが、次のことがわかりました。

PagerAdapternotifyDataSetChanged()メソッドは、基礎となるページが変更されたことをViewPagerに通知するだけです。たとえば、動的にページを作成/削除した場合(リストに項目を追加または削除した場合)、ViewPagerがそれを処理します。この場合、私はViewPagergetItemPosition()getCount()メソッドを使って新しいビューが削除されるべきかインスタンス化されるべきかを決定すると思います。

私は、notifyDataSetChanged()呼び出しの後、ViewPagerが子ビューを取得し、getItemPosition()を使用してその位置を確認すると思います。子ビューに対してこのメ​​ソッドがPOSITION_NONEを返す場合、ViewPagerはビューが削除されたことを認識し、destroyItem()を呼び出し、このビューを削除します。

このように、ページの内容を更新するだけの場合は、getItemPosition()をオーバーライドして常にPOSITION_NONEを返すことはまったく間違っています。以前に作成されたビューは破棄され、notifyDatasetChanged()を呼び出すたびに新しいビューが作成されます。いくつかのTextViewsだけではそれほど問題ではないように思えるかもしれませんが、ListViewsがデータベースから移入されるような複雑なビューがあるとき、これは本当の問題とリソースの浪費です。

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

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

464

FragmentPagerAdapterFragmentStatePagerAdapterに変更します。

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

最終的には、ビューページャのnotifyDataSetChanged()を監視します。

77
Kit

答え 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();
}

ビューを更新するために、実際にはinstantiateItemnotifyDataSetChangedで同様のコードを使用できます。私のコードでは、まったく同じ方法を使用しています。

41
Grimmace

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

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

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

}
25
Mario Mosca

上記のすべての解決策を試してこの問題を解決し、また thisthis 、および this この問題を解決し、ViewPagerを作成して古いFragmentを破棄し、_pagerを新しいFragmentsで埋めるために失敗しました。私は次のように問題を解決しました:

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

 public class myPagerAdapter extends FragmentPagerAdapter {

2)ViewPagerおよびtitleを保存するfragmentのアイテムを次のように作成します。

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インスタンスを取得させ、次のように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();

役に立てば幸いです。

20
Sami Eltamawy

私は同じ問題を抱えており、私の解決策はViewPagerAdapter#getItemId(int position)を上書きすることです。

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

デフォルトでは、このメソッドはアイテムの位置を返します。 ViewPageritemIdが変更されたかどうかをチェックし、変更された場合にのみページを再作成するとします。しかし、実際にはページが異なっていても、上書きされないバージョンはitemIdと同じ位置を返し、ViewPagerはそのページが1ページに置き換えられると定義していないため、再作成する必要があります。

これを使用するには、各ページにlong idが必要です。通常、それはユニークであることが期待されています、しかし私はこの場合、それがちょうど同じページの前の値と異なるべきであることを提案します。そのため、ここではアダプタ内の連続カウンタまたはランダムな整数(広い分布を持つ)を使用することができます。

このトピックの解決策として挙げられているタグのタグの使用よりも一貫した方法であると思います。しかし、おそらくすべてのケースではありません。

9
atlascoder

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();
    }
}
5
evan.z

OPが彼の質問を投げてから2年半後、この問題はまだよく、まだ問題です。これに対するGoogleの優先順位はそれほど高くないことは明らかなので、修正を見つけるのではなく、回避策を見つけました。私にとって大きな突破口は、問題の本当の原因が何であるかを見つけることでした( この記事 で受け入れられている答えを見てください)。アクティブページが正しく更新されていないことが問題であることが明らかになったら、次の回避策は明白でした。

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

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

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

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

その後、2ページ目のページをリロードしても、古いデータが表示されるバグがあります。ただし、これらのページは更新され、新しいデータが表示されます。この更新はページが表示される前に行われるため、ユーザーはページが正しくないことを認識できません。

これが誰かに役立つことを願っています!

5
RichardP

もっと簡単な方法:FragmentPagerAdapterを使い、ページビューをフラグメントにラップする。彼らは更新されますか

5
alfongj

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

両方のアダプタの実現方法は同じです。ですので、 " extend FragmentPagerAdapter "を " extend FragmentStatePagerAdapter "に変更するだけです。

これらすべての解決策は私を助けてくれませんでした。ですから、私は実用的な解決策を見つけました。毎回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);
5
Mahdi Perfect

万が一誰かが FragmentStatePagerAdapter を基にしたアダプタ(ViewPagerに表示目的に必要な最小限のページを作成させます)を使用している場合でも、アダプタのgetItemPositionを上書きするという@ rui.araujoの答えは無駄ですしかし、それでもまだ改善することができます。

擬似コードでは:

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;
}
4
Jerry Tian

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

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

すべての更新が終わったら、setSelectedPage(position)に続けてnotifyDataSetChanged()を使います。

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

3
enKoder

この問題を捜し求めた結果、私は本当に良い解決策を見つけました。これが正しい方法です。基本的に、instantiateItemはビューがインスタンス化されたときにのみ呼び出され、ビューが破棄されない限り二度と呼び出されません(これはgetItemPosition関数をオーバーライドしてPOSITION_NONEを返すときに起こります)。代わりに、作成したビューを save 作成してアダプタで更新するか、他の人が更新できるようにget関数を生成するか、アダプタを更新するset関数(私のお気に入り)を使用します。

だから、あなたの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);
}

お役に立てれば!

3
German

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

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

3
Naveen LP

すべてのフラグメントを動的に更新することができます、あなたは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
Cabezas

このように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
Akshay dashore

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

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

内部的には、このライブラリのアダプタは必要なときだけgetItemPosiition()POSITION_NONEを返します。

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

@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
T.Nakama

これは私のようなもの全てで、Viewpagerをサービス(または他のバックグラウンドスレッド)から更新する必要があり、提案はどれもうまくいきませんでした。少しのログチェックの後、notifyDataSetChanged()メソッドは決して返されません。 getItemPosition(Object object)は、それ以上の処理を行わずにそこですべての終わりと呼ばれます。それから、親のPagerAdapterクラスのドキュメント(サブクラスのドキュメントにはありません)で、「データセットの変更はメインスレッドで行わなければならず、notifyDataSetChanged()の呼び出しで終了する必要があります」ということがわかりました。したがって、この場合の実用的な解決策は、(POSITION_NONEを返すように設定されたFragmentStatePagerAdapterおよびgetItemPosition(Object object)を使用する)ことでした。

そして、notifyDataSetChanged()を呼び出します。

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

私は、ViewPagerの論理を持っていると思います。

一連のページを更新して新しいデータセットに基づいて表示する必要がある場合は、 notifyDataSetChanged() を呼び出します。それから、ViewPagerは getItemPosition() に対していくつかの呼び出しを行い、そこにObjectとしてFragmentを渡します。このフラグメントは、古いデータセット(破棄したいもの)または新しいデータセット(表示したいもの)のどちらかになります。それで、 getItemPosition() をオーバーライドして、私のFragmentが古いデータセットからのものか新しいものからのものかを何らかの方法で判断しなければなりません。

私の場合は、左ペインにトップアイテムのリスト、右にスワイプビュー(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);
  }
}

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

以下のコードは私のために働きました。

以下のように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を作成しました。フラグメント(私たちの場合は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
Rabee

私にとってうまくいったのは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
Fonix

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が必要です。この場合、RecyclerView.Adapterになります。

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
Yves Delerm

私の場合は、そのフラグメントが@override onCreateView(...)に入るたびに値を再起動していたので、次のようにnullまたは空の値を処理します。

if(var == null){
    var = new Something();
}

if(list.isEmpty()){
    list = new ArrayList<MyItem>();
}

if(myAdapter == null){
    myAdapter = new CustomAdapter();
    recyclerView.setAdapter(myAdapter);
}

// etc...

フラグメントクラスは、変更するたびにonCreateViewに入り、実行時にUIからビューに戻ることができます。

0
Ninja Coding

1.まず最初にPageradapterクラスにgetItempositionメソッドを設定する必要がありますリスナー

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

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) {
    }
}
}
0
Naveen Kumar

私は実際にViewPagerCirclePageIndicatornotifyDataSetChanged()を使い、その後ViewPagerdestroyDrawingCache()を呼び出してもうまくいきます。他の解決策はどれも私にとってはうまくいきませんでした。

0
czaku

TabPlayoutをViewPagerAdapterで使用しています。フラグメント間でデータをやり取りするため、またはフラグメント間で通信するために、完全に正常に機能する以下のコードを使用し、フラグメントが表示されるたびに更新します。 2番目のフラグメントの内側のボタンをクリックすると、以下のコードが書き込まれます。

b.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            String text=e1.getText().toString(); // get the text from EditText

            // move from one fragment to another fragment on button click
            TabLayout tablayout = (TabLayout) getActivity().findViewById(R.id.tab_layout); // here tab_layout is the id of TabLayout which is there in parent Activity/Fragment
            if (tablayout.getTabAt(1).isSelected()) { // here 1 is the index number of second fragment i-e current Fragment

                LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(getContext());
                Intent i = new Intent("EDIT_TAG_REFRESH");
                i.putExtra("MyTextValue",text);
                lbm.sendBroadcast(i);

            }
            tablayout.getTabAt(0).select(); // here 0 is the index number of first fragment i-e to which fragment it has to moeve

        }
    });

以下は受信フラグメントの最初のフラグメント(私の場合)i-eに書かれなければならないコードです。

MyReceiver r;
Context context;
String newValue;
public void refresh() {
    //your code in refresh.
    Log.i("Refresh", "YES");
}
public void onPause() {
    super.onPause();

    LocalBroadcastManager.getInstance(context).unregisterReceiver(r);
}
public void onResume() {
    super.onResume();
    r = new MyReceiver();
    LocalBroadcastManager.getInstance(getActivity()).registerReceiver(r,
            new IntentFilter("EDIT_TAG_REFRESH"));
} // this code has to be written before onCreateview()


 // below code can be written any where in the fragment
 private class MyReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
    PostRequestFragment.this.refresh();
        String action = intent.getAction();
        newValue=intent.getStringExtra("MyTextValue");
        t1.setText(newValue); // upon Referesh set the text
    }
}
0
Tara

POSITION_NONEを返してすべてのフラグメントをもう一度作成する代わりに、ここで提案したように実行できます。 ViewPagerを動的に更新しますか。

0
M-WaJeEh

私にとってうまくいったことは、遷移の終わりに呼び出されるfinishUpdateメソッドをオーバーライドし、そこでnotifyDataSetChanged()を実行することでした。

public class MyPagerAdapter extends FragmentPagerAdapter {
    ...
    @Override
    public void finishUpdate(ViewGroup container) {
        super.finishUpdate(container);
        this.notifyDataSetChanged();
    }
    ...
}
0
horro

GetItemPosition()をオーバーライドする正しい方法:

@Override
public int getItemPosition(@NonNull Object object) {
    if (!(object instanceof MyPageView)) {
        // Should never happen
        return super.getItemPosition(object);
    }
    MyDataObject dataObject = ((MyPageView) object).getData();
    if (!(dataObject instanceof MyDataObject)) {
        // Should never happen
        return super.getItemPosition(object);
    }
    if (lstItems.contains(dataObject)) {
        return POSITION_UNCHANGED;
    }
    return POSITION_NONE;
}

手短に言えば、ビューをペイントするために使用しているデータをビューに添付または保存する必要があります。そこからgetItemPosition()が呼び出されると、ViewPagerは現在表示されているビューがまだアダプタ上に存在するかどうかを判断し、それに応じて動作することができます。

0
Reaper

この単純なコードが必要です。

コード:

private void updateAdapter(){
    if(adapterViewPager != null){
        int from = vpMyViewPager.getCurrentItem() - vpMyViewPager.getOffscreenPageLimit();
        int to   = vpMyViewPager.getCurrentItem() + vpMyViewPager.getOffscreenPageLimit();
        vpMyViewPager.removeAllViews();
        for(int i = from; i <= to; i++){
            if(i < 0){
                continue;
            }
            adapterViewPager.instantiateItem(vpMyViewPager, i);
        }
    }
}

説明:

offscreenPageLimitname__のViewPagername__を変更していない場合は、進む方向に応じて常に3から4つの子があります。また、子の中に正しいコンテンツを表示するために、適切なコンテンツを取得するためにアダプタを使用します。 ViewPagername__でremoveAllViews()を呼び出すと、実際には3から4のViewname__が実際にはWindownameの階層から削除され、instantiateItem(ViewGroup viewPager, int index)を呼び出しても、3から4のViewname__sしか再作成されません。それからすべてが正常に戻り、スワイプしてスクロールすると、ViewPagername__はそのアダプターを使用して内容を表示します。たとえばoffscreenPageLimitname__を5に設定した場合、その子の数はすべてのデバイスで同じになるわけではありませんが、おそらく11から12人程度の子になりますが、それだけではありません。これは速い。

0
M D P

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

まず、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

私は遅くダムを知っていますが、それでも誰かを助けることができます。私はただ答えを延長するだけで、コメントも追加しました。

まあ、

答え自体は非効率的だと言っています

必要なときにのみ更新するために、これを行うことができます

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);
    }
}
0
Har Kal

私の経験からの最良の解決策: https://stackoverflow.com/a/44177688/3118950 、これはlong getItemId()をオーバーライドし、デフォルトの位置の代わりにユニークなIDを返します。その答えに加えて、総量がページ制限を下回り、フラグメントが置き換えられたときにonDetach()/onDestory()が呼び出されない場合、古いフラグメントはフラグメントマネージャに保持されることに気づくためにインポートされます。

0
Eliyahu Shwartz

価値があるために、KitKat +では、setOffscreenPageLimitが十分に高い場合、adapter.notifyDataSetChanged()は新しいビューを表示するのに十分であると思われます。私はviewPager.setOffscreenPageLimit(2)をすることによって望ましい振る舞いを得ることができます。

0
mszaro

これはひどい問題で、素晴らしい解決策を提示できてうれしいです。シンプルで効率的、そして効果的!

下記を参照してください、コードは いつ を示すためにフラグを使ってPOSITION_NONEを返すことを示します

public class ViewPagerAdapter extends PagerAdapter
{
    // Members
    private boolean mForceReinstantiateItem = false;

    // This is used to overcome terrible bug that Google isn't fixing
    // We know that getItemPosition() is called right after notifyDataSetChanged()
    // Therefore, the fix is to return POSITION_NONE right after the notifyDataSetChanged() was called - but only once
    @Override
    public int getItemPosition(Object object)
    {
        if (mForceReinstantiateItem)
        {
            mForceReinstantiateItem = false;
            return POSITION_NONE;
        }
        else
        {
            return super.getItemPosition(object);
        }
    }

    public void setData(ArrayList<DisplayContent> newContent)
    {
        mDisplayContent = newContent;
        mForceReinstantiateItem = true;
        notifyDataSetChanged();
    }

}
0

FragmentPagerAdapterは前のfragmentsをきれいにしないので、フラグメントマネージャでViewPagerに追加することができるので、私はここに私自身の解決策を残します。それで、私はFragmentPagerAdapterを追加する前に実行するメソッドを作成します:

(私の場合、3つ以上のフラグメントを追加することはありませんが、例えばgetFragmentManager().getBackStackEntryCount()を使用して、すべてのfragmentsをチェックすることができます。

/**
 * this method is solving a bug in FragmentPagerAdapter which don't delete in the fragment manager any previous fragments in a ViewPager.
 *
 * @param containerId
 */
public void cleanBackStack(long containerId) {
    FragmentTransaction transaction = getFragmentManager().beginTransaction();
    for (int i = 0; i < 3; ++i) {
        String tag = "Android:switcher:" + containerId + ":" + i;
        Fragment f = getFragmentManager().findFragmentByTag(tag);
        if (f != null) {
            transaction.remove(f);
        }
    }
    transaction.commit();
}

フレームワークによるタグの作成方法が変わると、動作が停止するので、これは回避策であることを私は知っています。

(現在は"Android:switcher:" + containerId + ":" + i

それからそれを使う方法はコンテナを入手した後です:

ViewPager viewPager = (ViewPager) view.findViewById(R.id.view_pager);
cleanBackStack(viewPager.getId());
0
lm2a