Как определить, когда фрагмент становится видимым в ViewPager
: Фрагмент onResume() на ViewPager запускается до того, как фрагмент становится фактически видимым.
например, у меня есть 2 фрагменты с ViewPager и FragmentPagerAdapter. Второй фрагмент доступен только для авторизованных пользователей, и мне нужно попросить пользователя войти в систему, когда фрагмент станет видимым (с помощью диалогового окна предупреждения).
но ViewPager создает второй фрагмент, когда первый виден, чтобы кэшировать второй фрагмент и делает его видимым, когда пользователь начинает стучать.
так onResume() событие произойдет во втором фрагменте задолго до того, как он станет видимым. Вот почему я пытаюсь найти событие, которое срабатывает, когда второй фрагмент становится видимым, чтобы показать в нужный момент диалог.
как это можно сделать?
21 ответов:
обновление: библиотека поддержки Android (rev 11) наконец Исправлена проблема видимой подсказки пользователя, теперь, если вы используете библиотеку поддержки для фрагментов, то вы можете безопасно использовать
getUserVisibleHint()или переопределитьsetUserVisibleHint()чтобы захватить изменения, как описано в ответе горна.обновление 1 вот одна маленькая проблема с
getUserVisibleHint(). Это значение по умолчаниюtrue.// Hint provided by the app that this fragment is currently visible to the user. boolean mUserVisibleHint = true;так что может быть проблема, когда вы пытаетесь использовать его раньше
setUserVisibleHint()был вызван. В качестве обходного пути вы можете установить значение вonCreateспособ такой.public void onCreate(@Nullable Bundle savedInstanceState) { setUserVisibleHint(false);
устаревший ответ:
в большинстве случаев используют,
ViewPagerпоказывать только одну страницу за раз, но предварительно кэшированные фрагменты также переводятся в" видимое " состояние (фактически невидимое), если вы используетеFragmentStatePagerAdapterinAndroid Support Library pre-r11.заменить :
public class MyFragment extends Fragment { @Override public void setMenuVisibility(final boolean visible) { super.setMenuVisibility(visible); if (visible) { // ... } } // ... }чтобы захватить состояние фокуса фрагмента, который я думаю, является наиболее подходящим состоянием "видимость" Вы имеете в виду, так как только один фрагмент в ViewPager может фактически разместить свои пункты меню вместе с элементами родительской активности.
как определить, когда фрагмент становится видимым в ViewPager
вы можете сделать следующее, переопределив
setUserVisibleHintв своемFragment:public class MyFragment extends Fragment { @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser) { } else { } } }
это, кажется, восстановить нормальный
onResume()поведение, которое вы ожидаете. Он хорошо играет с нажатием клавиши home, чтобы выйти из приложения, а затем повторно войти в приложение.onResume()не вызывается дважды подряд.@Override public void setUserVisibleHint(boolean visible) { super.setUserVisibleHint(visible); if (visible && isResumed()) { //Only manually call onResume if fragment is already visible //Otherwise allow natural fragment lifecycle to call onResume onResume(); } } @Override public void onResume() { super.onResume(); if (!getUserVisibleHint()) { return; } //INSERT CUSTOM CODE HERE }
вот еще один способ, используя
onPageChangeListener:ViewPager pager = (ViewPager) findByViewId(R.id.viewpager); FragmentPagerAdapter adapter = new FragmentPageAdapter(getFragmentManager); pager.setAdapter(adapter); pager.setOnPageChangeListener(new OnPageChangeListener() { public void onPageSelected(int pageNumber) { // Just define a callback method in your fragment and call it like this! adapter.getItem(pageNumber).imVisible(); } public void onPageScrolled(int arg0, float arg1, int arg2) { // TODO Auto-generated method stub } public void onPageScrollStateChanged(int arg0) { // TODO Auto-generated method stub } });
setUserVisibleHint()вызывается иногда доonCreateView()и иногда после чего вызывает проблемы.чтобы преодолеть это, вам нужно проверить
isResumed()внутриsetUserVisibleHint()метод. Но в данном случае я понялsetUserVisibleHint()вызывается только если фрагмент возобновлен и виден, а не при создании.так что если вы хотите обновить что-то, когда фрагмент
visible, поставил функцию обновления как вonCreate()иsetUserVisibleHint():@Override public View onCreateView(...){ ... myUIUpdate(); ... } .... @Override public void setUserVisibleHint(boolean visible){ super.setUserVisibleHint(visible); if (visible && isResumed()){ myUIUpdate(); } }обновление: еще я понял
myUIUpdate()иногда вызывается дважды, причина в том, что если у вас есть 3 вкладки, и этот код находится на 2-й вкладке, когда вы впервые открываете 1-ю вкладку, 2-я вкладка также создается даже не видна иmyUIUpdate()называется. Затем, когда вы проводите до 2-й вкладки,myUIUpdate()Сif (visible && isResumed())называется и в результате,myUIUpdate()может быть вызван дважды в секунду.другое
package com.example.com.ui.fragment; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.example.com.R; public class SubscribeFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_subscribe, container, false); return view; } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser) { // called here } } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); } }
переопределить
setPrimaryItem()наFragmentPagerAdapterподкласс. Я использую этот метод, и он работает хорошо.@Override public void setPrimaryItem(ViewGroup container, int position, Object object) { // This is what calls setMenuVisibility() on the fragments super.setPrimaryItem(container, position, object); if (object instanceof MyWhizBangFragment) { MyWhizBangFragment fragment = (MyWhizBangFragment) object; fragment.doTheThingYouNeedToDoOnBecomingVisible(); } }
переопределить
Fragment.onHiddenChanged()для этого.
public void onHiddenChanged(boolean hidden)вызывается, когда скрытое состояние (как возвращается
isHidden()) фрагмент изменился. Фрагменты начинаются не скрытыми; это будет вызываться всякий раз, когда фрагмент изменяет состояние от этого.параметры
hidden-boolean: True, если фрагмент теперь скрыт, false, если он не виден.
для определения
FragmentнаViewPagerвидимо, я совершенно уверен, что только с помощьюsetUserVisibleHintне хватает.
Вот мое решение для проверки фрагмента видимого, невидимого при первом запуске viewpager, переключаться между страницами, перейти к другой деятельности / фрагмент / фон / передний план'public class BaseFragmentHelpLoadDataWhenVisible extends Fragment { protected boolean mIsVisibleToUser; // you can see this variable may absolutely <=> getUserVisibleHint() but it not. Currently, after many test I find that /** * This method will call when viewpager create fragment and when we go to this fragment from * background or another activity, fragment * NOT call when we switch between each page in ViewPager */ @Override public void onStart() { super.onStart(); if (mIsVisibleToUser) { onVisible(); } } @Override public void onStop() { super.onStop(); if (mIsVisibleToUser) { onInVisible(); } } /** * This method will call at first time viewpager created and when we switch between each page * NOT called when we go to background or another activity (fragment) when we go back */ @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); mIsVisibleToUser = isVisibleToUser; if (isResumed()) { // fragment have created if (mIsVisibleToUser) { onVisible(); } else { onInVisible(); } } } public void onVisible() { Toast.makeText(getActivity(), TAG + "visible", Toast.LENGTH_SHORT).show(); } public void onInVisible() { Toast.makeText(getActivity(), TAG + "invisible", Toast.LENGTH_SHORT).show(); } }объяснение Вы можете проверить logcat ниже тщательно, то я думаю, что вы можете знать, почему это решение будет работать
первый запуск
Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=false Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=false Fragment3: setUserVisibleHint: isVisibleToUser=false isResumed=false Fragment1: setUserVisibleHint: isVisibleToUser=true isResumed=false // AT THIS TIME isVisibleToUser=true but fragment still not created. If you do something with View here, you will receive exception Fragment1: onCreateView Fragment1: onStart mIsVisibleToUser=true Fragment2: onCreateView Fragment3: onCreateView Fragment2: onStart mIsVisibleToUser=false Fragment3: onStart mIsVisibleToUser=falseперейти на страницу 2
Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=true Fragment2: setUserVisibleHint: isVisibleToUser=true isResumed=trueперейти на страницу 3
Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=true Fragment3: setUserVisibleHint: isVisibleToUser=true isResumed=trueперейти к фону:
Fragment1: onStop mIsVisibleToUser=false Fragment2: onStop mIsVisibleToUser=false Fragment3: onStop mIsVisibleToUser=trueперейти на передний план
Fragment1: onStart mIsVisibleToUser=false Fragment2: onStart mIsVisibleToUser=false Fragment3: onStart mIsVisibleToUser=trueнадеюсь, что это поможет
Я понял, что
onCreateOptionsMenuиonPrepareOptionsMenuметоды вызываются только в случае фрагмент действительно видны. Я не мог найти ни одного метода, который ведет себя так, как эти, Также я пробовалOnPageChangeListenerно это не сработало для ситуаций, например, мне нужна переменная, инициализированная вonCreateметод.таким образом, эти два метода могут быть использованы для этой проблемы в качестве обходного пути, особенно для небольших и коротких заданий.
Я думаю, это лучшее решение, но не лучший. Я буду использовать это, но ждать лучшего решения в то же время.
С уважением.
другое решение опубликовано здесь переопределение setPrimaryItem в pageradapter Крисом Ларсоном почти работал для меня. Но этот метод вызывается несколько раз для каждой установки. Также я получил NPE вид и т. д. во фрагменте как это не готово первые несколько раз этот метод вызывается. Со следующими изменениями это сработало для меня:
private int mCurrentPosition = -1; @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { super.setPrimaryItem(container, position, object); if (position == mCurrentPosition) { return; } if (object instanceof MyWhizBangFragment) { MyWhizBangFragment fragment = (MyWhizBangFragment) object; if (fragment.isResumed()) { mCurrentPosition = position; fragment.doTheThingYouNeedToDoOnBecomingVisible(); } } }
добавить следующий код внутри фрагмент
@Override public void setMenuVisibility(final boolean visible) { super.setMenuVisibility(visible); if (visible && isResumed()) { } }
я столкнулся с той же проблемой при работе с
FragmentStatePagerAdaptersи 3 вкладки. Я должен был показать Dilaog всякий раз, когда 1-я вкладка была нажата и скрыть его при нажатии других вкладок.переопределение
setUserVisibleHint()в одиночку не помогло найти текущий видимый фрагмент.при нажатии с 3-й вкладки - - - - - > 1-я вкладка. Он срабатывал дважды для 2-го фрагмента и для 1-го фрагмента. Я объединил его с методом isResumed ().
@Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); isVisible = isVisibleToUser; // Make sure that fragment is currently visible if (!isVisible && isResumed()) { // Call code when Fragment not visible } else if (isVisible && isResumed()) { // Call code when Fragment becomes visible. } }
у нас есть особый случай с MVP, где фрагмент должен уведомить ведущего, что вид стал видимым, и ведущий вводится кинжалом в
fragment.onAttach().
setUserVisibleHint()недостаточно, мы обнаружили 3 разных случая, которые необходимо было решить (onAttach()упоминается так, что вы знаете, когда ведущий доступен):
фрагмент только что был создан. Система делает следующее звонки:
setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null onAttach() ... onResume()фрагмент уже создан и кнопка home нажата. При восстановлении приложения на передний план, это называется:
onResume()изменить ориентацию:
onAttach() // presenter available onResume() setUserVisibleHint()мы только хотим, чтобы подсказка видимости попала к ведущему один раз, так что вот как мы это делаем:
@Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View root = inflater.inflate(R.layout.fragment_list, container, false); setHasOptionsMenu(true); if (savedInstanceState != null) { lastOrientation = savedInstanceState.getInt(STATE_LAST_ORIENTATION, getResources().getConfiguration().orientation); } else { lastOrientation = getResources().getConfiguration().orientation; } return root; } @Override public void onResume() { super.onResume(); presenter.onResume(); int orientation = getResources().getConfiguration().orientation; if (orientation == lastOrientation) { if (getUserVisibleHint()) { presenter.onViewBecomesVisible(); } } lastOrientation = orientation; } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (presenter != null && isResumed() && isVisibleToUser) { presenter.onViewBecomesVisible(); } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putInt(STATE_LAST_ORIENTATION, lastOrientation); }
взгляд
focused view!это работает для меня
public static boolean isFragmentVisible(Fragment fragment) { Activity activity = fragment.getActivity(); View focusedView = fragment.getView().findFocus(); return activity != null && focusedView != null && focusedView == activity.getWindow().getDecorView().findFocus(); }
отметим, что
setUserVisibleHint(false)не вызывается при остановке активности / фрагмента. Вам все равно нужно будет проверить start / stop, чтобы правильноregister/unregisterлюбых слушателей/и т. д.кроме того, вы получите
setUserVisibleHint(false)если ваш фрагмент начинается в невидимом состоянии; вы не хотите, чтобыunregisterтам, так как вы никогда не регистрировались раньше в этом случае.@Override public void onStart() { super.onStart(); if (getUserVisibleHint()) { // register } } @Override public void onStop() { if (getUserVisibleHint()) { // unregister } super.onStop(); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser && isResumed()) { // register if (!mHasBeenVisible) { mHasBeenVisible = true; } } else if (mHasBeenVisible){ // unregister } }
я столкнулся с этой проблемой, когда я пытался получить таймер для запуска, когда фрагмент в viewpager был на экране для пользователя, чтобы увидеть.
таймер всегда запускается непосредственно перед тем, как фрагмент был замечен пользователем. Это потому что
onResume()метод во фрагменте вызывается до того, как мы сможем увидеть фрагмент.мое решение было сделать проверку в
onResume()метод. Я хотел вызвать определенный метод ' foo ()', когда фрагмент 8 был текущим фрагментом просмотра пейджеров.@Override public void onResume() { super.onResume(); if(viewPager.getCurrentItem() == 8){ foo(); //Your code here. Executed when fragment is seen by user. } }надеюсь, что это помогает. Я видел, как эта проблема всплывала много раз. Это, кажется, самое простое решение, которое я видел. Многие другие не совместимы с более низкими API и т. д.
У меня была та же проблема.
ViewPagerвыполняет другие события жизненного цикла фрагмента, и я не мог изменить это поведение. Я написал простой пейджер, используя фрагменты и доступные анимации. SimplePager
попробуйте это, это работа для меня:
@Override public void onHiddenChanged(boolean hidden) { super.onHiddenChanged(hidden); if (hidden) { }else {} }
Я переопределил метод Count связанного FragmentStatePagerAdapter и вернул ему общее количество минус количество страниц для скрытия:
public class MyAdapter : Android.Support.V13.App.FragmentStatePagerAdapter { private List<Fragment> _fragments; public int TrimmedPages { get; set; } public MyAdapter(Android.App.FragmentManager fm) : base(fm) { } public MyAdapter(Android.App.FragmentManager fm, List<Android.App.Fragment> fragments) : base(fm) { _fragments = fragments; TrimmedPages = 0; } public override int Count { //get { return _fragments.Count; } get { return _fragments.Count - TrimmedPages; } } }Итак, если в ViewPager изначально добавлено 3 фрагмента, и только первые 2 должны отображаться до тех пор, пока не будет выполнено какое-либо условие, переопределите счетчик страниц, установив TrimmedPages в 1, и он должен показывать только первые две страницы.
Это хорошо работает для страниц в конце, но не очень поможет для тех, кто на начало или середина (хотя есть много способов сделать это).
Comments