У меня возникли некоторые проблемы при работе с фрагментом с помощью ViewPager.
Что у меня есть: MainActivity
(скажем, MainActivity
), которое содержит ViewPager
для отображения некоторых фрагментов. Некоторые из них содержат интерфейс обратного вызова, который будет вызываться для выполнения MainActivity
либо MainActivity
в MainActivity
. MainActivity
имеет класс FragmentPagerAdapter
, который используется в качестве адаптера ViewPager
. И List<Fragment>
в FragmentPagerAdapter
для хранения некоторого фрагмента, который будет отображаться в ViewPager
.
Что я ожидаю:
При первом запуске Fragment вызывал методы интерфейса обратного вызова, когда я нажимал на кнопку, и MainActivity делала что-то внутри этого. Это работало отлично.
После поворота экрана я ожидал, что он будет работать так же, как при первом запуске, НО
NullPointerException: попытка вызвать метод для нулевого ссылочного объекта (в частности, интерфейса Fragment) ударила меня по лицу.
Что я знаю:
- getItem(int position)
больше не будет вызываться после создания фрагмента. Вместо этого будет вызван instantiateItem(ViewGroup container, int position)
.
- FragmentManager
сохранит некоторый фрагмент в mActive.mValues
ViewPager и фрагменты - как правильно сохранить состояние фрагмента? (Я ссылался на эту и некоторые другие те же темы в StackOverflow.)
Что я попробовал и увидел:
- Переопределить instantiateItem(ViewGroup container, int position)
- отлажено за 1 день. Я видел, что когда я getSupportFragmentManager()
в методе MainActivity onCreate()
суперструктору FragmentPagerAdapter
, при первом запуске он имеет "адрес в памяти", предположим, что это "1a1a1a1". mActive.mValues
FragmentManager
сохранил некоторый "адрес в памяти" фрагмента, который идентичен содержащему их List<Fragment>
(предположим, что это был "qwertyu"). Что означало, что это было правильно.
Но когда я повернул экран, снова передав getSupportFragmentManager()
, "адрес в памяти" был совершенно другим, предположим, что "9f9f9f9". А FragmentManager
mActive.mValues
содержал другой набор фрагментов "адрес в памяти" (предположим, что "abcdeff"), хотя количество фрагментов в нем было равно числу фрагментов, которые были сохранены при первом запуске (до вращения),
Я добавил фрагмент в List<Fragment>
с новым "адресом в памяти" (предположим, "abababa"), имеет интерфейс обратного вызова. Но когда я нажал на кнопку в нем, это был фрагмент, который был в FragmentManager
mActive.mValues
после поворота (с "адресом в памяти" является "abcdeff", как я предполагал выше), и тот не имел обратного вызова интерфейс (из-за того, что не был установлен в MainActivity
первую очередь). И вызвал исключение NullPointerException, как упомянуто выше.
Мои вопросы сейчас:
- Прежде всего, как избавиться от этой проблемы !? Было бы лучше продолжать использовать FragmentPagerAdapter
вместо другого класса. Но я рассмотрю возможность использования другого класса.
- Во-вторых, вы можете объяснить, почему FragmentManager
сохранил экземпляр Fragment до ротации. Но после поворота он создает совершенно другой экземпляр Fragment, но все равно использует его вместо фрагмента, который был сохранен в List<Fragment>
?
Вот код (я думаю, что я не использовал метод instantiateItem(ViewGroup container, int position)
правильно, поэтому он все еще вызывал проблему).
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Attach the SectionsPagerAdapter to the ViewPager
SectionsPagerAdapter pagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
ViewPager viewPager = findViewById(R.id.viewPager);
viewPager.setAdapter(pagerAdapter);
}
//
//
//Adapter class
private class SectionsPagerAdapter extends FragmentPagerAdapter {
private static final int PAGE_HOME = 0;
private int tabCount = 1;
private List<Fragment> fragmentList = new ArrayList<>();
private List<String> fragmentTitleList = new ArrayList<>();
//private FragmentManager fragmentManager;
SectionsPagerAdapter(FragmentManager fm) {
super(fm);
//fragmentManager = fm;
//Default HomeFragment
HomeFragment homeFragment = new HomeFragment();
//Callback interface
homeFragment.setOnCategoryFragmentChangedListener(new HomeFragment.OnCategoryFragmentChangedListener() {
//This method will be called when a button in HomeFragment is clicked
@Override
public void onAddNewCategory(String categoryName) {
addNewCategory(categoryName);
}
});
fragmentList.add(homeFragment);
fragmentTitleList.add("Home");
}
@Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
@Override
public int getCount() {
return tabCount;
}
@Override
public CharSequence getPageTitle(int position) {
return fragmentTitleList.get(position);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
fragmentList.set(position, (Fragment) super.instantiateItem(container, position));
return fragmentList.get(position);
}
private void addNewCategory(String categoryName) {
CategoryFragment fragment = new CategoryFragment();
tabCount += 1;
fragmentList.add(fragment);
fragmentTitleList.add(categoryName);
notifyDataSetChanged();
}
}
}
Пожалуйста помоги. Я схожу с ума уже 2 дня...!
Ну что ж, сразу после того, как я заснул, я нашел решение. Это правда, что я не использовал метод instantiateItem()
правильно. После повторной отладки я обнаружил, что метод instantiateItem()
получает вызов всякий раз, когда я смахиваю (или выбираю, если использую также TabLayout) на другой фрагмент, и даже получаю вызов до getItem(int pos)
, независимо от того, что это при первом запуске или после вращение. Вот почему я думаю, что мы должны настроить Fragment в методе instantiateItem()
.
Итак, вот как я сейчас использую метод instantiateItem()
:
@Override
public Object instantiateItem(ViewGroup container, int position) {
fragmentList.set(position, (Fragment) super.instantiateItem(container, position));
Fragment fragment = fragmentList.get(position);
if (position == PAGE_HOME) {
((HomeFragment) fragment).setOnCategoryFragmentChangedListener(new HomeFragment.OnCategoryFragmentChangedListener() {
@Override
public void onAddNewCategory(String categoryName) {
addNewCategory(categoryName);
}
});
}
return fragment;
}
Если у кого-то есть лучшее решение, пожалуйста, просто скажите мне, если вы не возражаете. Я подумаю об этом.
Я считаю, что Android перезапускает действие во время изменения ориентации, создавая несколько экземпляров FragmentPagerAdapter и несколько экземпляров List.
Я не совсем понимаю ваш вопрос, но подозреваю, что instantiateItem ничего не делает в любом случае. Разве Fragment getItem(int pos)
работает без переопределения instantiateItem()?
getItem(int pos)
работал без переопределенияinstantiateItem()
. Но, как я уже сказал, после поворота экранаgetItem(int pos)
больше не будет вызываться для фрагментов, которые уже были созданы при первом запуске. В коде будет создан новый экземпляр HomeFragment, который будет добавлен вList<Fragment>
; но ViewPager не отображал этот экземпляр (который был добавлен вList
), вместо этого он отображал экземпляр вmActive.mValues
FragmentManager
, что вызвало NPE.mActive.mValues
FragmentManager (но в новом списке <Fragment>) после поворота экрана? (Дополнительный вопрос: и я должен сделать это, воссоздавList<>
затем использовать его? Есть ли лучший способ, как переработка или повторное использование уже созданного фрагмента? ")