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

754

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

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

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

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

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

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

Ответы:

581

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

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

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}
Горн
источник
7
Благодаря сегодняшнему обновлению библиотеки поддержки Android (версия 11), видимая пользователю подсказка наконец исправлена. Теперь можно безопасно использовать видимую подсказку для ViewPager.
Оазис Фенг
58
Я обнаружил, что метод setUserVisibleHint вызывается ДО ТОГО, как вызывается onCreateView, и это затрудняет отслеживание любой инициализации.
AndroidDev
11
@AndroidDev Если вы хотите запустить некоторый код, когда подсказка становится истинной, но для которой требуется, чтобы дерево представлений уже было инициализировано, просто оберните этот блок кода, isResumed()чтобы избежать NPE. Работает нормально для меня.
Рави Таплиял
13
Просто смешно, что существует так много разных хаков для того, что SDK должен предоставить по умолчанию.
Mike6679
15
setUserVisibleHintсейчас устарел
SR
525

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

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

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

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

Оазис Фэн
источник
73
Будьте осторожны при использовании getActivity (), при первом запуске он будет нулевым.
Вовкаб
10
Обратите внимание, что setUserVisibleHint () всегда работал правильно в FragmentPagerAdapter.
louielouie
8
Это решение (а также решение Горна) немного отстает. В случаях, когда просмотрщик выполняется быстро и многократно, этот метод вызывается с задержкой (когда фрагмент больше не виден / невидим).
AsafK
3
Я получаю trueдля getUserVisibleHint (), когда onCreateOptionsMenuвызывается, и когда setUserVisibleHintвызывается, меню, кажется, еще не было создано. В конечном итоге я получаю две опции меню, которые добавляются, когда мне нужно только меню из фрагмента, который виден. Есть предложения на этот счет?
cYrixmorten
13
setUserVisibleHintсейчас устарел
SR
143

Это, кажется, восстанавливает нормальное 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
}
craigrs84
источник
4
Именно то, что я искал. Решает проблему с setUserVisibleHintвызовом раньше onCreateViewи setUserVisibleHintне вызывается, если приложение переходит в фоновый режим, а затем на передний план. Потрясающие! Спасибо!
Алекс
11
Я думаю, что вызывать onResume самостоятельно - довольно плохая идея, но в остальном все, что вам нужно для ответа на вопрос этого поста, находится в функции setUserVisibleHint!
Квентин Г.
4
Я предпочел бы реализовать простой onVisibleToUser()метод и вызывать его из onResume()и из, setUserVisibleHint(boolean)а не вызывать onResume()себя и вмешиваться в обратные вызовы жизненного цикла. В противном случае я думаю, что этот подход работает хорошо, спасибо!
Стефан Хеннингсен
1
Да, я согласен с вышеуказанными комментариями. В общем, старайтесь избегать вызова открытых методов из открытых методов в вашем классе. Сделайте приватный метод и вызовите его из обоих открытых методов.
Йохан Франзен
4
setUserVisibleHint устарела!
Хамид Реза
70

Вот еще один способ использования 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

  }
});
Ostkontentitan
источник
1
Это решение работает хорошо, спасибо. Это должно быть рекомендуемое решение для тех, кто собирает старые платформы с использованием пакетов поддержки фрагментов (v4)
Франк Инь,
7
Стоит отметить, что это не может дать вам ожидаемый результат, если вы используете FragmentStatePagerAdapter и создаете экземпляр нового экземпляра Fragment в getItem (), потому что вы устанавливаете только состояние для нового фрагмента, а не того, которое получил viewpager
Бен Пирсон
1
Это решение хорошо, если вы хотите выполнить что-то, когда фрагмент становится видимым. Он не сохраняет видимое состояние фрагмента (то есть не знает, когда фрагмент невидим).
AsafK
Я такой же тип проблемы. Пожалуйста, помогите мне решить проблему. Мое сообщение: stackoverflow.com/questions/23115283/…
Джитен Пармар
это дает исключение нулевого указателя для адаптера, который я использую в методе iamVisible фрагмента. Я пытаюсь установить данные, полученные из сети, только когда фрагмент виден.
zaphod100.10
58

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

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

Поэтому, если вы хотите что-то обновить, когда Fragment установлен 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()from if (visible && isResumed())и, как результат, myUIUpdate()может вызываться дважды в секунду.

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

Решение:

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

Объяснение:

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

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

Обновление Вы можете поместить весь этот код в BaseFragmentкод, подобный этому, и переопределить метод.

Джемшит Искендеров
источник
1
Ваше решение работает отлично, за исключением одного сценария, когда фрагмент открывается нормально, а затем вы нажимаете какую-то кнопку, чтобы заменить его другим фрагментом, а затем нажимаете кнопку назад, чтобы извлечь новый фрагмент из backstack, если в этом случае setUserVisibleHintвызов не был получен . ! а внутри onCreateViewметод fragmentVisibleбыл false! так что фрагмент оказался пустым! Какие-нибудь мысли.?
Алаа АбуЗарифа
28

Чтобы обнаружить 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 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();
    }
}

ОБЪЯСНЕНИЕ Вы можете внимательно проверить 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

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

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

Фан Ван Линь
источник
1
Это не работает, когда фрагмент имеет другой пейджер просмотра, который также состоит из фрагмента.
Шихаб Уддин
извините, я не могу опубликовать ответ в это время, потому что у меня нет достаточно времени, если возможно, пожалуйста, отправьте мой мерзавец о вашей проблеме здесь github.com/PhanVanLinh/AndroidViewPagerSkeleton/tree/master . SubChildContainerFragmentиспользуется для обнаружения a fragment has another view pager which also consists fragment. Можно смешивать SubChildContainerFragmentи ChildContainerFragmentдо 1 класса. Надеюсь, это поможет. Я отправлю полный ответ позже
Фан Ван Лин
26
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);
    }
}
w3hacker
источник
2
Это должен быть принятый ответ. Также работает с android.app.Fragment, что является огромным бонусом.
RajV
setUserVisibleHintУстаревший
ekashking
23

В ViewPager2и ViewPagerиз версии androidx.fragment:fragment:1.1.0вы можете просто использовать onPauseи onResumeобратные вызовы, чтобы определить, какой фрагмент в настоящее время видим для пользователя. onResumeобратный вызов вызывается, когда фрагмент становится видимым и onPauseкогда он перестает быть видимым.

В случае ViewPager2 это поведение по умолчанию, но такое же поведение можно легко включить для старого блага ViewPager.

Чтобы включить это поведение в первом ViewPager, вы должны передать FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTпараметр в качестве второго аргумента FragmentPagerAdapterконструктора.

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

Примечание: setUserVisibleHint()метод и FragmentPagerAdapterконструктор с одним параметром теперь устарели в новой версии Fragment from android jetpack.

lukjar
источник
1
Спасибо за хорошее решение. Я искал это долгое время. Отличная работа
Анураг Шривастава
1
Для ViewPager2 параметр BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT не является поведением по умолчанию, его необходимо установить вручную. При установке этой опции ваше решение работало на меня. Спасибо.
Drian
1
Начиная с APR 2020, это обновленное решение, которое работает как шарм.
Пракаш
1
@ 4ntoine, пожалуйста, подумайте о том, чтобы принять этот ответ как правильный ответ, поскольку на данный момент это правильный ответ, и большинство ответов используют устаревший метод setUserVisibleHint
Jorn Rigter
16

Переопределить 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();
    }
}
Крис Ларсон
источник
1
Это почти сработало, но были проблемы с NPE и вызывались несколько раз. Ниже выложена альтернатива!
Гобер
@ Сохранение объекта Object в первый раз, проверка и игнорирование последующего вызова с тем же объектом Object, что и FragmentStateAdapter в методе setPrimaryItem ()
wanglugao
12

Переопределите Fragment.onHiddenChanged()для этого.

public void onHiddenChanged(boolean hidden)

Вызывается, когда скрытое состояние (возвращаемое isHidden()) фрагмента изменилось. Фрагменты начинаются не скрытыми; это будет вызвано всякий раз, когда фрагмент изменяет состояние от этого.

Параметры
hidden- boolean: True, если фрагмент теперь скрыт, false, если он не виден.

Дан
источник
3
этот метод уже устарел и больше не может быть использован
bluewhile
4
@bluewhile где вы видите, что это устарело? developer.android.com/reference/android/app/...
Cel
1
Я только использовал это с успехом, и документация, кажется, не указывает, что это устарело.
Тревор
1
@bluewhile, в 4.1 (API 16) это еще не устарело.
дан
8
Я использую API уровня 19 и могу подтвердить, что хотя это не устарело, оно не работает так, как рекламируется. Например, onHiddenChanged не вызывается, когда фрагмент скрыт другим фрагментом.
4

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

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

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

С уважением.

ismailarilik
источник
2

Другое решение, размещенное здесь, переопределяющее 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();
        }
    }
}
Gober
источник
2

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

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

     }
}
Афзаал Ифтихар
источник
Это работает только для ViewPagerFragments. Не для фрагментов в нормальной деятельности.
AndroidGuy
2

Я столкнулся с той же проблемой при работе с FragmentStatePagerAdapters3 вкладками. Мне приходилось показывать Dilaog каждый раз, когда нажимали первую вкладку, и скрывать его при нажатии на другие вкладки.

Одно 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.
    }

}
Аман Арора
источник
2

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

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

  1. Фрагмент был только что создан. Система делает следующие звонки:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
  2. Фрагмент уже создан и нажата домашняя кнопка. При восстановлении приложения на передний план это называется:

    onResume()
  3. Изменение ориентации:

    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

Обнаружение по 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();
}
Мохаммед Реза Норузи
источник
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 и т. Д.

Malone
источник
1

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

Рукмал Диас
источник
1

Я использовал это, и это сработало!

mContext.getWindow().getDecorView().isShown() //boolean
Али Акрам
источник
1

Я поддерживаю SectionsPagerAdapter с дочерними фрагментами, поэтому после большой головной боли я наконец-то получил рабочую версию, основанную на решениях из этой темы:

public abstract class BaseFragment extends Fragment {

    private boolean visible;
    private boolean visibilityHintChanged;

    /**
     * Called when the visibility of the fragment changed
     */
    protected void onVisibilityChanged(View view, boolean visible) {

    }

    private void triggerVisibilityChangedIfNeeded(boolean visible) {
        if (this.visible == visible || getActivity() == null || getView() == null) {
            return;
        }
        this.visible = visible;
        onVisibilityChanged(getView(), visible);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (!visibilityHintChanged) {
            setUserVisibleHint(false);
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        if (getUserVisibleHint() && !isHidden()) {
            triggerVisibilityChangedIfNeeded(true);
        }
    }

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

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        visibilityHintChanged = true;
        if (isVisibleToUser && isResumed() && !isHidden()) {
            triggerVisibilityChangedIfNeeded(true);
        } else if (!isVisibleToUser) {
            triggerVisibilityChangedIfNeeded(false);
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        triggerVisibilityChangedIfNeeded(false);
    }

    @Override
    public void onStop() {
        super.onStop();
        triggerVisibilityChangedIfNeeded(false);
    }

    protected boolean isReallyVisible() {
        return visible;
    }
}
Andoctorey
источник
Забыл упомянуть, как только вы добавляете дочерний фрагмент в набор родительских фрагментов фрагмент.setUserVisibleHint (true);
Андокторей
И не забудьте использовать getChildFragmentManager () вместо getFragmentManager () для добавления дочерних фрагментов.
Андокторея
0

Обратите внимание, что 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
    }
}
elevenfive
источник
0

Простой способ реализации - проверка, вошел ли пользователь в систему, прежде чем перейти к фрагменту.

В вашей MainActivity вы можете сделать что-то подобное внутри метода onNavigationItemSelected .

 case R.id.nav_profile_side:


                if (User_is_logged_in) {

                    fragmentManager.beginTransaction()
                            .replace(R.id.content_frame
                                    , new FragmentProfile())
                            .commit();
                }else {

                    ShowLoginOrRegisterDialog(fragmentManager);

                }

                break;

Однако, если вы используете навигационный ящик, выбор в нем будет изменен на Профиль, хотя мы не перешли к ProfileFragment.

Чтобы сбросить выбор до текущего выбора, запустите код ниже

        navigationView.getMenu().getItem(0).setChecked(true);
Мартин Мбаэ
источник
0

setUserVisibleHint (логическое видимое) теперь не рекомендуется, так что это правильное решение

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

В ViewPager2 и ViewPager из версии androidx.fragment:fragment:1.1.0вы можете просто использовать onPause()и onResume()определить, какой фрагмент в данный момент виден пользователю. onResume()вызывается, когда фрагмент становится видимым и onPauseкогда он перестает быть видимым.

Чтобы включить это поведение в первом ViewPager, вы должны передать FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENTпараметр в качестве второго аргумента FragmentPagerAdapterконструктора.

Рахул
источник
-4

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

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

Саамы
источник