如何确定何时在ViewPager中可见碎片

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

例如,我有2个片段与ViewPagerFragmentPagerAdapter 。 第二个片段仅适用于授权用户,我需要让用户在片段变得可见时使用警报对话框login。

ViewPager创build第二个片段时,第一个是可见的,以便caching第二个片段,并使其在用户开始滑动时可见。

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

如何才能做到这一点?

更新 :Android支持库(修订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 Android Support Library pre-r11 ,则预caching片段也会处于“可见”状态(实际上不可见)。

我重写:

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

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

如何确定何时在ViewPager中可见碎片

你可以通过在你的Fragment覆盖setUserVisibleHint来完成以下工作:

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

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

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

所以如果你想在Fragment visible时候更新一些东西,把你的更新函数放在onCreate()setUserVisibleHint()

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

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

另一个问题!visiblesetUserVisibleHint !visible setUserVisibleHint被调用1)当你离开片段屏幕时2)在它被创build之前,当你第一次切换到分片屏幕时。

解:

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

说明:

fragmentResumefragmentVisible :确保onCreateView() myUIUpdate()仅在创build并显示片段时调用,而不是在继续时调用。 当你在第一个选项卡时,它也解决了问题,第二个选项卡即使不可见也被创build。 这解决了这个问题,并检查当onCreate时,碎片屏幕是否可见。

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

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

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

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

为此重写Fragment.onHiddenChanged()

public void onHiddenChanged(boolean hidden)

当片段的隐藏状态(由isHidden()返回)已更改时调用。 片段开始不隐藏; 这将被称为每当片段改变状态。

参数
hiddenboolean :如果片段现在隐藏,则为真;如果不可见,则为false。

我发现onCreateOptionsMenuonPrepareOptionsMenu方法仅在片段真正可见的情况下被调用。 我找不到像这些行为的任何方法,我也尝试OnPageChangeListener但它不适合的情况下,例如,我需要一个variables在onCreate方法初始化。

所以这两个方法可以用于解决这个问题,特别是对于短期和短期工作。

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

问候。

另一个解决scheme在这里重写了由kris larson在pageradapter中的setPrimaryItem几乎为我工作。 但是这个方法在每个设置中被多次调用。 我还从视图中得到了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()) { } } 
 public abstract class FragmentHelpLoadDataWhenVisible extends Fragment { @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,Bundle savedInstanceState) { if(getUserVisibleHint()){ // fragment is visible loadData(); } return super.onCreateView(inflater, container, savedInstanceState); } @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser && isResumed()) { // fragment is visible and have created loadData(); } } public void loadData(){ // data for fragment when it visible here } } 

你需要setUserVisibleHintonCreateView loadData() ,因为setUserVisibleHint可以在onCreateView之前调用,所以如果我们总是在setUserVisibleHint loadData() ,我们可以得到NullPointerException

例如,我的viewpager有3个选项卡(Tab1,Tab2,Tab3)。
我第一次去这个viewpager时,函数会按顺序调用

 Tab1 => setUserVisibleHint (isVisibleToUser = false) (isResumed = false) Tab2 => setUserVisibleHint (isVisibleToUser = false) (isResumed = false) Tab1 => setUserVisibleHint (isVisibleToUser = true) (isResumed = false) Tab1 => onCreateView (getUserVisibleHint() = true) Tab2 => onCreateView (getUserVisibleHint() = false) 

在这种情况下, loadData()被调用到Tab1 onCreateView中(我们不会得到任何NullPointerException

然后我到Tab2,函数会按照顺序调用

 Tab3 => setUserVisibleHint (isVisibleToUser = false) (isResumed = false) Tab1 => setUserVisibleHint (isVisibleToUser = false) (isResumed = true) Tab2 => setUserVisibleHint (isVisibleToUser = true) (isResumed = true) Tab3 => onCreateView (getUserVisibleHint() = false) 

在这种情况下, loadData()Tab2 setUserVisibleHint中被调用,就像我们所期望的那样

请注意, 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 } } 

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

定时器总是在用户看到片段之前开始。 这是因为片段中的onResume()方法在我们可以看到片段之前被调用。

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

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

希望这可以帮助。 我看到这个问题popup很多。 这似乎是我见过的最简单的解决scheme。 许多其他人不适合较低的API等。

我遇到过同样的问题。 ViewPager执行其他片段生命周期事件,我无法改变这种行为。 我用碎片和可用的animation写了一个简单的寻呼机。 SimplePager

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

setUserVisibleHint()是不够的,我们已经检测到需要解决的3个不同的情况( onAttach()被提及,以便知道何时可以使用演示者):

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

     setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null onAttach() ... onResume() 
  2. 已经创build片段,并按下主页button。 当将应用程序恢复到前台时,这被称为:

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

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

重写setUserVisibleHint()本身无助于查找当前的可见片段。

从第三个标签—–>第一个标签点击。 它为第二个片段和第一个片段触发两次。 我把它与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. } } 

在片段中使用GestureDetector.OnGestureListenerand ,当发生这种情况时,您将知道“this”片段是可见的

在片段中使用手势检测器

我在这里处理可见状态: 检查片段目前是否可见或不在android? 。 我将Fragment的开关types拆分为三种方式,如果嵌套使用,则在其父Fragment中处理子Fragment的可见状态。 它为我工作。

我覆盖了关联的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,并且只有前两个片段应显示,直到满足某些条件,则通过将TrimmedPages设置为1来覆盖页面计数,并且它应该只显示前两个页面。

这对于页面来说很好,但是对于开始或者中间的页面不会有帮助(尽pipe有很多方法可以做到这一点)。