互換ライブラリの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) {
}
}
}
これを実現するにはいくつかの方法があります。
最初の方法は簡単ですが、少し非効率的です。
このようにあなたのgetItemPosition
でPagerAdapter
をオーバーライドしてください:
public int getItemPosition(Object object) {
return POSITION_NONE;
}
このように、notifyDataSetChanged()
を呼び出すと、ビューポケットベルはすべてのビューを削除し、それらすべてをリロードします。そのため、リロード効果が得られます。
2番目のオプション、 Alvaro Luis Bustamante(以前のalvarolb)によって推奨された は、新しいビューをインスタンス化するときにsetTag()
のinstantiateItem()
メソッドを使うことです。 notifyDataSetChanged()
を使う代わりに、更新したいビューを見つけるためにfindViewWithTag()
を使うことができます。
2番目のアプローチは非常に柔軟で高性能です。元の研究のためのalvarolbへの賛辞。
私はPagerAdapter
にはいかなる種類のバグもないと思います。問題は、それがどのように機能するかを理解するのが少し複雑なことです。ここで説明されている解決策を見ると、誤解されているので、私の視点から見たインスタンス化されたビューの使用法はよくありません。
ここ数日、私はPagerAdapter
とViewPager
を使って仕事をしてきましたが、次のことがわかりました。
PagerAdapter
のnotifyDataSetChanged()
メソッドは、基礎となるページが変更されたことをViewPager
に通知するだけです。たとえば、動的にページを作成/削除した場合(リストに項目を追加または削除した場合)、ViewPager
がそれを処理します。この場合、私はViewPager
がgetItemPosition()
とgetCount()
メソッドを使って新しいビューが削除されるべきかインスタンス化されるべきかを決定すると思います。
私は、notifyDataSetChanged()
呼び出しの後、ViewPager
が子ビューを取得し、getItemPosition()
を使用してその位置を確認すると思います。子ビューに対してこのメソッドがPOSITION_NONE
を返す場合、ViewPager
はビューが削除されたことを認識し、destroyItem()
を呼び出し、このビューを削除します。
このように、ページの内容を更新するだけの場合は、getItemPosition()
をオーバーライドして常にPOSITION_NONE
を返すことはまったく間違っています。以前に作成されたビューは破棄され、notifyDatasetChanged()
を呼び出すたびに新しいビューが作成されます。いくつかのTextView
sだけではそれほど問題ではないように思えるかもしれませんが、ListViewsがデータベースから移入されるような複雑なビューがあるとき、これは本当の問題とリソースの浪費です。
そのため、ビューを削除して再度インスタンス化しなくても、ビューの内容を効率的に変更する方法がいくつかあります。それはあなたが解決したい問題によります。 私のアプローチは、setTag()
メソッド内のインスタンス化されたビューに対してinstantiateItem()
メソッドを使用することです。そのため、データを変更したり、必要なビューを無効にしたりする場合は、ViewPager
でfindViewWithTag()
メソッドを呼び出して、以前にインスタンス化したビューを取得し、毎回新しいビューを削除/作成せずに必要に応じて変更/使用できます。値を更新したいのですが。
たとえば、100個のページに100個のTextView
があり、1つの値だけを定期的に更新したいとします。前に説明したアプローチでは、これは各更新で100個のTextView
を削除してインスタンス化することを意味します。それは意味がありません...
FragmentPagerAdapter
をFragmentStatePagerAdapter
に変更します。
getItemPosition()
メソッドをオーバーライドしてPOSITION_NONE
を返します。
最終的には、ビューページャのnotifyDataSetChanged()
を監視します。
答え 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
で同様のコードを使用できます。私のコードでは、まったく同じ方法を使用しています。
同じ問題がありました。私にとっては、FragmentStatePagerAdapterを拡張し、以下のメソッドをオーバーライドするように働きました。
@Override
public Parcelable saveState() {
return null;
}
@Override
public void restoreState(Parcelable state, ClassLoader loader) {
}
上記のすべての解決策を試してこの問題を解決し、また this 、 this 、および this この問題を解決し、ViewPager
を作成して古いFragment
を破棄し、_pager
を新しいFragment
sで埋めるために失敗しました。私は次のように問題を解決しました:
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();
役に立てば幸いです。
私は同じ問題を抱えており、私の解決策はViewPagerAdapter#getItemId(int position)
を上書きすることです。
@Override
public long getItemId(int position) {
return mPages.get(position).getId();
}
デフォルトでは、このメソッドはアイテムの位置を返します。 ViewPager
はitemId
が変更されたかどうかをチェックし、変更された場合にのみページを再作成するとします。しかし、実際にはページが異なっていても、上書きされないバージョンはitemId
と同じ位置を返し、ViewPagerはそのページが1ページに置き換えられると定義していないため、再作成する必要があります。
これを使用するには、各ページにlong id
が必要です。通常、それはユニークであることが期待されています、しかし私はこの場合、それがちょうど同じページの前の値と異なるべきであることを提案します。そのため、ここではアダプタ内の連続カウンタまたはランダムな整数(広い分布を持つ)を使用することができます。
このトピックの解決策として挙げられているタグのタグの使用よりも一貫した方法であると思います。しかし、おそらくすべてのケースではありません。
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();
}
}
OPが彼の質問を投げてから2年半後、この問題はまだよく、まだ問題です。これに対するGoogleの優先順位はそれほど高くないことは明らかなので、修正を見つけるのではなく、回避策を見つけました。私にとって大きな突破口は、問題の本当の原因が何であるかを見つけることでした( この記事 で受け入れられている答えを見てください)。アクティブページが正しく更新されていないことが問題であることが明らかになったら、次の回避策は明白でした。
私のフラグメント(ページ)で:
私のアクティビティでは、ページの読み込みを行います。
その後、2ページ目のページをリロードしても、古いデータが表示されるバグがあります。ただし、これらのページは更新され、新しいデータが表示されます。この更新はページが表示される前に行われるため、ユーザーはページが正しくないことを認識できません。
これが誰かに役立つことを願っています!
もっと簡単な方法:FragmentPagerAdapter
を使い、ページビューをフラグメントにラップする。彼らは更新されますか
私はこの問題について非常に興味深い決断を下した。すべてのフラグメントをメモリに保持する 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);
万が一誰かが 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つのページがあり、そのうちの1ページで他の3ページのビューを更新していたという同様の問題を抱えていました。現在のページに隣接するページのウィジェット(SeekBars、TextViewなど)を更新することができました。最後の2ページには、mTabsAdapter.getItem(position)
を呼び出すときに未初期化ウィジェットがあります。
私の問題を解決するために、私はsetSelectedPage(index)
を呼び出す前にgetItem(position)
を使いました。これによりページがインスタンス化され、各ページの値とウィジェットを変更できるようになります。
すべての更新が終わったら、setSelectedPage(position)
に続けてnotifyDataSetChanged()
を使います。
更新中のメインページのリストビューにわずかなちらつきが見られますが、目立つものはありません。私はそれを徹底的にテストしていませんが、それは私の当面の問題を解決します。
この問題を捜し求めた結果、私は本当に良い解決策を見つけました。これが正しい方法です。基本的に、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);
}
お役に立てれば!
他の誰かが役に立つと思った場合に備えて、この回答を投稿しているだけです。全く同じことをするために、私は単に互換性ライブラリからViewPagerとPagerAdapterのソースコードを取り、それを私のコードの中にコンパイルしました(あなたはすべてのエラーを整理してあなた自身をインポートする必要があります、しかしそれは間違いなくできます)。
次に、CustomViewPagerで、updateViewAt(int position)というメソッドを作成します。ビュー自体はViewPagerクラスで定義されたArrayList mItemsから取得できます(インスタンス化項目でビューのIDを設定し、このIDをupdateViewAt()メソッドの位置と比較する必要があります)。その後、必要に応じてビューを更新できます。
すべてのフラグメントを動的に更新することができます、あなたは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。
このように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);
}
}
}
常に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によって作成されたページもサポートします。
これは私のようなもの全てで、Viewpagerをサービス(または他のバックグラウンドスレッド)から更新する必要があり、提案はどれもうまくいきませんでした。少しのログチェックの後、notifyDataSetChanged()メソッドは決して返されません。 getItemPosition(Object object)は、それ以上の処理を行わずにそこですべての終わりと呼ばれます。それから、親のPagerAdapterクラスのドキュメント(サブクラスのドキュメントにはありません)で、「データセットの変更はメインスレッドで行わなければならず、notifyDataSetChanged()の呼び出しで終了する必要があります」ということがわかりました。したがって、この場合の実用的な解決策は、(POSITION_NONEを返すように設定されたFragmentStatePagerAdapterおよびgetItemPosition(Object object)を使用する)ことでした。
そして、notifyDataSetChanged()を呼び出します。
runOnUiThread(new Runnable() {
@Override
public void run() {
pager.getAdapter().notifyDataSetChanged();
}
});
私は、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が提案した手法に基づいています。
私にとってうまくいったのは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
トリックは私にとってクラッシュを引き起こしたので、選択肢はありませんでした)。
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メソッドを呼び出すことにより、ビューを更新できます。
私の場合は、そのフラグメントが@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からビューに戻ることができます。
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) {
}
}
}
私は実際にViewPager
とCirclePageIndicator
にnotifyDataSetChanged()
を使い、その後ViewPager
にdestroyDrawingCache()
を呼び出してもうまくいきます。他の解決策はどれも私にとってはうまくいきませんでした。
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
}
}
POSITION_NONE
を返してすべてのフラグメントをもう一度作成する代わりに、ここで提案したように実行できます。 ViewPagerを動的に更新しますか。
私にとってうまくいったことは、遷移の終わりに呼び出されるfinishUpdate
メソッドをオーバーライドし、そこでnotifyDataSetChanged()
を実行することでした。
public class MyPagerAdapter extends FragmentPagerAdapter {
...
@Override
public void finishUpdate(ViewGroup container) {
super.finishUpdate(container);
this.notifyDataSetChanged();
}
...
}
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は現在表示されているビューがまだアダプタ上に存在するかどうかを判断し、それに応じて動作することができます。
この単純なコードが必要です。
コード:
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);
}
}
}
説明:
offscreenPageLimit
name__のViewPager
name__を変更していない場合は、進む方向に応じて常に3から4つの子があります。また、子の中に正しいコンテンツを表示するために、適切なコンテンツを取得するためにアダプタを使用します。 ViewPager
name__でremoveAllViews()
を呼び出すと、実際には3から4のView
name__が実際にはWindow
nameの階層から削除され、instantiateItem(ViewGroup viewPager, int index)
を呼び出しても、3から4のView
name__sしか再作成されません。それからすべてが正常に戻り、スワイプしてスクロールすると、ViewPager
name__はそのアダプターを使用して内容を表示します。たとえばoffscreenPageLimit
name__を5に設定した場合、その子の数はすべてのデバイスで同じになるわけではありませんが、おそらく11から12人程度の子になりますが、それだけではありません。これは速い。
データセットの変更を通知する簡単な方法を作ったと思います。
まず、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);
それでおしまい。
私は遅くダムを知っていますが、それでも誰かを助けることができます。私はただ答えを延長するだけで、コメントも追加しました。
まあ、
答え自体は非効率的だと言っています
必要なときにのみ更新するために、これを行うことができます
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);
}
}
私の経験からの最良の解決策: https://stackoverflow.com/a/44177688/3118950 、これはlong getItemId()
をオーバーライドし、デフォルトの位置の代わりにユニークなIDを返します。その答えに加えて、総量がページ制限を下回り、フラグメントが置き換えられたときにonDetach()
/onDestory()
が呼び出されない場合、古いフラグメントはフラグメントマネージャに保持されることに気づくためにインポートされます。
価値があるために、KitKat +では、setOffscreenPageLimit
が十分に高い場合、adapter.notifyDataSetChanged()
は新しいビューを表示するのに十分であると思われます。私はviewPager.setOffscreenPageLimit(2)
をすることによって望ましい振る舞いを得ることができます。
これはひどい問題で、素晴らしい解決策を提示できてうれしいです。シンプルで効率的、そして効果的!
下記を参照してください、コードは いつ を示すためにフラグを使って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();
}
}
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());