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

Я использую 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 вручную.

Любая помощь будет принята с благодарностью!




Ответы (10)


Старайтесь записывать тег каждый раз, когда создается экземпляр 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);
    }
}
person faylon    schedule 24.08.2012
comment
Выглядит нормально, но в моем классе не работает. У вас это работает? Я все еще получаю null за фрагмент. - person sandalone; 15.12.2012
comment
У меня это работает, я использовал его в нескольких проектах. Вы можете попробовать прочитать исходный код FragmentPagerAdapter и распечатать журналы для отладки. - person faylon; 17.12.2012
comment
Спасибо работает как шарм даже после поворота экранов. Даже я смог изменить содержимое FragmentA с помощью MyActivity из FragmentB, используя это решение. - person Mehdi Fanai; 03.04.2013
comment
Я думаю, что это один из лучших способов сделать это. Элегантный и простой, он работает как для FragmentPagerAdapter, так и (я думаю) FragmentStatePagerAdapter. Кроме того, он не содержит ссылок на фрагменты, поэтому позволяет фреймворку управлять сборщиком мусора и т. Д. - person pjco; 24.05.2013
comment
У меня отлично работает, и я озадачен, почему гораздо меньше людей приняли это решение по сравнению с тем, которое основано на внутренней реализации makeFragmentName(). - person Trevor; 23.10.2013
comment
Он работает для FragmentPagerAdaper, но не работает для FragmentStatePagerAdaper (Fragment.getTag() всегда возвращает null. - person Engine Bai; 22.02.2015
comment
Вы можете прокомментировать getItem? Я думал, getItem был вызван instantiateItem. В этом случае как используется position? - person Antonio Sesto; 20.03.2015
comment
Я попробовал это решение, создал метод обновления данных фрагмента. Но теперь я не могу видеть виды фрагмента. Я вижу, что данные будут фрагментироваться, но представления не видны. Любая помощь? - person Arun Badole; 18.05.2015

Ответ Барксайда работает с 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)
person Pēteris Caune    schedule 16.01.2012
comment
Я бы упростил это, просто создав PagerAdapter (PagerAdapter a = pager.getAdapter ();). Или даже MyFragment f49 = (MyFragment) pager.getAdapter (). InstantiateItem (pager, 49); instantiateItem () взят из PagerAdapter, поэтому вам не нужно приводить его тип для вызова instantiateItem () - person Dandre Allison; 21.08.2012
comment
... и на всякий случай, если кому-то интересно, ViewPager отслеживает индекс просматриваемого фрагмента (ViewPager.getCurrentItem ()), который равен 49 в приведенном выше примере ... но я должен сказать, что я Я УДИВИТЕЛЬНО, что ViewPager API не возвращает ссылку на фактический фрагмент напрямую. - person mblackwell8; 20.03.2013
comment
С FragmentStatePagerAdapter, использование приведенного выше кода с a.instantiateItem(viewPager, viewPager.getCurrentItem()); (чтобы избавиться от 49), предложенное @ mblackwell8, отлично работает. - person Cedric Simon; 17.03.2014
comment
Подождите, поэтому мне нужно передать ViewPager каждому фрагменту, который он содержит, просто чтобы иметь возможность делать что-то с представлениями во фрагменте? В настоящее время все, что я делаю в onCreate на текущей странице, происходит на следующей странице. Это звучит очень сомнительно с точки зрения утечки. - person G_V; 17.12.2014
comment
важно отметить, что любые вызовы instantiateItem должны быть окружены вызовами _2 _ / _ 3_. подробности в моем ответе на аналогичный вопрос: stackoverflow.com/questions/14035090/ - person morgwai; 28.12.2016

Хорошо, я думаю, что нашел способ выполнить запрос 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
         }
      }

Понятия не имею, можно ли это сделать, но так будет до тех пор, пока не будет предложено что-нибудь получше.

person barkside    schedule 12.09.2011
comment
Я вошел в систему только для того, чтобы +1 к вашему ответу и вопросу. - person SuitUp; 21.02.2012
comment
поскольку этого нет в официальной документации, я бы не стал его использовать. Что, если они изменят тег в будущем выпуске? - person Thierry-Dimitri Roy; 17.04.2012
comment
FragmentPagerAdapter поставляется со статическим методом makeFragmentName, используемым для генерации тега, который вы могли бы использовать вместо этого для менее хакерского подхода: HomeListFragment fragment = (HomeListFragment) getSupportFragmentManager().findFragmentByTag(FragmentPagerAdapter.makeFragmentName(R.id.viewpager, 0)); - person dgmltn; 01.05.2012
comment
почему бы тебе не добавить это в качестве ответа? - person Ovidiu Latcu; 14.05.2012
comment
@dgmltn, makeFragmentName - это private static метод, поэтому вы не можете получить к нему доступ. - person StenaviN; 05.06.2012
comment
Вы нашли для этого лучшее решение? - person VansFannel; 21.06.2012
comment
См. Мой ответ ниже, чтобы узнать о неугомонном способе решения этой проблемы, основанном на исходном коде IOSched2012. - person Ryan R; 15.01.2013
comment
@barkside: Ты сделал мой день :) - person hrules6872; 15.05.2013
comment
Милая мать Иисуса, это работает! Я просто скрещу пальцы и воспользуюсь этим. - person Bogdan Alexandru; 28.03.2014
comment
Это абсолютно ужасно и взломано. Вы не можете полагаться на это, поскольку структура Android может измениться. Вместо этого управляйте фрагментами самостоятельно, примерно так: gist.github.com/nesquena/c715c9b22fb873b1d259 Тем не менее, ужасно, что платформа Android не предоставляет лучшего API. @AlexLockwood, ты серьезно не думаешь, что это нормально, что люди используют вышеуказанное решение, не так ли? - person Ashok SoThree; 04.06.2014
comment
Это лучшее и лучшее решение для обновления данных или для другого варианта. Спаси день - person Anand Savjani; 14.04.2019

Если вы спросите меня, лучше второе решение на следующей странице, отслеживающее все «активные» страницы фрагментов: 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);
}

"

person Frank    schedule 11.08.2012
comment
@Frank второе решение по ссылке простое ...! Спасибо .. :) - person TheFlash; 15.11.2013
comment
Лучше использовать SparseArray вместо карты - person GaRRaPeTa; 06.03.2014
comment
или SparseArrayCompat - person Dori; 01.07.2014
comment
обновил мой ответ, поэтому он использует SparseArray, если вы нацеливаетесь на ‹11, используйте вместо этого SparseArrayCompat, потому что класс, обновленный в версии 11. - person Frank; 01.07.2014
comment
Это нарушит события жизненного цикла. См. stackoverflow.com/a/24662049/236743 - person Dori; 09.07.2014

Хорошо, после тестирования метода @barkside выше я не смог заставить его работать с моим приложением. Затем я вспомнил, что приложение IOSched2012 также использует 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();
    }
person Ryan R    schedule 14.01.2013
comment
Райан, это означает, что вы игнорируете функции, которые ViewPager использует для «сохранения» ваших экземпляров фрагментов, и устанавливаете их в общедоступную переменную, которая доступна? Почему при восстановлении у вас есть if (mSocialStreamFragment == null) {? - person Thomas Clowes; 01.05.2013
comment
это хороший ответ и +1 для io12 app ref - не уверен, будет ли это работать при изменении жизненного цикла, хотя из-за того, что getItem() не вызывается после воссоздания родителя из-за изменения жизненного цикла. См. stackoverflow.com/a/24662049/236743. В примере это частично защищено для одного фрагмента, но не для двух других. - person Dori; 11.07.2014

Вы можете скопировать 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();
    }


}
person Jiang Qi    schedule 11.09.2012

Также без проблем работает:

где-то в макете фрагмента страницы:

<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();
}
person Hox    schedule 17.07.2015

В качестве альтернативы вы можете переопределить метод 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;
}
person Vlad Spreys    schedule 19.10.2014

Я хочу поделиться своим подходом на случай, если он может помочь кому-то еще:

Это мой адаптер для пейджера:

 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);
person kiduxa    schedule 28.10.2013
comment
это сломается после методов жизненного цикла активности / фрагмента - person Dori; 09.07.2014

Это мое решение, поскольку мне не нужно отслеживать свои вкладки, и мне все равно нужно обновлять их все.

    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);
        }
    }
person abhi08638    schedule 25.02.2018