Як визначити, коли фрагмент стане видимим у ViewPager


754

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

Наприклад, у мене є 2 фрагменти з ViewPagerі FragmentPagerAdapter. Другий фрагмент доступний лише для авторизованих користувачів, і мені потрібно попросити користувача увійти, коли фрагмент стане видимим (за допомогою діалогового вікна попередження).

АЛЕ ViewPagerстворює другий фрагмент, коли перший видно, щоб кешувати другий фрагмент, і робить його видимим, коли користувач починає перебирати.

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

Як це можна зробити?


18
"У мене є 2 фрагмент з ViewPager та FragmentPagerAdapter. Другий фрагмент може бути доступний лише для авторизованих користувачів, і я повинен попросити використовувати для входу, коли фрагмент стане видимим (діалогове діалогове вікно)." - ІМХО, це жахливо UX. Спливаючи діалогове вікно, тому що користувач провела горизонтально, це призведе до того, що я отримаю оцінку зірки в Play Store.
CommonsWare

чи краще просто відображати інформацію в TextView кнопкою "Вхід"? Яке ваше рішення для цього випадку?
4нтоїн

5
"Немає необхідності завантажувати дані, якщо вони не відображатимуться." - тоді ви не повинні ставити його в ViewPager. У двосторінковому пейджері обидві сторінки будуть завантажені негайно, незалежно від того, подобається вам це чи ні. Досвід користувача ViewPagerповинен бути , що зміст там відразу після зчитування, через якийсь - то час ні. Ось чому ViewPagerініціалізує сторінку попереду того, що видно, щоб гарантувати користувачеві.
CommonsWare

2
Здається, що ViewPager недостатньо гнучкий і не дозволяє вимкнути кешування, оскільки мінімальний setOffscreenPageLimit становить 1: stackoverflow.com/questions/10073214/… . Не бачите жодних причин для цього, і очікувана поведінка (у разі обов'язкового кешування) полягає у створенні фрагмента, АЛЕ фрагмент вогню onResume (), коли фрагмент стане видимим.
4ntoine

1
Трохи запізнюючись, але для тих, хто стикається з тією ж проблемою, ви можете спробувати бібліотеку FragmentViewPager (я автор), яка займається цією проблемою і надає кілька додаткових функцій. Для зразка перегляньте сторінку GitHub проекту або відповідь stackoverflow .
С. Бруханда

Відповіді:


582

Як визначити, коли фрагмент стане видимим у 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 (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показуйте лише одну сторінку за раз, але попередньо кешовані фрагменти також переводяться у "видимий" стан (фактично невидимий), якщо ви використовуєте 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
}

4
Саме те, що я шукав. Вирішує проблему з setUserVisibleHintвикликом раніше, onCreateViewі setUserVisibleHintце не викликає, якщо додаток переходить у фоновий режим, а потім на перший план. Дивовижно! Дякую!
Олексій

11
Я думаю, що зателефонувати onResume yourself - це дуже погана ідея, але в іншому випадку все, що вам потрібно, щоб відповісти на запитання цієї публікації, знаходиться у функції 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

  }
});

1
Це рішення добре працює, дякую. Це має бути рекомендованим рішенням для тих, хто будує для старих платформ із використанням пакетів підтримки (v4) фрагментів підтримки
Frank Yin

7
Варто зауважити, що це не може дати вам очікуваного результату, якщо ви використовуєте FragmentStatePagerAdapter та інстанціюєте новий екземпляр Fragment на getItem (), оскільки ви будете встановлювати стан лише на новому фрагменті, а не на той, який отримав оглядач
Бен Пірсон

1
Це рішення добре, якщо ви хочете щось виконати, коли фрагмент стане видимим. Він не зберігає видимий стан фрагмента (тобто він не знає, коли фрагмент невидимий).
AsafK

Я однаковий тип проблем. будь ласка, допоможіть мені вирішити проблему. Мої повідомлення: stackoverflow.com/questions/23115283 / ...
Jeeten Parmar

це дає нульовий вказівник виключення для адаптера, який я використовую в методі iamVisible фрагмента. Я намагаюся встановити дані, отримані з мережі, лише коли фрагмент видно.
zaphod100.10

58

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-й вкладці, коли ви вперше відкриваєте першу вкладку, 2-я вкладка також створюється, навіть її не видно, і myUIUpdate()вона називається. Потім, коли ви переходите на другу вкладку, myUIUpdate()з 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
Ваше рішення працює чудово, за винятком одного сценарію, коли фрагмент відкривається нормально, а потім ви натискаєте якусь кнопку, щоб замінити його на інший фрагмент, а потім натисніть назад, щоб вискакувати новий фрагмент із задніх версій, він у цьому випадку setUserVisibleHintне отримав дзвінок .. ! а всередину onCreateViewметод fragmentVisibleбув false!? тому фрагмент виявився порожнім ..! якісь думки.
Алаа АбуЗаріфа

28

Для того, щоб виявити Fragmentв ViewPagerвидимої, я абсолютно впевнений , що тільки з допомогою setUserVisibleHint цього недостатньо.
Ось моє рішення перевірити, видимий чи невидимий фрагмент. Спочатку, запускаючи панель перегляду, переключіться на сторінку, перейдіть до іншої діяльності / фрагмента / фону / переднього плану '

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

ПОЯСНЕННЯ Ви можете уважно перевірити нижченаведений логічний код нижче, я думаю, ви можете знати, чому це рішення спрацює

Перший запуск

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

Проект DEMO тут

Сподіваюся, це допоможе


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

2
Це має бути прийнятою відповіддю. Працює з android.app.Fragment також, що є величезним бонусом.
РаджВ

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 з Android jetpack.


1
Дякую за приємне рішення. Я шукав це давно. Чудова робота
Анураг Срівастава

1
Для ViewPager2 опція BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT не є поведінкою за замовчуванням, її потрібно встановити вручну. Під час встановлення цієї опції ваше рішення працювало на мене. Дякую.
driAn

1
Станом на квітень 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 і викликали кілька разів. Опубліковано альтернативу нижче!
Гобер


@Gober, що зберігає об'єкт вперше, перевіряє та ігнорує подальший виклик тим самим об'єктом Object, як і FragmentStateAdapter, що робить метод setPrimaryItem ()
wanglugao

12

Переосмислити Fragment.onHiddenChanged()для цього.

public void onHiddenChanged(boolean hidden)

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

Параметри
hidden- boolean: Вірно, якщо фрагмент зараз приховано, хибний, якщо його не видно.


3
цей метод на сьогоднішній день застарів, і його більше не можна використовувати
блюфіл

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методі.

Тож ці два методи можна використовувати для вирішення цієї проблеми як вирішення, зокрема для невеликих та коротких робочих місць.

Я думаю, це краще рішення, але не найкраще. Я буду використовувати це, але чекайте кращого рішення в той же час.

З повагою


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

2

Додайте наступний код всередині фрагмента

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

     }
}

Це працює лише для ViewPagerFragments. Не для уламків при нормальній активності.
AndroidGuy

2

Я зіткнувся з тією ж проблемою під час роботи FragmentStatePagerAdapters 3 вкладками. Мені довелося показувати 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 та ін.


1

У мене було те саме питання. ViewPagerвиконує інші події життєвого циклу фрагмента, і я не міг змінити такої поведінки. Я написав простий пейджер, використовуючи фрагменти та доступні анімації. SimplePager



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

Забули згадати, щойно ви додасте дочірній фрагмент до набору батьківських фрагментів fragment.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
    }
}

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;

Однак якщо ви використовуєте ящик навігації, вибір у ящику буде змінено на профіль, хоча ми не перейшли до профілюFragment.

Щоб скинути вибір до поточного вибору, запустіть код нижче

        navigationView.getMenu().getItem(0).setChecked(true);

-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, і він повинен показувати лише перші дві сторінки.

Це добре підходить для сторінок у кінці, але звичайно не допоможе тим, хто на початку чи в середині (хоча існує маса способів цього зробити).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.