Фрагменты, кажется, очень хороши для разделения логики пользовательского интерфейса на некоторые модули. Но вместе с тем ViewPager
его жизненный цикл все еще остается для меня туманным. Поэтому мысли Гуру крайне необходимы!
редактировать
Смотрите тупое решение ниже ;-)
Объем
Основная деятельность имеет ViewPager
фрагменты. Эти фрагменты могут реализовывать несколько иную логику для других (подосновных) действий, поэтому данные фрагментов заполняются через интерфейс обратного вызова внутри действия. И все отлично работает при первом запуске, но! ...
проблема
Когда действие воссоздается (например, при изменении ориентации), делайте ViewPager
фрагменты. Код (вы найдете ниже) говорит, что каждый раз, когда создается действие, я пытаюсь создать новый ViewPager
адаптер фрагментов так же, как фрагменты (возможно, это проблема), но FragmentManager уже хранит все эти фрагменты где-то (где?) И запускает механизм отдыха для тех. Таким образом, механизм восстановления вызывает «старый» фрагмент onAttach, onCreateView и т. Д. С моим вызовом интерфейса обратного вызова для инициирования данных через реализованный метод Activity. Но этот метод указывает на вновь созданный фрагмент, который создается с помощью метода onCreate в Activity.
вопрос
Может быть, я использую неправильные шаблоны, но даже книга Android 3 Pro не так много об этом. Поэтому, пожалуйста , дайте мне один-два удара и укажите, как это сделать правильно. Большое спасибо!
Код
Основная деятельность
public class DashboardActivity extends BasePagerActivity implements OnMessageListActionListener {
private MessagesFragment mMessagesFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
Logger.d("Dash onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.viewpager_container);
new DefaultToolbar(this);
// create fragments to use
mMessagesFragment = new MessagesFragment();
mStreamsFragment = new StreamsFragment();
// set titles and fragments for view pager
Map<String, Fragment> screens = new LinkedHashMap<String, Fragment>();
screens.put(getApplicationContext().getString(R.string.dashboard_title_dumb), new DumbFragment());
screens.put(getApplicationContext().getString(R.string.dashboard_title_messages), mMessagesFragment);
// instantiate view pager via adapter
mPager = (ViewPager) findViewById(R.id.viewpager_pager);
mPagerAdapter = new BasePagerAdapter(screens, getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
// set title indicator
TitlePageIndicator indicator = (TitlePageIndicator) findViewById(R.id.viewpager_titles);
indicator.setViewPager(mPager, 1);
}
/* set of fragments callback interface implementations */
@Override
public void onMessageInitialisation() {
Logger.d("Dash onMessageInitialisation");
if (mMessagesFragment != null)
mMessagesFragment.loadLastMessages();
}
@Override
public void onMessageSelected(Message selectedMessage) {
Intent intent = new Intent(this, StreamActivity.class);
intent.putExtra(Message.class.getName(), selectedMessage);
startActivity(intent);
}
BasePagerActivity ака помощник
public class BasePagerActivity extends FragmentActivity {
BasePagerAdapter mPagerAdapter;
ViewPager mPager;
}
адаптер
public class BasePagerAdapter extends FragmentPagerAdapter implements TitleProvider {
private Map<String, Fragment> mScreens;
public BasePagerAdapter(Map<String, Fragment> screenMap, FragmentManager fm) {
super(fm);
this.mScreens = screenMap;
}
@Override
public Fragment getItem(int position) {
return mScreens.values().toArray(new Fragment[mScreens.size()])[position];
}
@Override
public int getCount() {
return mScreens.size();
}
@Override
public String getTitle(int position) {
return mScreens.keySet().toArray(new String[mScreens.size()])[position];
}
// hack. we don't want to destroy our fragments and re-initiate them after
@Override
public void destroyItem(View container, int position, Object object) {
// TODO Auto-generated method stub
}
}
Фрагмент
public class MessagesFragment extends ListFragment {
private boolean mIsLastMessages;
private List<Message> mMessagesList;
private MessageArrayAdapter mAdapter;
private LoadMessagesTask mLoadMessagesTask;
private OnMessageListActionListener mListener;
// define callback interface
public interface OnMessageListActionListener {
public void onMessageInitialisation();
public void onMessageSelected(Message selectedMessage);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// setting callback
mListener = (OnMessageListActionListener) activity;
mIsLastMessages = activity instanceof DashboardActivity;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
inflater.inflate(R.layout.fragment_listview, container);
mProgressView = inflater.inflate(R.layout.listrow_progress, null);
mEmptyView = inflater.inflate(R.layout.fragment_nodata, null);
return super.onCreateView(inflater, container, savedInstanceState);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// instantiate loading task
mLoadMessagesTask = new LoadMessagesTask();
// instantiate list of messages
mMessagesList = new ArrayList<Message>();
mAdapter = new MessageArrayAdapter(getActivity(), mMessagesList);
setListAdapter(mAdapter);
}
@Override
public void onResume() {
mListener.onMessageInitialisation();
super.onResume();
}
public void onListItemClick(ListView l, View v, int position, long id) {
Message selectedMessage = (Message) getListAdapter().getItem(position);
mListener.onMessageSelected(selectedMessage);
super.onListItemClick(l, v, position, id);
}
/* public methods to load messages from host acitivity, etc... */
}
Решение
Тупое решение состоит в том, чтобы сохранить фрагменты внутри onSaveInstanceState (хоста Activity) с помощью putFragment и получить их внутри onCreate через getFragment. Но у меня все еще есть странное чувство, что вещи не должны работать так ... Смотрите код ниже:
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getSupportFragmentManager()
.putFragment(outState, MessagesFragment.class.getName(), mMessagesFragment);
}
protected void onCreate(Bundle savedInstanceState) {
Logger.d("Dash onCreate");
super.onCreate(savedInstanceState);
...
// create fragments to use
if (savedInstanceState != null) {
mMessagesFragment = (MessagesFragment) getSupportFragmentManager().getFragment(
savedInstanceState, MessagesFragment.class.getName());
StreamsFragment.class.getName());
}
if (mMessagesFragment == null)
mMessagesFragment = new MessagesFragment();
...
}
источник
Ответы:
Когда
FragmentPagerAdapter
объект добавляет фрагмент в FragmentManager, он использует специальный тег, основанный на конкретной позиции, в которую будет помещен фрагмент.FragmentPagerAdapter.getItem(int position)
вызывается только тогда, когда фрагмент для этой позиции не существует. После поворота Android заметит, что он уже создал / сохранил фрагмент для этой конкретной позиции, и поэтому он просто пытается восстановить соединение с нимFragmentManager.findFragmentByTag()
, а не создавать новый. Все это бесплатно при использованииFragmentPagerAdapter
и поэтому обычно в вашемgetItem(int)
методе есть код инициализации вашего фрагмента .Даже если мы не использовали a
FragmentPagerAdapter
, не рекомендуется создавать новый фрагмент каждый раз вActivity.onCreate(Bundle)
. Как вы заметили, когда фрагмент добавляется в FragmentManager, он будет воссоздан для вас после поворота, и нет необходимости добавлять его снова. Это является распространенной причиной ошибок при работе с фрагментами.Обычный подход при работе с фрагментами такой:
При использовании
FragmentPagerAdapter
мы отказываемся от управления фрагментами для адаптера, и нам не нужно выполнять вышеуказанные шаги. По умолчанию, он будет предварительно загружать только один фрагмент впереди и позади текущей позиции (хотя он не уничтожит их, если вы не используетеFragmentStatePagerAdapter
). Это контролируется ViewPager.setOffscreenPageLimit (int) . Из-за этого прямой вызов методов для фрагментов вне адаптера не гарантируется как допустимый, поскольку они могут даже не существовать.Короче говоря, ваше решение использовать
putFragment
его для получения ссылки впоследствии не так уж и безумно, и не так уж отличается от обычного способа использовать фрагменты в любом случае (см. Выше). В противном случае получить ссылку сложно, поскольку этот фрагмент добавляется адаптером, а не вами лично. Просто убедитесь, чтоoffscreenPageLimit
он достаточно высок, чтобы всегда загружать нужные фрагменты, поскольку вы полагаетесь на его наличие. Это обходит возможности отложенной загрузки ViewPager, но, похоже, это то, что вы хотите для своего приложения.Другой подход заключается в переопределении
FragmentPageAdapter.instantiateItem(View, int)
и сохранении ссылки на фрагмент, возвращенный из супер-вызова, перед его возвратом (он имеет логику для поиска фрагмента, если он уже существует).Для более полной картины взгляните на некоторые из источников FragmentPagerAdapter (короткий) и ViewPager (длинный).
источник
FragmentPageAdapter.instantiateItem(View, int)
. Наконец исправлена длительная ошибка, которая появляется только при смене ротации / конфигурации и сводит меня с ума ...FragmentPageAdapter.instantiateItem(ViewGroup, int)
скорее, чемFragmentPageAdapter.instantiateItem(View, int)
.onDetach()
или что-то еще?Я хочу предложить решение, которое расширяет
antonyt
«S замечательный ответ и упоминание переопределения ,FragmentPageAdapter.instantiateItem(View, int)
чтобы сохранить ссылки на созданныйFragments
таким образом Вы можете сделать работу на них позже. Это также должно работать сFragmentStatePagerAdapter
; см примечания для деталей.Вот простой пример того, как получить ссылку на
Fragments
возвращаемое значение,FragmentPagerAdapter
которое не зависит от внутреннегоtags
набора вFragments
. Ключ переопределитьinstantiateItem()
и сохранить ссылки там, а не вgetItem()
.или если вы предпочитаете работать с
tags
переменными / ссылками на члены классаFragments
вы также можете захватитьtags
наборFragmentPagerAdapter
таким же образом: ПРИМЕЧАНИЕ: это не относится к,FragmentStatePagerAdapter
так как оно не устанавливаетсяtags
при создании егоFragments
.Обратите внимание, что этот метод НЕ полагается на имитацию внутреннего
tag
набора с помощьюFragmentPagerAdapter
и вместо этого использует надлежащие API для их получения. Таким образом, даже еслиtag
изменения в будущих версияхSupportLibrary
вы все равно будете в безопасности.Не забывай что в зависимости от вашего дизайна
Activity
, над которымFragments
вы пытаетесь работать, он может существовать, а может и не существовать, поэтому вы должны учитывать это, выполняяnull
проверки перед использованием ваших ссылок.Кроме того, если вместо этого вы работаете
FragmentStatePagerAdapter
, то вы не хотите хранить жесткие ссылки на свои,Fragments
потому что у вас их может быть много, а жесткие ссылки будут излишне хранить их в памяти. Вместо этого сохранитеFragment
ссылки вWeakReference
переменных вместо стандартных. Нравится:источник
FragmetPagerAdapter
в onCreate of Activity при каждом повороте экрана. Это неправильно, потому что это может обойти повторное использование уже добавленных фрагментовFragmentPagerAdapter
instantiateItem()
- это путь; это помогло мне управлять поворотами экрана и извлекать мои существующие экземпляры Fragment после возобновления Activity и Adapter; Я оставил себе комментарии в коде как напоминание: после поворотаgetItem()
НЕ вызывается; только этот методinstantiateItem()
вызывается. Супер реализация дляinstantiateItem()
повторного присоединения фрагментов после ротации (при необходимости), вместо создания новых экземпляров!Fragment createdFragment = (Fragment) super.instantiateItem..
в первом решении.Я нашел другое относительно простое решение для вашего вопроса.
Как вы можете видеть из исходного кода FragmentPagerAdapter , фрагменты, управляемые
FragmentPagerAdapter
хранилищем, находятсяFragmentManager
под тегом, созданным с использованием:viewId
Этоcontainer.getId()
, тоcontainer
вашViewPager
экземпляр.index
Позиция фрагмента. Следовательно, вы можете сохранить идентификатор объекта вoutState
:Если вы хотите связаться с этим фрагментом, вы можете получить, если
FragmentManager
, например:источник
tag
, попробуйте мой ответ .Я хочу предложить альтернативное решение для, возможно, немного другого случая, так как многие из моих поисков ответов продолжали приводить меня к этой теме.
Мой случай - я создаю / добавляю страницы динамически и перемещаю их в ViewPager, но при повороте (onConfigurationChange) я получаю новую страницу, потому что, конечно, OnCreate вызывается снова. Но я хочу сохранить ссылку на все страницы, которые были созданы до ротации.
Проблема - у меня нет уникальных идентификаторов для каждого фрагмента, который я создаю, поэтому единственный способ ссылки был каким-то образом хранить ссылки в массиве, который будет восстановлен после изменения ротации / конфигурации.
Обходной путь - Ключевая концепция была иметь активность (которая отображает фрагменты) также управлять массивом ссылок на существующие фрагменты, так как эта деятельность может использовать Связки в onSaveInstanceState
Итак, в рамках этого занятия я объявляю частного участника для отслеживания открытых страниц.
Это обновляется каждый раз, когда onSaveInstanceState вызывается и восстанавливается в onCreate.
... так что, как только он будет сохранен, его можно получить ...
Это были необходимые изменения в основной деятельности, и поэтому мне нужны были члены и методы в моем FragmentPagerAdapter, чтобы это работало, поэтому в
идентичная конструкция (как показано выше в MainActivity)
и эта синхронизация (как используется выше в onSaveInstanceState) поддерживается специально методами
И, наконец, в классе фрагмента
для того, чтобы все это заработало, было сделано два изменения:
а затем добавить это в onCreate, чтобы фрагменты не были уничтожены
Я все еще нахожусь в процессе обдумывания фрагментов и жизненного цикла Android, поэтому предостережение здесь может заключаться в избыточности / неэффективности этого метода. Но это работает для меня, и я надеюсь, что может быть полезным для других в случаях, подобных моему.
источник
Мое решение очень грубое, но работает: так как мои фрагменты динамически создаются из сохраненных данных, я просто удаляю все фрагменты из
PageAdapter
перед вызовомsuper.onSaveInstanceState()
и затем воссоздаю их при создании активности:Вы не можете удалить их
onDestroy()
, иначе вы получите это исключение:java.lang.IllegalStateException:
Не могу выполнить это действие послеonSaveInstanceState
Вот код на странице адаптера:
Я сохраняю только текущую страницу и восстанавливаю ее
onCreate()
после создания фрагментов.источник
Что это
BasePagerAdapter
? Вы должны использовать один из стандартных адаптеров пейджера - либо,FragmentPagerAdapter
либоFragmentStatePagerAdapter
, в зависимости от того, хотите ли вы, чтобы фрагменты, которые больше не нужны,ViewPager
либо сохранялись (первый), либо сохраняли свое состояние (последний) и создавались заново, если снова нужноПример кода для использования
ViewPager
можно найти здесьЭто правда, что управление фрагментами в пейджере представлений между экземплярами операций немного сложнее, потому что
FragmentManager
в среде заботится о сохранении состояния и восстановлении любых активных фрагментов, которые создал пейджер. Все это действительно означает, что адаптер при инициализации должен убедиться, что он повторно соединяется с любыми восстановленными фрагментами. Вы можете посмотреть на кодFragmentPagerAdapter
илиFragmentStatePagerAdapter
посмотреть, как это делается.источник
Если у кого-то возникают проблемы с его FragmentStatePagerAdapter, неправильно восстанавливающего состояние его фрагментов ... то есть ... новые фрагменты создаются FragmentStatePagerAdapter вместо того, чтобы восстанавливать их из состояния ...
Убедитесь, что вы звоните,
ViewPager.setOffscreenPageLimit()
прежде чем позвонитьViewPager.setAdapter(fragmentStatePagerAdapter)
После вызова
ViewPager.setOffscreenPageLimit()
... ViewPager сразу же посмотрит на свой адаптер и попытается получить его фрагменты. Это может произойти до того, как ViewPager сможет восстановить фрагменты из saveInstanceState (создавая новые фрагменты, которые нельзя повторно инициализировать из SavedInstanceState, поскольку они новые).источник
Я придумал это простое и элегантное решение. Предполагается, что действие отвечает за создание фрагментов, а адаптер просто обслуживает их.
Это код адаптера (здесь нет ничего странного, за исключением того факта, что
mFragments
это список фрагментов, поддерживаемый Activity)Вся проблема этого потока заключается в получении ссылки на «старые» фрагменты, поэтому я использую этот код в onCreate Activity.
Конечно, вы можете дополнительно настроить этот код при необходимости, например, убедившись, что фрагменты являются экземплярами определенного класса.
источник
Чтобы получить фрагменты после изменения ориентации, вы должны использовать .getTag ().
Для большей обработки я написал свой собственный ArrayList для моего PageAdapter, чтобы получить фрагмент по viewPagerId и FragmentClass в любой позиции:
Так что просто создайте MyPageArrayList с фрагментами:
и добавьте их в viewPager:
после этого вы можете получить после ориентации изменить правильный фрагмент, используя его класс:
источник
Добавить:
перед вашим классом.
это не работает, сделайте что-то вроде этого:
источник