Оновіть дані у 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.