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

时间:2012-04-05 07:58:15

标签: android android-fragments android-viewpager

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

例如,我有两个ViewPagerFragmentPagerAdapter的片段。第二个片段仅供授权用户使用,我需要让用户在片段可见时登录(使用警告对话框)。

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

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

如何做到这一点?

25 个答案:

答案 0 :(得分:553)

  

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

您可以通过覆盖setUserVisibleHint

中的Fragment来执行以下操作
public class MyFragment extends Fragment {
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (isVisibleToUser) {
        }
        else {
        }
    }
}

答案 1 :(得分:129)

这似乎可以恢复您期望的正常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
}

答案 2 :(得分:61)

以下是使用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

  }
});

答案 3 :(得分:53)

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个标签,并且此代码位于第二个标签页上,则当您首次打开第一个标签页时,即使它不可见,也会创建第二个选项卡,并调用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;
    }
}

说明:

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

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

<强>更新 您可以将所有这些代码放在BaseFragment代码like this中并覆盖方法。

答案 4 :(得分:24)

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

答案 5 :(得分:23)

要检测Fragment中可见的ViewPager,我确信仅使用 setUserVisibleHint是不够的。
这是我的解决方案,以检查片段是可见还是不可见。首先,当启动viewpager时,在页面之间切换,转到另一个activity / fragment / background / foreground`

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

<强>说明 你可以仔细检查下面的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

DEMO project here

希望有所帮助

答案 6 :(得分:14)

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

答案 7 :(得分:10)

覆盖Fragment.onHiddenChanged()

  

public void onHiddenChanged(boolean hidden)

     

当片段的隐藏状态(由isHidden()返回)发生更改时调用。片段开始没有隐藏;只要片段从那里改变状态,就会调用它。

     

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

答案 8 :(得分:6)

ViewPager2版的ViewPagerandroidx.fragment:fragment:1.1.0中,您可以仅使用onPauseonResume回调来确定用户当前可见的片段。当片段可见时调用onResume回调,当片段不可见时调用onPause

对于ViewPager2,它是默认行为,但可以很容易地为旧商品ViewPager启用相同的行为。

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

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

注意:android jetpack的新版本Fragment中不推荐使用带有一个参数的setUserVisibleHint()方法和FragmentPagerAdapter构造函数。

答案 9 :(得分:5)

这只对我有用!!并且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.
        }
    }

    @Override
    public void onResume() {
        super.onResume();
        didVisibilityChange();
    }

    @Override
    public void setMenuVisibility(boolean visible) {
        super.setMenuVisibility(visible);
        didVisibilityChange();
    }
}

经过测试,也可以与NaviagationDrawer一起使用, 那里isMenuVisible()将始终返回true(并且onResume()似乎足够,但我们也希望支持ViewPager)。

setUserVisibleHint已过时。如果覆盖此方法,则应将传入true时实现的行为移至Fragment.onResume(),并将传入false时实现的行为移至Fragment.onPause()

答案 10 :(得分:4)

我发现onCreateOptionsMenuonPrepareOptionsMenu方法仅在片段确实可见的情况下调用。我找不到任何行为与此类似的方法,我也试过OnPageChangeListener但它不适用于这种情况,例如,我需要在onCreate方法中初始化变量。

因此,这两种方法可用于解决此问题,特别适用于小型和短期工作。

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

问候。

答案 11 :(得分:3)

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

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

在版本androidx.fragment:fragment:1.1.0 ViewPager2 ViewPager 中,您可以仅使用onPause()onResume()确定当前可见的片段为用户。当片段变为可见时,将调用onResume(),而当片段变得不可见时,将调用onPause

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

答案 12 :(得分:2)

另一个解决方案posted here overriding setPrimaryItem in the pageradapter by kris larson几乎对我有用。但是每种设置都会多次调用此方法。此外,我从片段中的视图等获得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();
        }
    }
}

答案 13 :(得分:2)

尝试一下,对我有用:

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

        if (hidden) {

        }else
        {}
    }

答案 14 :(得分: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();
}

答案 15 :(得分:2)

在片段

中添加以下代码
@Override
public void setMenuVisibility(final boolean visible) 
 {
    super.setMenuVisibility(visible);
    if (visible && isResumed()) 
     {

     }
}

答案 16 :(得分:2)

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

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

从第3个标签点击时-----&gt;第一个标签。 它为第二个片段和第一个片段触发了两次。 我将它与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.
    }

}

答案 17 :(得分:1)

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

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

我的解决方案是检查onResume()方法。我想打电话给某种方法&#39; foo()&#39;当片段8是视图寻呼机当前片段时。

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

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

答案 18 :(得分:1)

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

答案 19 :(得分: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
    }
}

答案 20 :(得分:0)

我使用了它,而且有效!

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

答案 21 :(得分:0)

我支持带有子片段的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;
    }
}

答案 22 :(得分: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;

但是,如果您使用的是导航抽屉,尽管我们没有转到ProfileFragment,但抽屉中的选择将更改为Profile。

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

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

答案 23 :(得分:0)

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

每当出现选项卡并返回其位置时,都会调用

setPrimaryItem。如果位置相同,则表示我们不动。如果位置发生变化而当前位置不是我们单击的选项卡,则设置为-1。

private int mCurrentPosition = -1;

@Override
public void setPrimaryItem(@NotNull ViewGroup container, int position, @NotNull Object object) {
    // This is what calls setMenuVisibility() on the fragments
    super.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;
    }
}

答案 24 :(得分:-3)

我覆盖了相关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覆盖页面计数,它应该只显示前两页。 / p>

这对于最后的页面很有用,但对于开头或中间的页面确实没有帮助(尽管有很多方法可以做到这一点)。