it-swarm.com.ru

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

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

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

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

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

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

671
4ntoine

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 может фактически размещать свои пункты меню вместе с элементами родительского действия.

493
Oasis Feng

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

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

public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}
541
gorn

Кажется, это восстанавливает нормальное поведение 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
}
121
craigrs84

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

  }
});
59
Ostkontentitan

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

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

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

Другой проблема - это !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() вызывается только при создании и отображении фрагмента, а не при возобновлении. Это также решает проблему, когда вы находитесь на 1-й вкладке, 2-я вкладка создается, даже если она не видна. Это решает это и проверяет, виден ли фрагмент экрана, когда onCreate.

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

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

53
Jemshit Iskenderov
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);
    }
}
24
w3hacker

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

ОБЪЯСНЕНИЕ Вы можете внимательно проверить приведенный ниже журнал, тогда я думаю, что вы, возможно, знаете, почему это решение будет работать

Первый запуск

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

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

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

19
Linh

Переопределите 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();
    }
}
14
kris larson

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

public void onHiddenChanged(boolean hidden)

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

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

10
dan

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

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

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

С уважением.

4
ismailarilik

Я столкнулся с той же проблемой при работе с 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.
    }

}
2
Aman Arora

Другое решение размещенное здесь переопределение setPrimaryItem в pradapter Крисом Ларсоном почти сработало для меня. Но этот метод вызывается несколько раз для каждой настройки. Также я получил 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
Gober

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

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

     }
}
2
Afzaal Iftikhar

Попробуйте это, это работа для меня:

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

        if (hidden) {

        }else
        {}
    }
2
Kyo Huu

У нас есть особый случай с 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
Pin

Обнаружение по 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();
}
2
Mohammad Reza Norouzi

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

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

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

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

1
Rukmal Dias

Я поддерживаю 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;
    }
}
0
Andoctorey

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

В вашей 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
Martin Mbae

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

mContext.getWindow().getDecorView().isShown() //boolean
0
Ali Akram