Обновить данные в ListFragment как часть ViewPager

142

Я использую ViewPager совместимости с v4 в Android. My FragmentActivity содержит набор данных, которые должны отображаться по-разному на разных страницах моего ViewPager. Пока у меня всего 3 экземпляра одного и того же ListFragment, но в будущем у меня будет 3 экземпляра разных ListFragments. 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;
        }
   ...

Теперь, как вы можете видеть, моей первой попыткой было notifyDataSetChanged для всего FragmentPagerAdapter, и это показало, что иногда данные обновляются, но у других я получил исключение IllegalStateException: не могу выполнить это действие после onSaveInstanceState.

Моя вторая попытка вызвала попытку вызвать функцию обновления в моем ListFragment, но getId в getItem вернул 0. Согласно документам, которые я пробовал

получение ссылки на фрагмент из FragmentManager с помощью findFragmentById () или findFragmentByTag ()

но я не знаю тег или идентификатор моих фрагментов! У меня есть android: id = "@ + id / viewpager" для ViewPager и android: id = "@ android: id / list" для моего ListView в макете ListFragment, но я не думаю, что они полезны.

Итак, как я могу: а) обновить весь 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за фрагмент.
сандалон
1
У меня это работает, я использовал его в нескольких проектах. Вы можете попробовать прочитать исходный код FragmentPagerAdapter и распечатать журналы для отладки.
faylon
Спасибо работает как шарм даже после поворота экранов. Даже я смог изменить содержимое FragmentA с помощью MyActivity из FragmentB, используя это решение.
Мехди Фанай
5
Он работает, FragmentPagerAdaperно не работает FragmentStatePagerAdaper( Fragment.getTag()всегда возвращается null.
Engine Bai,
1
Я попробовал это решение, создал метод обновления данных фрагмента. Но теперь я не могу видеть виды фрагмента. Я вижу, что данные будут фрагментироваться, но представления не видны. Любая помощь?
Арун Бадоле
256

Ответ Barkside работает, FragmentPagerAdapterно не работает FragmentStatePagerAdapter, потому что он не устанавливает теги для фрагментов, которым он передает FragmentManager.

С FragmentStatePagerAdapterкажется , что мы можем получить путем, используя свой instantiateItem(ViewGroup container, int position)вызов. Он возвращает ссылку на фрагмент в позиции position. Если FragmentStatePagerAdapterуже содержит ссылку на рассматриваемый фрагмент, 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 ()
Дандре Эллисон
13
... и на всякий случай, если кому-то интересно, ViewPager отслеживает индекс просматриваемого фрагмента (ViewPager.getCurrentItem ()), который равен 49 в приведенном выше примере ... но я должен сказать, что я УДИВИТЕЛЬНО, что ViewPager API не возвращает ссылку на фактический фрагмент напрямую.
mblackwell8
6
С FragmentStatePagerAdapterпомощью выше кода с a.instantiateItem(viewPager, viewPager.getCurrentItem());(чтобы избавиться от 49), предложенный @ mblackwell8 работает отлично.
Cedric Simon
1
Подождите, поэтому мне нужно передать ViewPager каждому содержащемуся в нем фрагменту, чтобы иметь возможность делать что-то с представлениями во фрагменте? В настоящее время все, что я делаю в onCreate на текущей странице, происходит на следующей странице. Это звучит очень сомнительно с точки зрения утечки.
G_V
важно отметить, что любые вызовы instantiateItemдолжны быть окружены startUpdate/ finishUpdatecalls. подробности в моем ответе на аналогичный вопрос: stackoverflow.com/questions/14035090/…
morgwai
154

Хорошо, я думаю, что нашел способ выполнить запрос b) в моем собственном вопросе, поэтому я поделюсь им для пользы других. Тег фрагментов внутри ViewPager находится в форме "android:switcher:VIEWPAGER_ID:INDEX", где VIEWPAGER_ID- это R.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
так как этого нет в официальной документации, я бы не стал его использовать. Что, если они изменят тег в будущем выпуске?
Тьерри-Димитри Рой
4
FragmentPagerAdapter поставляется со статическим методом, makeFragmentNameиспользуемым для генерации тега, который вы могли бы использовать вместо этого для менее хакерского подхода: HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0));
dgmltn
8
@dgmltn makeFragmentName- это private staticметод, поэтому вы не можете получить к нему доступ.
StenaviN 05
1
Милая мать Иисуса, это работает! Я просто скрещу пальцы и воспользуюсь им.
Богдан Александру
35

Если вы спросите меня, второе решение на следующей странице, отслеживающее все «активные» страницы фрагментов, лучше: http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part -ii.html

Ответ от barkside для меня слишком хакерский.

вы отслеживаете все «активные» страницы-фрагменты. В этом случае вы отслеживаете страницы фрагментов в FragmentStatePagerAdapter, который используется ViewPager.

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

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

Чтобы избежать сохранения ссылки на «неактивные» страницы-фрагменты, нам необходимо реализовать метод destroyItem (...) FragmentStatePagerAdapter:

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

"

Фрэнк
источник
@Frank второе решение по ссылке простое ...! Спасибо .. :)
TheFlash
3
Лучше использовать SparseArray вместо карты
GaRRaPeTa 06
или SparseArrayCompat
Дори
обновил мой ответ, поэтому он использует SparseArray, если вы нацеливаетесь на <11, используйте вместо этого SparseArrayCompat, потому что класс, который обновлен в версии 11.
Фрэнк
2
Это нарушит события жизненного цикла. См. Stackoverflow.com/a/24662049/236743
Дори
13

Хорошо, после тестирования метода @barkside выше я не смог заставить его работать с моим приложением. Затем я вспомнил, что приложение IOSched2012 также использует a viewpager, и именно здесь я нашел свое решение. Он не использует идентификаторы фрагментов или теги, поскольку они не хранятся viewpagerв легкодоступном виде.

Вот важные части из приложений HomeActivity для IOSched. Обратите особое внимание на комментарий , в нем и заключается ключ:

@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");
    }
}

И хранить экземпляры вас Fragmentsв FragmentPagerAdapterкак так:

    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 использует для «сохранения» экземпляров фрагментов, и устанавливаете для них общедоступную переменную, которая доступна? Почему при восстановлении у вас есть if (mSocialStreamFragment == null) {?
Thomas Clowes
1
это хороший ответ и +1 для ссылки приложения io12 - не уверен, будет ли это работать при изменении жизненного цикла, хотя из-за того, что он getItem()не вызывается после воссоздания родителя из-за изменения жизненного цикла. См. Stackoverflow.com/a/24662049/236743 . В этом примере это частично защищено для одного фрагмента, но не для двух других
Дори
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, если он существует:

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();
}
Hox
источник
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);
Kiduxa
источник
1
это сломается после методов жизненного цикла активности / фрагмента
Дори
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);
        }
    }
abhi08638
источник