如何确定 Fragment 何时在 ViewPager 中可见

Posted

技术标签:

【中文标题】如何确定 Fragment 何时在 ViewPager 中可见【英文标题】:How to determine when Fragment becomes visible in ViewPager 【发布时间】:2012-04-18 23:09:37 【问题描述】:

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

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

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

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

如何做到这一点?

【问题讨论】:

"我有 2 个带有 ViewPager 和 FragmentPagerAdapter 的片段。第二个片段仅供授权用户使用,当片段可见时我应该要求使用登录(警报对话框)。" - 恕我直言,那是糟糕的用户体验。因为用户水平滑动而弹出一个对话框会导致我在 Play 商店中给你一星评级。 用“登录”按钮在 TextView 上显示信息会更好吗?对于这种情况,您的解决方案是什么? "数据不显示就不需要加载。" -- 那么你不应该把它放在ViewPager 中。在两页寻呼机中,无论​​您喜欢与否,都会立即加载两个页面。 ViewPager 的用户体验应该是滑动后立即出现内容,而不是稍后。这就是ViewPager 在可见页面之前初始化页面的原因,以帮助确保用户体验。 看来 ViewPager 不够灵活,因为 setOffscreenPageLimit 最小值为 1,所以它不允许关闭缓存:***.com/questions/10073214/…。看不到任何原因,预期的行为(在强制缓存的情况下)是创建片段但在片段变得可见时触发片段的 onResume()。 有点晚了,但是对于任何面临同样问题的人,你可以尝试FragmentViewPager library(我是作者),它处理这个问题并提供了一些额外的功能。有关示例,请查看项目的 GitHub 页面或此 *** answer。 【参考方案1】:

如何确定 Fragment 何时在 ViewPager 中可见

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

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

【讨论】:

感谢今天的 android 支持库更新(第 11 版),最终修复了用户可见提示问题。现在可以安全地为 ViewPager 使用用户可见提示。 我发现 setUserVisibleHint 方法在 onCreateView 被调用之前被调用,这使得跟踪任何初始化变得困难。 @AndroidDev 如果您想在提示为真时运行一些代码,但需要已经初始化视图树,只需将该代码块包装在 isResumed() 中以避免 NPE。对我来说工作正常。 对于 SDK 默认应该提供的东西有这么多不同的 hack 真是荒谬。 setUserVisibleHint 现在已弃用【参考方案2】:

更新:Android 支持库(rev 11)终于fixed the user visible hint issue,现在如果您使用支持库来支持片段,那么您可以安全地使用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 
    @Override
    public void setMenuVisibility(final boolean visible) 
        super.setMenuVisibility(visible);
        if (visible) 
            // ...
        
    
   // ...

捕捉fragment的焦点状态,我认为这是你所说的“可见性”最合适的状态,因为ViewPager中只有一个fragment可以真正将其菜单项与父activity的项放在一起。

【讨论】:

小心使用getActivity(),第一次启动它会为null。 请注意,setUserVisibleHint() 在 FragmentPagerAdapter 中始终可以正常工作。 这个解决方案(以及 gorn 的)有点滞后。在视图页面被快速滑动多次的情况下,此方法将被延迟调用(当片段不再可见/不可见时)。 onCreateOptionsMenu 被调用时,getUserVisibleHint() 的true 被调用,而当setUserVisibleHint 被调用时,似乎还没有创建菜单。最终,当我只想要可见片段中的菜单时,我添加了两个选项菜单。在这方面有什么建议吗? setUserVisibleHint 现在已弃用【参考方案3】:

这似乎恢复了您所期望的正常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

【讨论】:

正是我想要的。解决了 setUserVisibleHintonCreateView 之前被调用的问题,并且如果应用程序进入后台然后进入前台,则 setUserVisibleHint 不会被调用。惊人的!谢谢! 我认为自己调用 onResume 是一个非常糟糕的主意,但除此之外,您需要回答这篇文章的问题的所有内容都在 setUserVisibleHint 函数中! 我宁愿实现一个简单的onVisibleToUser() 方法,并从onResume()setUserVisibleHint(boolean) 调用它,而不是自己调用onResume() 并干扰生命周期回调。否则我认为这种方法效果很好,谢谢! 是的,我同意上述 cmets。一般来说,尽量避免从类中的公共方法调用公共方法。创建一个私有方法并从两个公共方法中调用它。 setUserVisibleHint 已弃用!【参考方案4】:

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

  
);

【讨论】:

此解决方案效果很好,谢谢。对于那些使用 (v4) 片段支持包为旧平台构建的人来说,这应该是推荐的解决方案 值得注意的是,如果您使用 FragmentStatePagerAdapter 并在 getItem() 上实例化一个新的 Fragment 实例,这不会给您预期的结果,因为您只会在新的 Fragment 上设置状态而不是viewpager得到的那个 如果您想在片段变得可见时执行某些操作,则此解决方案很好。它不保持片段的可见状态(意思是,它不知道片段何时不可见)。 我是同类型的问题。请帮我解决问题。我的帖子:***.com/questions/23115283/… @AsafK 例如,如果标签中有ViewPager,那么一次只能看到一个片段,对吧?所以我们不能假设其余的都是隐藏的,除了 onPageSelected 被调用的那个吗?【参考方案5】:

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

要克服这个问题,您需要检查 isResumed() 以及 setUserVisibleHint() 方法内部。但在这种情况下,我意识到 setUserVisibleHint() 仅在 Fragment 恢复且可见时才被调用,而不是在创建时调用。

因此,如果您想在 Fragment 为 visible 时更新某些内容,请将您的更新函数放在 onCreate()setUserVisibleHint() 中:

@Override
public View onCreateView(...)
    ...
    myUIUpdate();
    ...        

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

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

另一个问题!visiblesetUserVisibleHint 中被调用,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:确保仅在片段创建且可见时调用onCreateView() 中的myUIUpdate(),而不是在恢复时调用。它还解决了您在第一个选项卡时的问题,即使第二个选项卡不可见也会创建。这解决了这个问题并检查onCreate 时片段屏幕是否可见。

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

更新 您可以将所有这些代码放入BaseFragment 代码like this 并覆盖方法。

【讨论】:

您的解决方案完美运行,除了在一种情况下,当片段正常打开然后您单击某个按钮将其替换为另一个片段然后单击返回以从后台弹出新片段时,它案例setUserVisibleHint 没有接到电话..!在onCreateView 方法中,fragmentVisiblefalse !?所以片段显示为空..!有什么想法吗?【参考方案6】:

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

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

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

FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)

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

【讨论】:

感谢您的出色解决方案。我一直在寻找这个。伟大的工作 对于 ViewPager2 选项 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 不是默认行为,您需要手动设置。设置此选项时,您的解决方案对我有用。谢谢。 自 2020 年 4 月起,这是更新后的解决方案,效果很好。 @4ntoine 请考虑接受此答案作为正确答案,因为到目前为止这是正确的,并且大多数答案都使用已弃用的 setUserVisibleHint 方法 我认为它也适用于 FragmentStateAdapter。这对任何安卓开发者来说都是一个巨大的解脱。感谢 google 解决了这么久。【参考方案7】:

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

旧答案(已弃用)

要在可见的ViewPager 中检测Fragment,我很确定仅使用 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

希望对你有帮助

【讨论】:

当一个片段有另一个视图寻呼机也包含片段时,它不起作用。 对不起,我现在不能发布答案,因为我没有足够的时间,如果可能的话,请参考我的 git github.com/PhanVanLinh/AndroidViewPagerSkeleton/tree/master。 SubChildContainerFragment 用于检测a fragment has another view pager which also consists fragment。您可以将 SubChildContainerFragmentChildContainerFragment 混合为 1 个类。希望它有所帮助。我稍后会发布完整的答案【参考方案8】:
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);
    

【讨论】:

这应该是公认的答案。也适用于 android.app.Fragment,这是一个巨大的好处。 setUserVisibleHint 已弃用【参考方案9】:

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

【讨论】:

这几乎奏效了,但 NPE 出现问题并被多次调用。在下面发布了替代方案! @kris ***.com/questions/40149039/… @Gober 首次保存 Object 对象,并检查并忽略后续调用相同的 Object 对象,就像 FragmentStateAdapter 在 setPrimaryItem() 方法中所做的那样【参考方案10】:

为此覆盖Fragment.onHiddenChanged()

public void onHiddenChanged(boolean hidden)

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

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

【讨论】:

此方法现已弃用,不能再使用 @bluewhile 你在哪里看到它被弃用了? developer.android.com/reference/android/app/… 我刚刚成功使用了它,文档似乎没有表明它已被弃用。 @bluewhile,在 4.1 (api 16) 中它还没有被弃用。 我使用的是 API 级别 19,并且可以确认虽然这没有被弃用,但它不像宣传的那样工作。例如,当片段被另一个片段隐藏时,不会调用 onHiddenChanged。【参考方案11】:

只有这对我有用! 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() 将始终返回 trueonResume() 似乎足够了,但我们也想支持 ViewPager)。

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

【讨论】:

【参考方案12】:

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

所以这两种方法可以作为一种解决方法来解决这个问题,特别是对于小而短的工作。

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

问候。

【讨论】:

【参考方案13】:

setUserVisibleHint(boolean visible) 现在已弃用所以这是正确的解决方案

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 构造函数的第二个参数传递。

【讨论】:

你是救生员,这是2021年最好的答案,因为以上所有答案都已贬值。【参考方案14】:

另一个解决方案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();
        
    

【讨论】:

不工作。它只调用一次。如果再次回到同一个页面,mCurrentPosition 和 position 是相等的【参考方案15】:

在片段内添加以下代码

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

     

【讨论】:

这仅适用于 ViewPagerFragments。不适用于正常活动的片段。【参考方案16】:

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

单独覆盖 setUserVisibleHint() 并不能帮助找到当前的可见片段。

从第 3 个选项卡单击时 -----> 第 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.
    


【讨论】:

【参考方案17】:

我们有一个 MVP 的特殊情况,即 Fragment 需要通知 Presenter 视图已经可见,而 Presenter 是由 Dagger 在fragment.onAttach() 中注入的。

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

    片段刚刚被创建。系统会进行以下调用:

    setUserVisibleHint() // before fragment's lifecycle calls, so presenter is null
    onAttach()
    ...
    onResume()
    

    已创建片段并按下主页按钮。将应用程序恢复到前台时,这称为:

    onResume()
    

    方向改变:

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

【讨论】:

【参考方案18】:

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

【讨论】:

focusedView 返回 null。 @ATES 是的,我意识到在某些不同的情况下,focusedView 将为空。我会努力寻找新的方法。【参考方案19】:

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

【讨论】:

【参考方案20】:

我用过这个,效果很好!

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

【讨论】:

【参考方案21】:

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

【讨论】:

忘了说,一旦你将子片段添加到父片段集fragment.setUserVisibleHint(true); 并且不要忘记使用 getChildFragmentManager() 而不是 getFragmentManager() 来添加子片段。【参考方案22】:

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

【讨论】:

【参考方案23】:

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

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

我的解决方案是检查onResume() 方法。当片段 8 是视图寻呼机当前片段时,我想调用某个方法 'foo()'。

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

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

【讨论】:

我否决了这个答案,因为它使用了“幻数”,这意味着它只适用于 Piyush 的应用程序。【参考方案24】:

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

在您的 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);

【讨论】:

【参考方案25】:

可能会很晚。这对我有用。我稍微更新了@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;
    

【讨论】:

【参考方案26】:

我重写了关联的 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 来覆盖页数,它应该只显示前两个页面。

这适用于结尾的页面,但对开头或中间的页面没有真正的帮助(尽管有很多方法可以做到这一点)。

【讨论】:

以上是关于如何确定 Fragment 何时在 ViewPager 中可见的主要内容,如果未能解决你的问题,请参考以下文章

Android tab导航的几种方法:ActionBar tab +fragment,Viewpager+pagerTitleStrip,开源框架ViewPageIndicator 和 ViewPag

如何知道 Fragment 何时变得不可见

Android:何时/为啥我应该使用 FrameLayout 而不是 Fragment?

在 Activity 重新创建时如何确定 Fragment 恢复?

如何确定控件何时对用户可见?

如何确定何时显示 UICollectionViewCell?