如何确定在ViewPager中何时可以看到片段

问题:ViewPager中的片段onResume()在片段实际可见之前被触发。

例如,我有2个片段ViewPagerFragmentPagerAdapter。第二个片段仅适用于授权用户,我需要在片段可见时要求用户登录(使用警报对话框)。

但是ViewPager在第一个片段可见时创建第二个片段,以便缓存第二个片段,并在用户开始滑动时使其可见。

所以onResume()事件在第二个片段变得可见之前很久就被触发了。这就是为什么我试图找到一个当第二个片段变得可见时触发的事件,以在适当的时刻显示一个对话框。

如何做到这一点?

367299 次浏览

如何确定在ViewPager中何时可以看到片段

您可以通过在Fragment中覆盖setUserVisibleHint来执行以下操作:

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

这是使用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
}});

覆盖Fragment.onHiddenChanged()为此。

public void onHiddenChanged(boolean hidden)

当片段的隐藏状态(由isHidden()返回)发生变化时调用。片段开始时不隐藏;每当片段改变状态时,都会调用它。

参数
hidden-boolean:如果片段现在隐藏,则为true,如果不可见,则为false。

更新:Android支持库(rev 11)最后修复了用户可见提示问题,现在如果您使用片段支持库,那么您可以安全地使用getUserVisibleHint()或覆盖setUserVisibleHint()来捕获gorn答案所描述的更改。

更新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一次只显示一个页面,但如果您在Android Support Library pre-r11中使用FragmentStatePagerAdapter,则预缓存的片段也会被置于“可见”状态(实际上是不可见的)。

我覆盖:

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

为了捕获Framer的焦点状态,我认为这是最合适的“可见性”状态,因为ViewPager中只有一个片段可以将其菜单项与父活动的项放在一起。

我覆盖了关联的FragmentStatePagerAdapter的Count方法,并让它返回总计数减去要隐藏的页数:

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

因此,如果有3个片段最初添加到ViewPager,并且只有前2个片段应该显示,直到满足某些条件,则通过将TrimmedPages设置为1来覆盖页数,并且它应该只显示前两个页面。

这对末尾的页面有好处,但对开头或中间的页面没有帮助(尽管有很多方法可以做到这一点)。

我遇到了同样的问题。ViewPager执行其他片段生命周期事件,我无法更改该行为。我使用片段和可用动画编写了一个简单的寻呼机。SimplePager

我发现onCreateOptionsMenuonPrepareOptionsMenu方法仅在片段真的可见的情况下调用。我找不到任何像这样的方法,我也尝试了OnPageChangeListener,但它不适用于这种情况,例如,我需要在onCreate方法中初始化的变量。

因此,这两种方法可以用作解决这个问题的方法,特别是对于小而短的工作。

我认为,这是更好的解决方案,但不是最好的。我会使用这个,但同时等待更好的解决方案。

问候。

这似乎恢复了您期望的正常onResume()行为。它可以很好地按home键离开应用程序,然后重新进入应用程序。onResume()不会连续调用两次。

@Overridepublic 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 onResumeonResume();}}
@Overridepublic void onResume(){super.onResume();if (!getUserVisibleHint()){return;}
//INSERT CUSTOM CODE HERE}

setUserVisibleHint()有时会被调用之前onCreateView(),有时会导致麻烦。

为了克服这个问题,你需要在setUserVisibleHint()方法中也检查isResumed()。但在这种情况下,我意识到如果Frament是恢复和可见的,而不是在创建时,setUserVisibleHint()会被调用只有

因此,如果你想在碎片为visible时更新一些东西,请将你的更新函数放在onCreate()setUserVisibleHint()中:

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

更新:我仍然意识到myUIUpdate()有时会被调用两次,原因是,如果您有3个选项卡并且此代码在第二个选项卡上,当您第一次打开第一个选项卡时,即使它不可见,也会创建第二个选项卡并调用myUIUpdate()。然后当您滑动到第二个选项卡时,if (visible && isResumed())中的myUIUpdate()被调用,因此,myUIUpdate()可能会在一秒钟内被调用两次。

另一个问题setUserVisibleHint中的!visible,当您离开片段屏幕时和2)在创建之前,当您第一次切换到片段屏幕时,都被称为1)。

解决方案:

private boolean fragmentResume=false;private boolean fragmentVisible=false;private boolean fragmentOnCreated=false;...
@Overridepublic View onCreateView(...){...//Initialize variablesif (!fragmentResume && fragmentVisible){   //only when first time fragment is createdmyUIUpdate();}...}
@Overridepublic void setUserVisibleHint(boolean visible){super.setUserVisibleHint(visible);if (visible && isResumed()){   // only at fragment screen is resumedfragmentResume=true;fragmentVisible=false;fragmentOnCreated=true;myUIUpdate();}else  if (visible){        // only at fragment onCreatedfragmentResume=false;fragmentVisible=true;fragmentOnCreated=true;}else if(!visible && fragmentOnCreated){// only when you go out of fragment screenfragmentVisible=false;fragmentResume=false;}}

说明:

fragmentResumefragmentVisible:确保onCreateView()中的myUIUpdate()仅在创建片段并可见时调用,而不是在恢复时调用。它还解决了当您在第一个选项卡时的问题,即使它不可见,也会创建第二个选项卡。这解决了这个问题并检查了onCreate时片段屏幕是否可见。

fragmentOnCreated:确保Frament不可见,并且在您第一次创建Frament时不会被调用。所以现在这个if子句只有在您从片段中滑出时才会被调用。

更新您可以将所有这些代码放在BaseFragment代码像这样中并覆盖方法。

覆盖FragmentPagerAdapter子类中的setPrimaryItem()。我使用这种方法,效果很好。

@Overridepublic void setPrimaryItem(ViewGroup container, int position, Object object) {// This is what calls setMenuVisibility() on the fragmentssuper.setPrimaryItem(container, position, object);
if (object instanceof MyWhizBangFragment) {MyWhizBangFragment fragment = (MyWhizBangFragment) object;fragment.doTheThingYouNeedToDoOnBecomingVisible();}}

我遇到了这个问题,当我试图让一个计时器触发时,在视图页中的片段在屏幕上供用户查看。

计时器总是在用户看到片段之前启动。这是因为片段中的onResume()方法在我们看到片段之前被调用。

我的解决方案是在onResume()方法中进行检查。我想在片段8是视图寻呼机当前片段时调用某个方法“foo()”。

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

希望有帮助。我经常看到这个问题弹出。这似乎是我见过的最简单的解决方案。很多其他解决方案与较低的API不兼容等。

请注意,在活动/片段停止时没有调用setUserVisibleHint(false)。您仍然需要检查开始/停止以正确register/unregister任何侦听器/等。

此外,如果你的片段以不可见的状态开始,你会得到setUserVisibleHint(false);你不想在那里unregister,因为你以前从未在这种情况下注册过。

@Overridepublic void onStart() {super.onStart();
if (getUserVisibleHint()) {// register}}
@Overridepublic void onStop() {if (getUserVisibleHint()) {// unregister}
super.onStop();}
@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && isResumed()) {// register
if (!mHasBeenVisible) {mHasBeenVisible = true;}} else if (mHasBeenVisible){// unregister}}
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 {
@Overridepublic View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {View view = inflater.inflate(R.layout.fragment_subscribe, container, false);return view;}
@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {// called here}}
@Overridepublic void onViewCreated(View view, @Nullable Bundle savedInstanceState) {super.onViewCreated(view, savedInstanceState);}}

另一个解决方案张贴在这里覆盖setPrimaryItem在页面适配器由kris larson几乎对我有效。但是这个方法对每个设置都被多次调用。此外,我在片段中从视图等中获得了NPE,因为在调用这个方法的前几次还没有准备好。通过以下更改,这对我有效:

private int mCurrentPosition = -1;
@Overridepublic 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();}}}

我在使用FragmentStatePagerAdapters和3标签时遇到了同样的问题。每当单击第一个标签时,我必须显示一个Dilaog,并在单击其他标签时隐藏它。

单独重写setUserVisibleHint()无助于找到当前可见的片段。

从第3个选项卡单击时 -----> 第1个选项卡。它触发了第二个片段和第一个片段两次。我将它与isResumed()方法组合在一起。

    @Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);isVisible = isVisibleToUser;
// Make sure that fragment is currently visibleif (!isVisible && isResumed()) {// Call code when Fragment not visible} else if (isVisible && isResumed()) {// Call code when Fragment becomes visible.}
}

在片段中添加以下代码

@Overridepublic void setMenuVisibility(final boolean visible){super.setMenuVisibility(visible);if (visible && isResumed()){
}}

ViewPager2+FragmentStateAdapter+onResume()(在片段中)解决问题

旧答案(已弃用)

要检测ViewPager可见的Fragment,我很确定只使用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*/@Overridepublic void onStart() {super.onStart();if (mIsVisibleToUser) {onVisible();}}
@Overridepublic 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*/@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);mIsVisibleToUser = isVisibleToUser;if (isResumed()) { // fragment have createdif (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=falseFragment2: setUserVisibleHint: isVisibleToUser=false isResumed=falseFragment3: setUserVisibleHint: isVisibleToUser=false isResumed=falseFragment1: 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 exceptionFragment1: onCreateViewFragment1: onStart mIsVisibleToUser=trueFragment2: onCreateViewFragment3: onCreateViewFragment2: onStart mIsVisibleToUser=falseFragment3: onStart mIsVisibleToUser=false

转到第2页

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

转到第3页

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

转到背景:

Fragment1: onStop mIsVisibleToUser=falseFragment2: onStop mIsVisibleToUser=falseFragment3: onStop mIsVisibleToUser=true

转到前台

Fragment1: onStart mIsVisibleToUser=falseFragment2: onStart mIsVisibleToUser=falseFragment3: onStart mIsVisibleToUser=true

演示项目

希望能有帮助

我们有一个MVP的特殊情况,其中片段需要通知演示者视图已变得可见,并且演示者由Dagger在fragment.onAttach()中注入。

setUserVisibleHint()还不够,我们检测到3种需要解决的不同情况(提到onAttach()是为了让您知道演示者何时可用):

  1. 刚刚创建了片段。系统进行以下调用:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is nullonAttach()...onResume()
  2. Fragment already created and home button is pressed. When restoring the app to foreground, this is called:

    onResume()
  3. Orientation change:

    onAttach() // presenter availableonResume()setUserVisibleHint()

We only want the visibility hint to get to the presenter once, so this is how we do it:

@Nullable@Overridepublic 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;}
@Overridepublic void onResume() {super.onResume();presenter.onResume();
int orientation = getResources().getConfiguration().orientation;if (orientation == lastOrientation) {if (getUserVisibleHint()) {presenter.onViewBecomesVisible();}}lastOrientation = orientation;}
@Overridepublic 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();}

我用了这个,它奏效了!

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

我支持带有子片段的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);}
@Overridepublic void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (!visibilityHintChanged) {setUserVisibleHint(false);}}
@Overridepublic void onResume() {super.onResume();if (getUserVisibleHint() && !isHidden()) {triggerVisibilityChangedIfNeeded(true);}}
@Overridepublic void onHiddenChanged(boolean hidden) {super.onHiddenChanged(hidden);triggerVisibilityChangedIfNeeded(!hidden);}
@Overridepublic void setUserVisibleHint(boolean isVisibleToUser) {super.setUserVisibleHint(isVisibleToUser);visibilityHintChanged = true;if (isVisibleToUser && isResumed() && !isHidden()) {triggerVisibilityChangedIfNeeded(true);} else if (!isVisibleToUser) {triggerVisibilityChangedIfNeeded(false);}}
@Overridepublic void onPause() {super.onPause();triggerVisibilityChangedIfNeeded(false);}
@Overridepublic void onStop() {super.onStop();triggerVisibilityChangedIfNeeded(false);}
protected boolean isReallyVisible() {return visible;}}

一种简单的实现方法是检查用户是否登录到之前中的片段。

在MainActivity中,您可以在关于导航项目方法中执行类似的操作。

 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;

但是,如果您使用的是抽屉菜单,则抽屉中的选择将更改为配置文件,尽管我们没有转到ProfileFrament。

要将选择重置为当前选择,请运行以下代码

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

在版本androidx.fragment:fragment:1.1.0ViewPager2ViewPager中,您可以仅使用onPauseonResume回调来确定当前对用户可见的片段。onResume回调在片段变得可见时调用,onPause在它停止可见时调用。

在ViewPager2的情况下,它是默认行为,但可以轻松地为旧商品ViewPager启用相同的行为。

要在第一个ViewPager中启用此行为,您必须将FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT参数作为FragmentPagerAdapter构造函数的第二个参数传递。

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

注意:setUserVisibleHint()方法和FragmentPagerAdapter带有一个参数的构造函数现在在来自android jetpack的新版本中被弃用。

setUserVisibleHint(布尔可见)现在是已弃用所以这是正确的解决方案

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

在版本androidx.fragment:fragment:1.1.0ViewPager2ViewPager中,您可以使用onPause()onResume()来确定哪个片段当前对用户可见。当片段变得可见时调用onResume(),当它停止可见时调用onPause

要在第一个ViewPager中启用此行为,您必须将FragmentPagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT参数作为FragmentPagerAdapter构造函数的第二个参数传递。

只有这个对我有用!!setUserVisibleHint(...)现在已弃用(我在末尾附上了文档),这意味着大多数其他答案都已弃用;-)

public class FragmentFirewall extends Fragment {/*** Required cause "setMenuVisibility(...)" is not guaranteed to be* called after "onResume()" and/or "onCreateView(...)" method.*/protected void didVisibilityChange() {Activity activity = getActivity();if (isResumed() && isMenuVisible()) {// Once resumed and menu is visible, at last// our Fragment is really visible to user.}}
@Overridepublic void onResume() {super.onResume();didVisibilityChange();}
@Overridepublic void setMenuVisibility(boolean visible) {super.setMenuVisibility(visible);didVisibilityChange();}}

测试并使用NaviagationDrawerisMenuVisible()总是会返回trueonResume()似乎足够了,但我们也想支持ViewPager)。

setUserVisibleHint已弃用。如果重写此方法,传递true时实现的行为应移动到Fragment.onResume(),传递false时实现的行为应移动到Fragment.onPause()

可能很晚。这对我很有用。我稍微更新了@Gobar和@kris Solutions的代码。我们必须更新PagerAdapter中的代码。

每次标签可见并返回其位置时都会调用setPrimaryItem。如果位置相同,则表示我们未移动。如果位置更改且当前位置不是我们单击的标签设置为-1。

private int mCurrentPosition = -1;
@Overridepublic void setPrimaryItem(@NotNull ViewGroup container, int position, @NotNull Object object) {// This is what calls setMenuVisibility() on the fragmentssuper.setPrimaryItem(container, position, object);if (position == mCurrentPosition) {return;}if (object instanceof YourFragment) {YourFragment fragment = (YourFragment) object;if (fragment.isResumed()) {mCurrentPosition = position;fragment.doYourWork();//Update your function}} else {mCurrentPosition = -1;}}

静态编程语言

override fun onHiddenChanged(hidden: Boolean) {super.onHiddenChanged(hidden)
// Your code goes here..}