How to determine when Fragment becomes visible in ViewPager

后端 未结 26 1338
心在旅途
心在旅途 2020-11-22 00:24

Problem: Fragment onResume() in ViewPager is fired before the fragment becomes actually visible.

For example, I have 2 fragments with

相关标签:
26条回答
  • 2020-11-22 00:41

    Override setPrimaryItem() in the FragmentPagerAdapter subclass. I use this method, and it works well.

    @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();
        }
    }
    
    0 讨论(0)
  • 2020-11-22 00:43

    UPDATE: Android Support Library (rev 11) finally fixed the user visible hint issue, now if you use support library for fragments, then you can safely use getUserVisibleHint() or override setUserVisibleHint() to capture the changes as described by gorn's answer.

    UPDATE 1 Here is one small problem with getUserVisibleHint(). This value is by default true.

    // Hint provided by the app that this fragment is currently visible to the user.
    boolean mUserVisibleHint = true;
    

    So there might be a problem when you try to use it before setUserVisibleHint() was invoked. As a workaround you might set value in onCreate method like this.

    public void onCreate(@Nullable Bundle savedInstanceState) {
        setUserVisibleHint(false);
    

    The outdated answer:

    In most use cases, ViewPager only show one page at a time, but the pre-cached fragments are also put to "visible" state (actually invisible) if you are using FragmentStatePagerAdapter in Android Support Library pre-r11.

    I override :

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

    To capture the focus state of fragment, which I think is the most suitable state of the "visibility" you mean, since only one fragment in ViewPager can actually place its menu items together with parent activity's items.

    0 讨论(0)
  • 2020-11-22 00:43

    I encountered the same problem while working with FragmentStatePagerAdapters and 3 tabs. I had to show a Dilaog whenever the 1st tab was clicked and hide it on clicking other tabs.

    Overriding setUserVisibleHint() alone didn't help to find the current visible fragment.

    When clicking from 3rd tab -----> 1st tab. It triggered twice for 2nd fragment and for 1st fragment. I combined it with isResumed() method.

        @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.
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 00:44

    setUserVisibleHint() gets called sometimes before onCreateView() and sometimes after which causes trouble.

    To overcome this you need to check isResumed() as well inside setUserVisibleHint() method. But in this case i realized setUserVisibleHint() gets called only if Fragment is resumed and visible, NOT when Created.

    So if you want to update something when Fragment is visible, put your update function both in onCreate() and setUserVisibleHint():

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

    UPDATE: Still i realized myUIUpdate() gets called twice sometimes, the reason is, if you have 3 tabs and this code is on 2nd tab, when you first open 1st tab, the 2nd tab is also created even it is not visible and myUIUpdate() is called. Then when you swipe to 2nd tab, myUIUpdate() from if (visible && isResumed()) is called and as a result,myUIUpdate() may get called twice in a second.

    The other problem is !visible in setUserVisibleHint gets called both 1) when you go out of fragment screen and 2) before it is created, when you switch to fragment screen first time.

    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:

    fragmentResume,fragmentVisible: Makes sure myUIUpdate() in onCreateView() is called only when fragment is created and visible, not on resume. It also solves problem when you are at 1st tab, 2nd tab is created even if it is not visible. This solves that and checks if fragment screen is visible when onCreate.

    fragmentOnCreated: Makes sure fragment is not visible, and not called when you create fragment first time. So now this if clause only gets called when you swipe out of fragment.

    Update You can put all this code in BaseFragment code like this and override method.

    0 讨论(0)
  • 2020-11-22 00:44

    To detect Fragment in ViewPager visible, I'm quite sure that only using setUserVisibleHint is not enough.
    Here is my solution to check if a fragment is visible or invisible. First when launching viewpager, switch between page, go to another 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();
        }
    }
    

    EXPLANATION You can check the logcat below carefully then I think you may know why this solution will work

    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

    Hope it help

    0 讨论(0)
  • 2020-11-22 00:44

    A simple way of implementing that is checking whether user is logged in before going to the fragment.

    In your MainActivity you may do something like this inside the onNavigationItemSelected method.

     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;
    

    However, if you are using navigation drawer, the selection in the drawer will have changed to Profile though we have not gone to the ProfileFragment.

    To reset the selection to the current selection run the code below

            navigationView.getMenu().getItem(0).setChecked(true);
    
    0 讨论(0)
提交回复
热议问题