ViewPagerの一部としてListFragmentのデータを更新する


142

Androidでv4互換のViewPagerを使用しています。私のFragmentActivityには、ViewPagerのさまざまなページにさまざまな方法で表示される一連のデータがあります。これまでのところ、同じListFragmentのインスタンスは3つしかありませんが、将来的には、異なるListFragmentのインスタンスが3つになる予定です。ViewPagerは垂直の電話画面上にあり、リストは並んでいません。

これで、ListFragmentのボタンが(FragmentActivityを介して)個別のフルページアクティビティを開始し、FragmentActivityがデータを変更して保存し、ViewPagerのすべてのビューを更新しようとします。私が行き詰まっているのはここです。

public class ProgressMainActivity extends FragmentActivity
{
    MyAdapter mAdapter;
    ViewPager mPager;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
    ...
        mAdapter = new MyAdapter(getSupportFragmentManager());

        mPager = (ViewPager) findViewById(R.id.viewpager);
        mPager.setAdapter(mAdapter);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data)
    {
        ...
        updateFragments();
        ...
    }
    public void updateFragments()
    {
        //Attempt 1:
        //mAdapter.notifyDataSetChanged();
        //mPager.setAdapter(mAdapter);

        //Attempt 2:
        //HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentById(mAdapter.fragId[0]);
        //fragment.updateDisplay();
    }

    public static class MyAdapter extends FragmentPagerAdapter implements
         TitleProvider
    {
      int[] fragId = {0,0,0,0,0};
      public MyAdapter(FragmentManager fm)
      {
         super(fm);
      }
      @Override
      public String getTitle(int position){
         return titles[position];
      }
      @Override
      public int getCount(){
         return titles.length;
      }

      @Override
      public Fragment getItem(int position)
      {

         Fragment frag = HomeListFragment.newInstance(position);
         //Attempt 2:
         //fragId[position] = frag.getId();
         return frag;
      }

      @Override
      public int getItemPosition(Object object) {
         return POSITION_NONE; //To make notifyDataSetChanged() do something
     }
   }

    public class HomeListFragment extends ListFragment
    {
    ...
        public static HomeListFragment newInstance(int num)
        {
            HomeListFragment f = new HomeListFragment();
            ...
            return f;
        }
   ...

ご覧のとおり、最初の試みはFragmentPagerAdapter全体でnotifyDataSetChangedを実行することでしたが、データが時々更新されることが示されましたが、IllegalStateExceptionを受け取った他のもの:onSaveInstanceStateの後でこのアクションを実行できません。

私の2番目の試みは、ListFragmentで更新関数を呼び出そうと試みましたが、getItemのgetIdは0を返しました。

findFragmentById()またはfindFragmentByTag()を使用して、FragmentManagerからFragmentへの参照を取得する

しかし、フラグメントのタグやIDがわかりません!ViewPager用のandroid:id = "@ + id / viewpager"と、ListFragmentレイアウトのListView用のandroid:id = "@ android:id / list"がありますが、これらは有用ではないと思います。

それで、私は次のいずれかを行うことができます:a)ViewPager全体を一度に安全に更新します(理想的にはユーザーを以前のページに戻します)-ユーザーがビューの変更を確認しても問題ありません。または、b)影響を受ける各ListFragmentで関数を呼び出して、手動でListViewを更新することもできます。

どんな助けも感謝して受け入れられます!

回答:


58

Fragementがインスタンス化されるたびにタグを記録するようにしてください。

public class MPagerAdapter extends FragmentPagerAdapter {
    private Map<Integer, String> mFragmentTags;
    private FragmentManager mFragmentManager;

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

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

    @Override
    public Fragment getItem(int position) {
        return Fragment.instantiate(mContext, AFragment.class.getName(), null);
    }

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

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

これは問題ありませんが、私のクラスでは機能しません。うまくいきますか?私はまだnullフラグメントを取得します。
Sandalone

1
それは私のために働き、私はいくつかのプロジェクトでそれを使用しました。FragmentPagerAdapterのソースコードを読み取って、いくつかのログを印刷してデバッグできます。
フェイロン

画面を回転した後でも、おかげで魅力のように機能します。このソリューションを使用して、FragmentBからMyActivityを使用してFragmentAのコンテンツを変更することもできました。
Mehdi Fanai 2013

5
それはのために働くFragmentPagerAdaperが、ために失敗したFragmentStatePagerAdaperFragment.getTag()常に返すnull
エンジン白

1
私はこのソリューションを試し、フラグメントのデータを更新するメソッドを作成しました。しかし今、私は断片の見方を見ることができません。データが断片化するのがわかりますが、ビューは表示されません。何か助け?
Arun Badole

256

バークサイドの回答は機能しますFragmentPagerAdapterが、機能しません。FragmentStatePagerAdapter渡されたフラグメントにタグを設定しないためFragmentManagerです。

これFragmentStatePagerAdapterで、そのinstantiateItem(ViewGroup container, int position)呼び出しを使用して、問題なく対処できるようです。位置のフラグメントへの参照を返しますpositionFragmentStatePagerAdapter問題のフラグメントへの参照を既に保持している場合instantiateItemは、そのフラグメントへの参照を返すだけgetItem()で、再度インスタンス化するために呼び出しません。

したがって、現在フラグメント#50を調べており、フラグメント#49にアクセスしたいとします。それらが近いので、#49がすでにインスタンス化されている可能性が高いです。そう、

ViewPager pager = findViewById(R.id.viewpager);
FragmentStatePagerAdapter a = (FragmentStatePagerAdapter) pager.getAdapter();
MyFragment f49 = (MyFragment) a.instantiateItem(pager, 49)

5
"a"をPagerAdapter(PagerAdapter a = pager.getAdapter();)にするだけで簡単にできます。またはMyFragment f49 =(MyFragment)pager.getAdapter()。instantiateItem(pager、49); instantiateItem()はPagerAdapterからのものなので、instantiateItem()を呼び出すために型キャストする必要はありません
Dandre Allison

12
...そして誰かが不思議に思っている場合に備えて、ViewPagerは、表示されたフラグメントのインデックス(ViewPager.getCurrentItem())を追跡します。これは、上記の例では49です...しかし、私はViewPager APIが実際のFragmentへの参照を直接返さないことに驚いています。
mblackwell8 2013年

6
FragmentStatePagerAdapterコードの上、使用してa.instantiateItem(viewPager, viewPager.getCurrentItem());完璧に動作します@ mblackwell8によって提案されたように(49を取り除くために)。
セドリックサイモン

1
ちょっと待って、フラグメント内のビューに対して物事を行えるようにするためだけに、ViewPagerに含まれるすべてのフラグメントにViewPagerを渡す必要がありますか?現在、現在のページでonCreateで行うすべてのことは、次のページで行われます。これはリークに関して非常に怪しいように聞こえます。
G_V 2014

への呼び出しはすべて/ 呼び出しinstantiateItemで囲む必要があることに言及することが重要です。:詳細は、同様の質問に対する私の答えであるstackoverflow.com/questions/14035090/...startUpdatefinishUpdate
morgwai

154

OK、自分の質問でリクエストb)を実行する方法を見つけたので、他の人の利益のために共有します。ViewPager内のフラグメントのタグはの形式"android:switcher:VIEWPAGER_ID:INDEX"でありVIEWPAGER_IDR.id.viewpagerはXMLレイアウトで、INDEXはビューページャー内の位置です。したがって、位置がわかっている場合(たとえば0)、次のように実行できますupdateFragments()

      HomeListFragment fragment = 
          (HomeListFragment) getSupportFragmentManager().findFragmentByTag(
                       "android:switcher:"+R.id.viewpager+":0");
      if(fragment != null)  // could be null if not instantiated yet
      {
         if(fragment.getView() != null) 
         {
            // no need to call if fragment's onDestroyView() 
            //has since been called.
            fragment.updateDisplay(); // do what updates are required
         }
      }

これが有効な方法であるかどうかはわかりませんが、より良いものが提案されるまで行われます。


4
回答と質問を+1するためにログインしました。
SuitUp

5
これは公式のドキュメントにはないので、私は使用しません。今後のリリースでタグが変更された場合はどうなりますか?
Thierry-Dimitri Roy

4
FragmentPagerAdapterは静的メソッドが付属していmakeFragmentNameますが、やや少ないハックアプローチを代わりに使うことができるとタグを生成するために使用される: HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
dgmltn

8
@dgmltn makeFragmentNameprivate staticメソッドであるため、アクセスできません。
StenaviN

1
イエスの甘い母、これはうまくいきます!指を交差させて使用します。
Bogdan Alexandru

35

あなたが私に尋ねた場合、すべての「アクティブな」フラグメントページを追跡する、以下のページの2番目のソリューションの方が優れています:http : //tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html

バークサイドからの答えは私にはあまりにもハッキーです。

すべての「アクティブな」フラグメントページを追跡します。この場合、ViewPagerで使用されるFragmentStatePagerAdapterでフラグメントページを追跡します。

private final SparseArray<Fragment> mPageReferences = new SparseArray<Fragment>();

public Fragment getItem(int index) {
    Fragment myFragment = MyFragment.newInstance();
    mPageReferences.put(index, myFragment);
    return myFragment;
}

「非アクティブ」なフラグメントページへの参照を保持しないようにするには、FragmentStatePagerAdapterのdestroyItem(...)メソッドを実装する必要があります。

public void destroyItem(View container, int position, Object object) {
    super.destroyItem(container, position, object);
    mPageReferences.remove(position);
}

...現在表示されているページにアクセスする必要がある場合は、次を呼び出します。

int index = mViewPager.getCurrentItem();
MyAdapter adapter = ((MyAdapter)mViewPager.getAdapter());
MyFragment fragment = adapter.getFragment(index);

... MyAdapterのgetFragment(int)メソッドは次のようになります。

public MyFragment getFragment(int key) {
    return mPageReferences.get(key);
}


リンクの@フランクの2番目のソリューションは簡単です...!ありがとうございます.. :)
TheFlash

3
マップの代わりにSparseArrayを使用する方が良い
GaRRaPeTa


あなたは、<11をターゲットSparseArrayCompatを使用している場合、それは、SparseArrayを使用していますので、私の答えを更新しました代わりに、理由はバージョン11に更新何クラス
フランク・

2
これはライフサイクルイベントで中断します。stackoverflow.com/a/24662049/236743
Dori

13

上記の@barksideでメソッドをテストした後、アプリケーションで機能させることができませんでした。次に、IOSched2012アプリもを使用していることを思い出しviewpagerました。そこで、私は自分のソリューションを見つけました。これらは格納されないため、フラグメントIDまたはタグは使用しません。viewpagerは、簡単にアクセスできる方法で。

IOSchedアプリのHomeActivity の重要な部分を以下に示します。コメントが重要なので、コメントに特に注意してください。

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);

    // Since the pager fragments don't have known tags or IDs, the only way to persist the
    // reference is to use putFragment/getFragment. Remember, we're not persisting the exact
    // Fragment instance. This mechanism simply gives us a way to persist access to the
    // 'current' fragment instance for the given fragment (which changes across orientation
    // changes).
    //
    // The outcome of all this is that the "Refresh" menu button refreshes the stream across
    // orientation changes.
    if (mSocialStreamFragment != null) {
        getSupportFragmentManager().putFragment(outState, "stream_fragment",
                mSocialStreamFragment);
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
    super.onRestoreInstanceState(savedInstanceState);
    if (mSocialStreamFragment == null) {
        mSocialStreamFragment = (SocialStreamFragment) getSupportFragmentManager()
                .getFragment(savedInstanceState, "stream_fragment");
    }
}

そしてあなたのインスタンスを次FragmentsFragmentPagerAdapterように保存します:

    private class HomePagerAdapter extends FragmentPagerAdapter {
    public HomePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return (mMyScheduleFragment = new MyScheduleFragment());

            case 1:
                return (mExploreFragment = new ExploreFragment());

            case 2:
                return (mSocialStreamFragment = new SocialStreamFragment());
        }
        return null;
    }

また、次のFragmentように通話を保護することを忘れないでください。

    if (mSocialStreamFragment != null) {
        mSocialStreamFragment.refresh();
    }

ライアン、これは、ViewPagerがフラグメントインスタンスを「保持」するために使用する関数をオーバーライドし、アクセス可能なパブリック変数に設定するということですか?(mSocialStreamFragment == null){?
Thomas Clowes 2013年

1
これは良い答えであり、io12アプリのrefの+1ですgetItem()。ライフサイクルの変更により親が再作成された後に呼び出されないため、ライフサイクルの変更全体で機能するかどうかは不明です。stackoverflow.com/a/24662049/236743を参照してください 。例では、これは1つのフラグメントに対して部分的に保護されていますが、他の2つのフラグメントに対しては保護されていません
Dori

2

FragmentPagerAdapter一部のソースコードをコピーして変更し、getTag()メソッドを追加できます

例えば

public abstract class AppFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentPagerAdapter";
private static final boolean DEBUG = false;

private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private Fragment mCurrentPrimaryItem = null;

public AppFragmentPagerAdapter(FragmentManager fm) {
    mFragmentManager = fm;
}


public abstract Fragment getItem(int position);

@Override
public void startUpdate(ViewGroup container) {
}

@Override
public Object instantiateItem(ViewGroup container, int position) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }

    final long itemId = getItemId(position);


    String name = getTag(position);
    Fragment fragment = mFragmentManager.findFragmentByTag(name);
    if (fragment != null) {
        if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
        mCurTransaction.attach(fragment);
    } else {
        fragment = getItem(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);

        mCurTransaction.add(container.getId(), fragment,
                getTag(position));
    }
    if (fragment != mCurrentPrimaryItem) {
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
    }

    return fragment;
}

@Override
public void destroyItem(ViewGroup container, int position, Object object) {
    if (mCurTransaction == null) {
        mCurTransaction = mFragmentManager.beginTransaction();
    }
    if (DEBUG) Log.v(TAG, "Detaching item #" + getItemId(position) + ": f=" + object
            + " v=" + ((Fragment) object).getView());
    mCurTransaction.detach((Fragment) object);
}

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    Fragment fragment = (Fragment) object;
    if (fragment != mCurrentPrimaryItem) {
        if (mCurrentPrimaryItem != null) {
            mCurrentPrimaryItem.setMenuVisibility(false);
            mCurrentPrimaryItem.setUserVisibleHint(false);
        }
        if (fragment != null) {
            fragment.setMenuVisibility(true);
            fragment.setUserVisibleHint(true);
        }
        mCurrentPrimaryItem = fragment;
    }
}

@Override
public void finishUpdate(ViewGroup container) {
    if (mCurTransaction != null) {
        mCurTransaction.commitAllowingStateLoss();
        mCurTransaction = null;
        mFragmentManager.executePendingTransactions();
    }
}

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

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

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


public long getItemId(int position) {
    return position;
}

private static String makeFragmentName(int viewId, long id) {
    return "android:switcher:" + viewId + ":" + id;
}

protected abstract String getTag(int position);
}

次に、それを拡張し、これらの抽象メソッドをオーバーライドします。Androidグループの変更を恐れる必要はありません。

FragmentPageAdapter 将来のソースコード

 class TimeLinePagerAdapter extends AppFragmentPagerAdapter {


    List<Fragment> list = new ArrayList<Fragment>();


    public TimeLinePagerAdapter(FragmentManager fm) {
        super(fm);
        list.add(new FriendsTimeLineFragment());
        list.add(new MentionsTimeLineFragment());
        list.add(new CommentsTimeLineFragment());
    }


    public Fragment getItem(int position) {
        return list.get(position);
    }

    @Override
    protected String getTag(int position) {
        List<String> tagList = new ArrayList<String>();
        tagList.add(FriendsTimeLineFragment.class.getName());
        tagList.add(MentionsTimeLineFragment.class.getName());
        tagList.add(CommentsTimeLineFragment.class.getName());
        return tagList.get(position);
    }


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


}

1

また、問題なく動作します:

ページフラグメントのレイアウトのどこかに:

<FrameLayout android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" android:id="@+id/fragment_reference">
     <View android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone"/>
</FrameLayout>

フラグメントのonCreateView():

...
View root = inflater.inflate(R.layout.fragment_page, container, false);
ViewGroup ref = (ViewGroup)root.findViewById(R.id.fragment_reference);
ref.setTag(this);
ref.getChildAt(0).setTag("fragment:" + pageIndex);
return root;

存在する場合、ViewPagerからFragmentを返すメソッド:

public Fragment getFragment(int pageIndex) {        
        View w = mViewPager.findViewWithTag("fragment:" + pageIndex);
        if (w == null) return null;
        View r = (View) w.getParent();
        return (Fragment) r.getTag();
}

1

または、次のようにsetPrimaryItemメソッドをオーバーライドできますFragmentPagerAdapter

public void setPrimaryItem(ViewGroup container, int position, Object object) { 
    if (mCurrentFragment != object) {
        mCurrentFragment = (Fragment) object; //Keep reference to object
        ((MyInterface)mCurrentFragment).viewDidAppear();//Or call a method on the fragment
    }

    super.setPrimaryItem(container, position, object);
}

public Fragment getCurrentFragment(){
    return mCurrentFragment;
}

0

他の誰かを助けることができる場合に備えて、私のアプローチを示したいと思います。

これは私のポケットベルアダプターです。

 public class CustomPagerAdapter extends FragmentStatePagerAdapter{
    private Fragment[] fragments;

    public CustomPagerAdapter(FragmentManager fm) {
        super(fm);
        fragments = new Fragment[]{
                new FragmentA(),
                new FragmentB()
        };
    }

    @Override
    public Fragment getItem(int arg0) {
        return fragments[arg0];
    }

    @Override
    public int getCount() {
        return fragments.length;
    }

}

私の活動で私は持っています:

public class MainActivity {
        private ViewPager view_pager;
        private CustomPagerAdapter adapter;


        @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        adapter = new CustomPagerAdapter(getSupportFragmentManager());
        view_pager = (ViewPager) findViewById(R.id.pager);
        view_pager.setAdapter(adapter);
        view_pager.setOnPageChangeListener(this);
          ...
         }

}

次に、現在のフラグメントを取得するには、次のようにします。

int index = view_pager.getCurrentItem();
Fragment currentFragment = adapter.getItem(index);

1
これは、アクティビティ/フラグメントライフサイクルメソッドの後で壊れます
Dori

0

タブを追跡する必要がなく、とにかくすべてを更新する必要があるため、これが私の解決策です。

    Bundle b = new Bundle();
    b.putInt(Constants.SharedPreferenceKeys.NUM_QUERY_DAYS,numQueryDays);
    for(android.support.v4.app.Fragment f:getSupportFragmentManager().getFragments()){
        if(f instanceof HomeTermFragment){
            ((HomeTermFragment) f).restartLoader(b);
        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.