首页 文章

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

提问于
浏览
653

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

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

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

因此, onResume() 事件在第二个片段变为可见之前就被触发了 . 那个's why I'm试图找到一个事件,当第二个片段变得可见时,该事件会在适当的时刻显示一个对话框 .

如何才能做到这一点?

21 回答

  • 2

    为此覆盖Fragment.onHiddenChanged() .

    public void onHiddenChanged(boolean hidden)当片段的隐藏状态(由isHidden()返回时)发生更改时调用 . 片段开始不隐藏;只要片段从那里改变状态,就会调用它 . 参数hidden - 布尔值:如果片段现在被隐藏则为True,如果片段不可见则为false .

  • 10

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

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

  • 483

    UPDATE :Android支持库(第11版)终于fixed the user visible hint issue,现在如果您使用片段支持库,那么您可以安全地使用 getUserVisibleHint() 或覆盖 setUserVisibleHint() 来捕获gorn的答案所描述的更改 .

    UPDATE 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 ,则预缓存的片段也会置于"visible"状态(实际上不可见) .

    我覆盖:

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

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

  • 4

    要检测 ViewPager 中的 Fragment 可见,我确定 only using 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();
        }
    }
    

    EXPLANATION 你可以仔细检查下面的logcat然后我想你可能知道为什么这个解决方案会起作用

    First launch

    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
    

    Go to page2

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

    Go to page3

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

    Go to background:

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

    Go to foreground

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

    DEMO project here

    希望它有所帮助

  • 2

    我们有一个MVP的特例,其中片段需要通知演示者视图已变为可见,并且演示者由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);
    }
    
  • 117

    在片段内添加以下代码

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

    我用过它,它有效!

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

    这似乎可以恢复您期望的正常行为 . 它可以很好地按下主页键离开应用程序,然后重新进入应用程序 . 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
    }
    
  • 1

    setUserVisibleHint() 有时会被调用 before onCreateView() ,有时会导致麻烦 .

    要解决这个问题,您需要检查 isResumed() 以及 setUserVisibleHint() 方法 . 但在这种情况下,我意识到 setUserVisibleHint() 被称为 only 如果Fragment被恢复并且可见,而不是在创建时 .

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

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

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

    另一个问题是 setUserVisibleHint 中的 !visible 被调用1)当你离开片段屏幕时2)在它创建之前,当你第一次切换到片段屏幕时 .

    Solution:

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

    Explanation:

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

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

    Update 您可以将所有这些代码放在 BaseFragment code like this并覆盖方法 .

  • 1

    请注意,在活动/片段停止时不会调用 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
        }
    }
    
  • 2

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

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

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

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

    问候 .

  • 2

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

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

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

    使用 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.
        }
    
    }
    
  • 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等不兼容 .

  • 12

    Detecting by 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();
    }
    
  • 535

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

    这对于最后的页面非常有用,但对于开头或中间的页面来说它们确实没什么用处(尽管有很多方法可以做到这一点) .

  • 14
    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);
        }
    }
    
  • 1

    试试这个,这对我有用:

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

相关问题