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



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



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



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



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



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

695   21  

21 ответов:

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

обновление 1 вот одна маленькая проблема с getUserVisibleHint(). Это значение по умолчанию true.

// Hint provided by the app that this fragment is currently visible to the user.
boolean mUserVisibleHint = true;

так что может быть проблема, когда вы пытаетесь использовать его раньше setUserVisibleHint() был вызван. В качестве обходного пути вы можете установить значение в onCreate способ такой.

public void onCreate(@Nullable Bundle savedInstanceState) {
    setUserVisibleHint(false);

устаревший ответ:

в большинстве случаев используют, ViewPager показывать только одну страницу за раз, но предварительно кэшированные фрагменты также переводятся в" видимое " состояние (фактически невидимое), если вы используете FragmentStatePagerAdapter in Android Support Library pre-r11.

заменить :

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

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

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

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

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

это, кажется, восстановить нормальный onResume() поведение, которое вы ожидаете. Он хорошо играет с нажатием клавиши home, чтобы выйти из приложения, а затем повторно войти в приложение. onResume() не вызывается дважды подряд.

@Override
public void setUserVisibleHint(boolean visible)
{
    super.setUserVisibleHint(visible);
    if (visible && isResumed())
    {
        //Only manually call onResume if fragment is already visible
        //Otherwise allow natural fragment lifecycle to call onResume
        onResume();
    }
}

@Override
public void onResume()
{
    super.onResume();
    if (!getUserVisibleHint())
    {
        return;
    }

    //INSERT CUSTOM CODE HERE
}

вот еще один способ, используя onPageChangeListener:

  ViewPager pager = (ViewPager) findByViewId(R.id.viewpager);
  FragmentPagerAdapter adapter = new FragmentPageAdapter(getFragmentManager);
  pager.setAdapter(adapter);
  pager.setOnPageChangeListener(new OnPageChangeListener() {

  public void onPageSelected(int pageNumber) {
    // Just define a callback method in your fragment and call it like this! 
    adapter.getItem(pageNumber).imVisible();

  }

  public void onPageScrolled(int arg0, float arg1, int arg2) {
    // TODO Auto-generated method stub

  }

  public void onPageScrollStateChanged(int arg0) {
    // TODO Auto-generated method stub

  }
});

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

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

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

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

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

другое

package com.example.com.ui.fragment;


import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.com.R;

public class SubscribeFragment extends Fragment {

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_subscribe, container, false);
        return view;
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        if (isVisibleToUser) {
            // called here
        }
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }
}

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

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    // This is what calls setMenuVisibility() on the fragments
    super.setPrimaryItem(container, position, object);

    if (object instanceof MyWhizBangFragment) {
        MyWhizBangFragment fragment = (MyWhizBangFragment) object;
        fragment.doTheThingYouNeedToDoOnBecomingVisible();
    }
}

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

public void onHiddenChanged(boolean hidden)

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

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

для определения Fragment на ViewPager видимо, я совершенно уверен, что только с помощьюsetUserVisibleHint не хватает.
Вот мое решение для проверки фрагмента видимого, невидимого при первом запуске viewpager, переключаться между страницами, перейти к другой деятельности / фрагмент / фон / передний план'

public class BaseFragmentHelpLoadDataWhenVisible extends Fragment {
    protected boolean mIsVisibleToUser; // you can see this variable may absolutely <=> getUserVisibleHint() but it not. Currently, after many test I find that

    /**
     * This method will call when viewpager create fragment and when we go to this fragment from
     * background or another activity, fragment
     * NOT call when we switch between each page in ViewPager
     */
    @Override
    public void onStart() {
        super.onStart();
        if (mIsVisibleToUser) {
            onVisible();
        }
    }

    @Override
    public void onStop() {
        super.onStop();
        if (mIsVisibleToUser) {
            onInVisible();
        }
    }

    /**
     * This method will call at first time viewpager created and when we switch between each page
     * NOT called when we go to background or another activity (fragment) when we go back
     */
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        mIsVisibleToUser = isVisibleToUser;
        if (isResumed()) { // fragment have created
            if (mIsVisibleToUser) {
                onVisible();
            } else {
                onInVisible();
            }
        }
    }

    public void onVisible() {
        Toast.makeText(getActivity(), TAG + "visible", Toast.LENGTH_SHORT).show();
    }

    public void onInVisible() {
        Toast.makeText(getActivity(), TAG + "invisible", Toast.LENGTH_SHORT).show();
    }
}

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

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

Fragment1: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment2: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment3: setUserVisibleHint: isVisibleToUser=false isResumed=false
Fragment1: setUserVisibleHint: isVisibleToUser=true isResumed=false // AT THIS TIME isVisibleToUser=true but fragment still not created. If you do something with View here, you will receive exception
Fragment1: onCreateView
Fragment1: onStart mIsVisibleToUser=true
Fragment2: onCreateView
Fragment3: onCreateView
Fragment2: onStart mIsVisibleToUser=false
Fragment3: onStart mIsVisibleToUser=false

перейти на страницу 2

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

перейти на страницу 3

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

перейти к фону:

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

перейти на передний план

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

демо-проект здесь

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

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

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

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

С уважением.

другое решение опубликовано здесь переопределение setPrimaryItem в pageradapter Крисом Ларсоном почти работал для меня. Но этот метод вызывается несколько раз для каждой установки. Также я получил NPE вид и т. д. во фрагменте как это не готово первые несколько раз этот метод вызывается. Со следующими изменениями это сработало для меня:

private int mCurrentPosition = -1;

@Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
    super.setPrimaryItem(container, position, object);

    if (position == mCurrentPosition) {
        return;
    }

    if (object instanceof MyWhizBangFragment) {
        MyWhizBangFragment fragment = (MyWhizBangFragment) object;

        if (fragment.isResumed()) {
            mCurrentPosition = position;
            fragment.doTheThingYouNeedToDoOnBecomingVisible();
        }
    }
}

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

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

     }
}

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

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

при нажатии с 3-й вкладки - - - - - > 1-я вкладка. Он срабатывал дважды для 2-го фрагмента и для 1-го фрагмента. Я объединил его с методом isResumed ().

    @Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    isVisible = isVisibleToUser;

    // Make sure that fragment is currently visible
    if (!isVisible && isResumed()) {
        // Call code when Fragment not visible
    } else if (isVisible && isResumed()) {
       // Call code when Fragment becomes visible.
    }

}

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

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

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

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

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

взгляд focused view!

это работает для меня

public static boolean isFragmentVisible(Fragment fragment) {
    Activity activity = fragment.getActivity();
    View focusedView = fragment.getView().findFocus();
    return activity != null
            && focusedView != null
            && focusedView == activity.getWindow().getDecorView().findFocus();
}

отметим, что setUserVisibleHint(false) не вызывается при остановке активности / фрагмента. Вам все равно нужно будет проверить start / stop, чтобы правильно register/unregister любых слушателей/и т. д.

кроме того, вы получите setUserVisibleHint(false) если ваш фрагмент начинается в невидимом состоянии; вы не хотите, чтобы unregister там, так как вы никогда не регистрировались раньше в этом случае.

@Override
public void onStart() {
    super.onStart();

    if (getUserVisibleHint()) {
        // register
    }
}

@Override
public void onStop() {
    if (getUserVisibleHint()) {
        // unregister
    }

    super.onStop();
}

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);

    if (isVisibleToUser && isResumed()) {
        // register

        if (!mHasBeenVisible) {
            mHasBeenVisible = true;
        }
    } else if (mHasBeenVisible){
        // unregister
    }
}

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

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

мое решение было сделать проверку в onResume() метод. Я хотел вызвать определенный метод ' foo ()', когда фрагмент 8 был текущим фрагментом просмотра пейджеров.

@Override
public void onResume() {
    super.onResume();
    if(viewPager.getCurrentItem() == 8){
        foo();
        //Your code here. Executed when fragment is seen by user.
    }
}

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

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

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

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

        if (hidden) {

        }else
        {}
    }

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

mContext.getWindow().getDecorView().isShown() //boolean

Я переопределил метод Count связанного FragmentStatePagerAdapter и вернул ему общее количество минус количество страниц для скрытия:

 public class MyAdapter : Android.Support.V13.App.FragmentStatePagerAdapter
 {   
     private List<Fragment> _fragments;

     public int TrimmedPages { get; set; }

     public MyAdapter(Android.App.FragmentManager fm) : base(fm) { }

     public MyAdapter(Android.App.FragmentManager fm, List<Android.App.Fragment> fragments) : base(fm)
     {
         _fragments = fragments;

         TrimmedPages = 0;
     }

     public override int Count
     {
         //get { return _fragments.Count; }
         get { return _fragments.Count - TrimmedPages; }
     }
 }

Итак, если в ViewPager изначально добавлено 3 фрагмента, и только первые 2 должны отображаться до тех пор, пока не будет выполнено какое-либо условие, переопределите счетчик страниц, установив TrimmedPages в 1, и он должен показывать только первые две страницы.

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

Comments

    Ничего не найдено.