Оновіть дані у ListFragment як частина ViewPager


142

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

Тепер, як ви бачите, моєю першою спробою було сповіститиDataSetChanged на весь FragmentPagerAdapter, і це показало, що іноді оновлюють дані, але інші я отримав IllegalStateException: Не можу виконати цю дію після onSaveInstanceState.

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

отримання посилання на фрагмент від FragmentManager, використовуючи findFragmentById () або findFragmentByTag ()

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

Отже, як я можу: a) безпечно оновити весь ViewPager за один раз (в ідеалі повернути користувача на сторінку, на якій він був раніше) - це нормально, що користувач бачить зміни подання. Або бажано, б) викликайте функцію у кожному з впливаючих ListFragment для оновлення ListView вручну.

Будь-яка допомога буде вдячно прийнята!

Відповіді:


58

Спробуйте записати тег щоразу, коли фрагмент буде створений.

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 і надрукувати деякі журнали для налагодження.
Файлон

Завдяки цьому працює як шарм навіть після обертових екранів. Навіть мені вдалося змінити вміст FragmentA за допомогою MyActivity від FragmentB, використовуючи це рішення.
Мехді Фанай

5
Він працює, FragmentPagerAdaperале не вдається FragmentStatePagerAdaper( Fragment.getTag()завжди повертайтеся null.
Двигун Бай,

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 (пейджер, 49); instantiateItem () походить від PagerAdapter, тому вам не потрібно вводити його, щоб викликати instantiateItem ()
Дандре Еллісон

12
... і на всякий випадок, коли хтось цікавиться, ViewPager відстежує індекс переглянутого фрагмента (ViewPager.getCurrentItem ()), який є 49 у прикладі вище ... але я повинен сказати, що я Я здивований, що API ViewPager не поверне посилання на фактичний фрагмент безпосередньо.
mblackwell8

6
З FragmentStatePagerAdapter, використання вищевказаного коду з a.instantiateItem(viewPager, viewPager.getCurrentItem());(щоб позбутися 49), запропонованого @ mblackwell8, прекрасно працює.
Седрик Саймон

1
Зачекайте, тож я повинен передати ViewPager кожному фрагменту, який він містить, просто щоб мати можливість робити погляди на фрагменті? В даний час все, що я роблю в onCreate на поточній сторінці, відбувається на наступній сторінці. Це звучить надзвичайно тіньово.
G_V

важливо зазначити, що будь-які дзвінки instantiateItemповинні бути оточені startUpdate/ finishUpdateдзвінками. Подробиці в моїй обороні на подібне питання: 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

1
Мила мати Ісуса, це працює! Я просто буду тримати палець схрещеним і використовувати його.
Богдан Олександру

35

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

Відповідь від норди для мене занадто хитра.

ви відстежуєте всі "активні" фрагменти сторінок. У цьому випадку ви відстежуєте сторінки фрагментів у 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;
}

Щоб уникнути посилання на "неактивні" сторінки з фрагментами, нам потрібно застосувати метод killItem (...) 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);

... де метод getFragment (int) MyAdapter виглядає так:

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

"


@Frank друге рішення у посиланні просте ...! Спасибі .. :)
TheFlash

3
Краще, якщо ви використовуєте SparseArray замість карти
GaRRaPeTa


оновив мою відповідь, щоб вона використовувала SparseArray, якщо ви націлюєте <11, використовуйте SparseArrayCompat замість того, що клас, який оновлено у версії 11.
Френк

2
Це порушить події життєвого циклу. Дивіться stackoverflow.com/a/24662049/236743
Дорі

13

Добре, протестувавши метод @barkside вище, я не зміг його працювати з моєю програмою. Тоді я згадав, що додаток IOSched2012 також використовує а viewpagerтакож саме там я знайшов своє рішення. Він не використовує ідентифікаторів фрагмента чи тегів, оскільки вони не зберігаються 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");
    }
}

І зберігати екземпляри вас 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 використовує для "збереження" екземплярів вашого фрагмента та встановлення публічної змінної, яка доступна? Чому у екземплярі відновлення у вас є, якщо (mSocialStreamFragment == null) {?
Томас Клоуз

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

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
це порушиться після активних / фрагментальних методів життєвого циклу
Дорі

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);
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.