Вопрос по android-viewpager, android – Как определить, когда Фрагмент становится видимым в ViewPager

679

Проблема: фрагментonResume() вViewPager срабатывает до того, как фрагмент становится фактически видимым.

Например, у меня есть 2 фрагмента сViewPager а такжеFragmentPagerAdapter, Второй фрагмент доступен только для авторизованных пользователей, и мне нужно попросить пользователя войти в систему, когда фрагмент станет видимым (используя диалоговое окно с предупреждением).

НоViewPager создает второй фрагмент, когда первый виден, чтобы кэшировать второй фрагмент, и делает его видимым, когда пользователь начинает считывание.

ИтакonResume() событие происходит во втором фрагменте задолго до того, как оно станет видимым. Вот почему я пытаюсь найти событие, которое запускается, когда второй фрагмент становится видимым, чтобы показать диалог в соответствующий момент.

Как это может быть сделано?

Кажется, что ViewPager недостаточно гибок и не позволяет отключить кеширование, так как минимальный setOffscreenPageLimit равен 1:stackoverflow.com/questions/10073214/…, Не вижу никакой причины для этого, и ожидаемое поведение (в случае обязательного кэширования) состоит в создании фрагмента, НО запускающего фрагмента onResume (), когда фрагмент становится видимым. 4ntoine
лучше ли просто отображать информацию в TextView с помощью & quot; Логин & quot; кнопка? Каково ваше решение для этого случая? 4ntoine
& quot; у меня есть 2 фрагмента с ViewPager и FragmentPagerAdapter. Второй фрагмент может быть доступен только авторизованным пользователям, и я должен попросить пользователя войти в систему, когда фрагмент станет видимым (диалоговое окно с предупреждением). & Quot; - ИМХО, это ужасный UX. Если вы откроете диалоговое окно, потому что пользователь провел пальцем по горизонтали, я получу одну оценку в магазине Play на уровне одной звезды. CommonsWare
Немного поздно, но для тех, кто сталкивается с той же проблемой, вы можете попробоватьFragmentViewPager  библиотека (я автор), которая занимается этой проблемой и предоставляет несколько дополнительных функций. Для примера, проверьте страницу GitHub проекта или этуstackoverflow answer. S. Brukhanda
& quot; Нет необходимости загружать данные, если они не будут отображаться. & quot; - тогда вы не должны помещать это вViewPager, В двухстраничном пейджере обе страницы будут загружены сразу, нравится вам это или нет. Пользовательский опытViewPager Предполагается, что контент появляется сразу после считывания, а не через некоторое время. ПоэтомуViewPager инициализирует страницу впереди того, что видно, чтобы обеспечить удобство работы пользователя. CommonsWare

Ваш Ответ

21   ответ
125

Это похоже на восстановление нормальногоonResume() поведение, которое вы ожидаете. Это хорошо работает, нажав клавишу домой, чтобы выйти из приложения, а затем снова войти в приложение.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
}
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededonVisibleToUser()Error: User Rate Limit ExceededonResume()Error: User Rate Limit ExceededsetUserVisibleHint(boolean)Error: User Rate Limit ExceededonResume()Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededsetUserVisibleHintError: User Rate Limit ExceededonCreateViewError: User Rate Limit ExceededsetUserVisibleHintError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
19

Fragment вViewPager видимо, я уверен, чтоonly using 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 be called when viewpager creates fragment and when we go to this fragment background or another activity or fragment
     * NOT called 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 called 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();
    }
}

EXPLANATION Вы можете проверить logcat ниже тщательно, тогда я думаю, вы знаете, почему это решение будет работать

First launch

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

Go to page2

Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment2: setUserVisibleHint: isVisibleToUser=true isResumed=true

Go to page3

Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=true
Fragment3: setUserVisibleHint: isVisibleToUser=true isResumed=true

Go to background:

Fragment1: onStop mIsVisibleToUser=false
Fragment2: onStop mIsVisibleToUser=false
Fragment3: onStop mIsVisibleToUser=true

Go to foreground

Fragment1: onStart mIsVisibleToUser=false
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=true

ДЕМО проект здесь

Надеюсь, это поможет

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededgithub.com/PhanVanLinh/AndroidViewPagerSkeleton/tree/master. SubChildContainerFragmentError: User Rate Limit Exceededa fragment has another view pager which also consists fragmentError: User Rate Limit ExceededSubChildContainerFragmentError: User Rate Limit ExceededChildContainerFragmentError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
1

когда пытался заставить таймер срабатывать, когда фрагмент в окне просмотра был на экране для просмотра пользователем.

Таймер всегда запускается непосредственно перед тем, как пользователь увидит фрагмент. Это потому что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 и т. Д.

1

setUserVisibleHint(false) не вызывается при остановке активности / фрагмента. Вам все еще нужно проверить запуск / остановку, чтобы правильно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
    }
}
2

когда фрагмент должен уведомить докладчика о том, что представление стало видимым, и докладчик введен Даггером вfragment.onAttach().

setUserVisibleHint() недостаточно, мы обнаружили 3 разных случая, которые необходимо решить (onAttach() упоминается, чтобы вы знали, когда ведущий доступен):

Fragment has just been created. The system makes the following calls:

setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
onAttach()
...
onResume()

Fragment already created and home button is pressed. When restoring the app to foreground, this is called:

onResume()

Orientation change:

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

@Override
public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);

        if (hidden) {

        }else
        {}
    }
0

mContext.getWindow().getDecorView().isShown() //boolean
24
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);
    }
}
Error: User Rate Limit Exceeded
4

onCreateOptionsMenu а такжеonPrepareOptionsMenu методы, вызываемые только в случае фрагментаreally видимый. Я не мог найти какой-либо метод, который ведет себя так, как я, также я пыталсяOnPageChangeListener но это не сработало для ситуаций, например, мне нужна переменная, инициализированная вonCreate метод.

Таким образом, эти два метода могут быть использованы для решения этой проблемы в качестве обходного пути, особенно для небольших и коротких рабочих мест.

Я думаю, что это лучшее решение, но не лучшее. Я буду использовать это, но в то же время буду ждать лучшего решения.

С уважением.

14

setPrimaryItem() вFragmentPagerAdapter подкласс. Я использую этот метод, и он работает хорошо.

@
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();
    }
}
Error: User Rate Limit Exceededstackoverflow.com/questions/40149039/…
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
2

Добавить следующий код внутри фрагмента

@Override
public void setMenuVisibility(final boolean visible) 
 {
    super.setMenuVisibility(visible);
    if (visible && isResumed()) 
     {

     }
}
Error: User Rate Limit Exceeded
59

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

  }
});
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededstackoverflow.com/questions/23115283/…
544

Вы можете сделать следующее, переопределивsetUserVisibleHint в вашемFragment:

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededisResumed()Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
2

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

Fragment.onHiddenChanged() для этого.

public void onHiddenChanged(boolean hidden)

Called when the hidden state (as returned by isHidden()) of the fragment has changed. Fragments start out not hidden; this will be called whenever the fragment changes state from that.

Parameters
hidden - boolean: True if the fragment is now hidden, false if it is not visible.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededdeveloper.android.com/reference/android/app/…
1

Я была такая же проблема.ViewPager выполняет другие события жизненного цикла фрагмента, и я не мог изменить это поведение. Я написал простой пейджер, используя фрагменты и доступные анимации. SimplePager

53

setUserVisibleHint() иногда вызываетсяbefore onCreateView() а иногда после чего возникают проблемы.

Чтобы преодолеть это нужно проверитьisResumed() а внутриsetUserVisibleHint() метод. Но в этом случае я понял,setUserVisibleHint() вызываетсяonly если фрагмент возобновлен и видим, НЕ при создании.

Так что если вы хотите обновить что-то, когда фрагментvisibleпоставь свою функцию обновления как вonCreate() а такжеsetUserVisibleHint():

@Override
public View onCreateView(...){
    ...
    myUIUpdate();
    ...        
}
  ....
@Override
public void setUserVisibleHint(boolean visible){
    super.setUserVisibleHint(visible);
    if (visible && isResumed()){
        myUIUpdate();
    }
}

UPDATE: Еще я понялаmyUIUpdate() иногда вызывается дважды, причина в том, что если у вас есть 3 вкладки и этот код находится на 2-й вкладке, при первом открытии 1-й вкладки также создается 2-я вкладка, даже если она не видна иmyUIUpdate() называется. Затем, когда вы проводите до 2-й вкладки,myUIUpdate() отif (visible && isResumed()) называется и в результате,myUIUpdate() может быть вызван дважды в секунду.

Другойproblem является!visible вsetUserVisibleHint Вызывается как 1) при выходе из экрана фрагмента и 2) до его создания при первом переключении на экран фрагмента.

Solution:

private boolean fragmentResume=false;
private boolean fragmentVisible=false;
private boolean fragmentOnCreated=false;
...

@Override
public View onCreateView(...){
    ...
    //Initialize variables
    if (!fragmentResume && fragmentVisible){   //only when first time fragment is created
        myUIUpdate();
    }
    ...        
}

@Override
public void setUserVisibleHint(boolean visible){
    super.setUserVisibleHint(visible);
    if (visible && isResumed()){   // only at fragment screen is resumed
        fragmentResume=true;
        fragmentVisible=false;
        fragmentOnCreated=true;
        myUIUpdate();
    }else  if (visible){        // only at fragment onCreated
        fragmentResume=false;
        fragmentVisible=true;
        fragmentOnCreated=true;
    }
    else if(!visible && fragmentOnCreated){// only when you go out of fragment screen
        fragmentVisible=false;
        fragmentResume=false;
    }
}

Explanation:

fragmentResume,fragmentVisible: УбеждаетсяmyUIUpdate() вonCreateView() вызывается только когда фрагмент создан и виден, а не при возобновлении. Это также решает проблему, когда вы находитесь на первой вкладке, вторая вкладка создается, даже если она не видна. Это решает это и проверяет, виден ли фрагмент экрана, когдаonCreate.

fragmentOnCreated: Гарантирует, что фрагмент не виден и не вызывается при первом создании фрагмента. Так что теперь это, если предложение вызывается только когда вы проводите из фрагмента.

Update Вы можете поместить весь этот код вBaseFragment кодкак это и переопределить метод.

Error: User Rate Limit ExceededsetUserVisibleHintError: User Rate Limit ExceededonCreateViewError: User Rate Limit ExceededfragmentVisibleError: User Rate Limit ExceededfalseError: User Rate Limit Exceeded
2

FragmentStatePagerAdapters и 3 вкладки. Мне приходилось показывать Dilaog каждый раз, когда нажимали 1-ю вкладку, и скрывать его при нажатии на другие вкладки.

ПереопределениеsetUserVisibleHint() одно только не помогло найти текущий видимый фрагмент.

При нажатии на 3-й вкладке ----- & gt; 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.
    }

}
2

опубликовал здесь переопределение setPrimaryItem в странице radaapter Крис Ларсон почти работал на меня. Но этот метод вызывается несколько раз для каждой настройки. Также я получил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();
        }
    }
}
493

UPDATE: Библиотека поддержки Android (версия 11) наконецисправлена проблема с видимой подсказкойТеперь, если вы используете библиотеку поддержки для фрагментов, то вы можете смело использоватьgetUserVisibleHint() или переопределитьsetUserVisibleHint() чтобы зафиксировать изменения, как описано в ответе Горна.

UPDATE 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 показывать только одну страницу за раз, но предварительно кэшированные фрагменты также отображаются как «видимые». состояние (фактически невидимое), если вы используетеFragmentStatePagerAdapter вAndroid Support Library pre-r11.

Я переопределяю:

public class MyFragment extends Fragment {
    @Override
    public void setMenuVisibility(final boolean visible) {
        super.setMenuVisibility(visible);
        if (visible) {
            // ...
        }
    }
   // ...
}

Чтобы зафиксировать состояние фокуса фрагмента, которое, я думаю, является наиболее подходящим состоянием «видимости» Вы имеете в виду, что только один фрагмент в ViewPager может фактически размещать свои пункты меню вместе с элементами родительской активности.

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededtrueError: User Rate Limit ExceededonCreateOptionsMenuError: User Rate Limit ExceededsetUserVisibleHintError: User Rate Limit Exceeded
-3

и он возвращает общее количество минус количество страниц, которые нужно скрыть:

 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, и на нем должны отображаться только первые две страницы.

Это хорошо работает для страниц в конце, но не очень помогает для страниц в начале или в середине (хотя есть много способов сделать это).

Похожие вопросы