Check if fragment is currently visible or no

2019-01-17 12:19发布

问题:

I know there are lots of similar questions in StackOverflow but my question is little different.

I have nested hierarchy of Fragments like in below structure:

                                  Activity
                                     |
                                     |
                                 AFragment
                                     |
                                (ViewPager)
                                 |       |         
                                 |       |         
                         BFragment       BFragment  .....
                            |                       
                       (ViewPager)                       
                        |       |                         
                        |       |                         
                 CFragment     CFragment  ...
                     |
                (ViewPager)                       
                  |     |                              
                  |     |                        
           DFragment   DFragment ...

Now i want to know that whether DFragment is showing to user or not?

I tried lots of solution from StackOverflow but couldn't get sucess.

What i tried is:

I tried setUserVisibleHint() but it returns truefor multiple DFragment in above hierarchy which is a cause of ViewPager

I also tried from these links: link1, link2, link3 and so on... but did not got actual solution.

Waiting for help. Thank you.

UPDATE

Adapter Class

class ViewPagerAdapter extends FragmentPagerAdapter {
        private final List<Fragment> mFragmentList = new ArrayList<>();
        private final List<String> mFragmentTitleList = new ArrayList<>();

        public ViewPagerAdapter(FragmentManager manager) {
            super(manager);
        }

        @Override
        public Fragment getItem(int position) {
            return mFragmentList.get(position);
        }

        @Override
        public int getCount() {
            return mFragmentList.size();
        }

        public void addFragment(Fragment fragment, String title) {
            mFragmentList.add(fragment);
            mFragmentTitleList.add(title);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return mFragmentTitleList.get(position);
        }
    }

回答1:

You can check, whether the View that fragment inflated is visible on the device's screen:


    Fragment fragment = ... // retrieve from `ViewPager`'s adapter.
    View fragmentRootView = fragment.getView();
    if (fragmentRootView != null && fragmentRootView.getGlobalVisibleRect(new Rect())) {
        // fragment is visible
    } else {
        // fragment is not visible
    }

getGlobalVisibleRect() will return true if part of the view is visible at the root level.



回答2:

try this

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

        }
        else
        {       

        }
    }


回答3:

You can override setUserVisibleHint() in each fragment where you want to check whether it is visible or not with a additional flag. For an example

boolean isVisited = false;

@Override
public void setUserVisibleHint(boolean isVisibleToUser) 
{
    super.setUserVisibleHint(isVisibleToUser);

    if (isVisibleToUser && !isVisited ) 
    {
         isVisited = true;
    }
    else if(isVisited)
    {
        // this fragment is already in front of user
    }
}

I was having similar problem. Once I was need to load data from server to fragment only when it was in-front of user. I solved it as above.

Hope it will help you as well.



回答4:

Give this a try..

The isVisible() adds and extra layer of visibility check.

    ViewPager viewPager=(ViewPager)findViewById(R.id.view_pager); 

    final Fragment[] fragments={new DFragment(),new MyFragment()};  //add all the other fragment objects present in the view pager......

    viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
        @Override
        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

        }

        @Override
        public void onPageSelected(int position) {
            if(fragments[position].isVisible()){
                //Cool stuff to do.
            }
        }

        @Override
        public void onPageScrollStateChanged(int state) {

        }
    });


回答5:

As far as I have tried I think this will work Please try and let me know. Do something like this in your activity.


public boolean isFragmentVisible(SwipeAdapter swipeAdapter) {
        SwipeAdapter swipeAdapterA = fragmentA.getViewPagerAdapter();
        BFragment bFragment = (BFragment) swipeAdapterA.getFragment(0);
        if (bFragment != null) {
            SwipeAdapter swipeAdapterB = bFragment.getViewPagerAdapter();
            CFragment cFragment = (CFragment) swipeAdapterA.getFragment(0);
            if (cFragment != null) {
                SwipeAdapter swipeAdapterC = cFragment.getViewPagerAdapter();
                DFragment dFragment = (DFragment) swipeAdapterA.getFragment(0);
                if (dFragment != null) {
                    return dFragment.isFragmentVisible();
                }
            }
        }
        return false;
    }

Use this class as your viewpager adapter

  class SwipeAdapter extends FragmentPagerAdapter {
        private Map<Integer, String> mFragmentTags;
        private FragmentManager mFragmentManager;

        private String mPagetile[];

        public SwipeAdapter(FragmentManager fm, Context context) {
            super(fm);
            mPagetile = context.getResources().getStringArray(R.array.pageTitle);
            mFragmentManager = fm;
            mFragmentTags = new HashMap<>();
        }

        @Override
        public Fragment getItem(int position) {
            switch (position) {
                case 0:
                    return new "Your_fragment";
                default:
                    break;
            }
            return null;
        }

        @Override
        public Object instantiateItem(ViewGroup container, int position) {
            Object obj = super.instantiateItem(container, position);
            if (obj instanceof Fragment) {
                Fragment f = (Fragment) obj;
                String tag = f.getTag();
                mFragmentTags.put(position, tag);
            }
            return obj;
        }

        public Fragment getFragment(int position) {
            String tag = mFragmentTags.get(position);
            if (tag == null)
                return null;
            return mFragmentManager.findFragmentByTag(tag);
        }

        @Override
        public CharSequence getPageTitle(int position) {
            return mPagetile[position];
        }

        @Override
        public int getCount() {
            return "Number of fragments";
        }
    }

Now in your DFragment do this

 boolean isVisible=false;

        @Override
        public void setMenuVisibility(boolean menuVisible) {
            super.setMenuVisibility(menuVisible);
            isVisible=menuVisible;
        }

        public boolean isFragmentVisible(){
            return isVisible;
        }

Let me know if it worked if not let me know the reason.



回答6:

We can easily map which fragment is now visible using mViewPager.getCurrentItem() method. This method gives int value number from which we can find the current fragment.

-- For access and get this method result we have two options:

  1. make Static viewPager instance in activity and use wherever we want
  2. again make viewPager instance in fragment (findViewById())

and using one from above two methods getCurrentItem() in fragment to find which fragment is visible now.

For example,

  1. In MainActivity (activity which is used for ViewPager) define

public static ViewPager mViewPager;

  • In Fragment, MainActivity.mViewPager.getCurrentItem() to get current fragment

Also, method that shows which fragment is visible:

public void whichIsVisible() {
    String msg = "";
    int curFragNo = MainActivity.mViewPager.getCurrentItem();
    switch (curFragNo) {
        case 0:
            msg = "AFragment is visible.";
            break;
        case 1:
            msg = "BFragment is visible.";
            break;
        case 2:
            msg = "CFragment is visible.";
            break;
        case 3:
            msg = "DFragment is visible.";
            break;
    }
    Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
}

(2) For non-static use,

  • get viewPager instance in fragment by below code,

viewPager=(ViewPager) view.getRootView().findViewById(R.id.container);

and then

viewPager.getCurrentItem() to get index and find current visible fragment as per use.



回答7:

The most simplest sloution might be. In onCreate of your DF fragment store a boolean in preferences. i.e Key = is_fragment_visible_to_user . And change it to true. And in onStop or onDestroy (whichever fills your requirement) change, it's value to false. With this, you can check it easily by accessing the preferences value. In the case of multiple instances, you can store the tag with the another key.



回答8:

When I had a similar problem I solved it by using this hacky method 'going under the hood' like

Inside your FragmentPagerAdapter(s) add this function.

public Fragment getActiveFragment(ViewPager container, int position) {
        String name = makeFragmentName(container.getId(), position);
        return fm.findFragmentByTag(name);
    }

    private static String makeFragmentName(int viewId, int index) {
        return "android:switcher:" + viewId + ":" + index;
    }

It is not the most graceful solution but as long as it works

CAUTION

This is a private method internal to ViewPager that could change at any time or for any OS version.



回答9:

Although the past for so long, but I still want to give my program, because I found that get visible state of Fragment is indeed not easy. Based on your request, you need to solve these three questions:

  • How to use FragmentPagerAdapter or FragmentStatePagerAdapter
  • How to determine the visible state of Fragment
  • How to handle when the Fragment is nested

At first, i defined a base Adapter help me to get the Fragment in ViewPager.If you Fragment's position and type is changeable in ViewPager, you must use public int getItemPosition(Object object) method and public Fragment getItem(int position) method to get the right position.

/**
 * Created by Kilnn on 2017/7/12.
 * A abstract FragmentStatePagerAdapter which hold the fragment reference.
 */
public abstract class SmartFragmentStatePagerAdapter extends FragmentStatePagerAdapter {

    private SparseArray<WeakReference<Fragment>> registeredFragments = new SparseArray<>();

    public SmartFragmentStatePagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment fragment = (Fragment) super.instantiateItem(container, position);
        registeredFragments.put(position, new WeakReference<>(fragment));
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        registeredFragments.remove(position);
        super.destroyItem(container, position, object);
    }

    public Fragment getRegisteredFragment(int position) {
        WeakReference<Fragment> reference = registeredFragments.get(position);
        return reference != null ? reference.get() : null;
    }
}

At then ,i define a base Fragment to handle the visible state.But there is a little flaw is that you must specify same switch type for one set of fragment .

import android.support.annotation.IntDef;
import android.support.v4.app.Fragment;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * Created by Kilnn on 2017/7/12.
 * A smart fragment know itself's visible state.
 */
public abstract class SmartFragment extends Fragment {

    private boolean isFragmentVisible;

    @Override
    public void onResume() {
        super.onResume();
        int switchType = getSwitchType();
        if (switchType == ATTACH_DETACH) {
            notifyOnFragmentVisible();
        } else if (switchType == SHOW_HIDE) {
            if (!isHidden()) {
                notifyOnFragmentVisible();
            }
        } else if (switchType == VIEW_PAGER) {
            //If the parent fragment exist and hidden when activity destroy,
            //when the activity restore, The parent Fragment  will be restore to hidden state.
            //And the sub Fragment which in ViewPager is also be restored, and the onResumed() method will callback.
            //And The sub Fragment's getUserVisibleHint() method will return true  if it is in active position.
            //So we need to judge the parent Fragment visible state.
            if (getUserVisibleHint() && isParentFragmentVisible()) {
                notifyOnFragmentVisible();
            }
        }
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        int switchType = getSwitchType();
        if (switchType == VIEW_PAGER) {
            if (isVisibleToUser) {
                //If ViewPager in ViewPager , the sub ViewPager will call setUserVisibleHint before onResume
                if (isResumed()) {
                    notifyOnFragmentVisible();
                }
            } else {
                notifyOnFragmentInvisible();
            }
        }
    }

    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        int switchType = getSwitchType();
        if (switchType == SHOW_HIDE) {
            if (hidden) {
                notifyOnFragmentInvisible();
            } else {
                //Just judge for safe
                if (isResumed()) {
                    notifyOnFragmentVisible();
                }
            }
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        notifyOnFragmentInvisible();
    }

    private boolean isParentFragmentVisible() {
        Fragment parent = getParentFragment();
        if (parent == null) return true;
        if (parent instanceof SmartFragment) {
            return ((SmartFragment) parent).isFragmentVisible();
        } else {
            //TODO May be can't get the correct visible state if parent Fragment is not SmartFragment
            return parent.isVisible();
        }
    }

    public boolean isFragmentVisible() {
        // Don't judge the state of the parent fragment,
        // because if the parent fragment visible state changes,
        // you must take the initiative to change the state of the sub fragment
//        return isFragmentVisible && isParentFragmentVisible();
        return isFragmentVisible;
    }

    public void notifyOnFragmentVisible() {
        if (!isFragmentVisible) {
            onFragmentVisible();
            isFragmentVisible = true;
        }
    }

    public void notifyOnFragmentInvisible() {
        if (isFragmentVisible) {
            onFragmentInvisible();
            isFragmentVisible = false;
        }
    }

    /**
     * If this method callback, the Fragment must be resumed.
     */
    public void onFragmentVisible() {

    }

    /**
     * If this method callback, the Fragment maybe is resumed or in onPause().
     */
    public void onFragmentInvisible() {

    }

    /**
     * Fragments switch with attach/detach(replace)
     */
    public static final int ATTACH_DETACH = 0;

    /**
     * Fragments switch with show/hide
     */
    public static final int SHOW_HIDE = 1;

    /**
     * Fragments manage by view pager
     */
    public static final int VIEW_PAGER = 2;

    @Retention(RetentionPolicy.SOURCE)
    @IntDef({ATTACH_DETACH, SHOW_HIDE, VIEW_PAGER})
    public @interface SwitchType {
    }

    @SwitchType
    public abstract int getSwitchType();

}

At last, hand the sub Fragment visible state in the parent Fragment if nested use.

public class ParentFragment extends SmartFragment{

    ....

    @Override
    public void onFragmentVisible() {
        super.onFragmentVisible();
        SmartFragment fragment = (SmartFragment) mAdapter.getRegisteredFragment(mViewPager.getCurrentItem());
        if (fragment != null) {
            fragment.notifyOnFragmentVisible();
        }
    }


    @Override
    public void onFragmentInvisible() {
        super.onFragmentInvisible();
        SmartFragment fragment = (SmartFragment) mAdapter.getRegisteredFragment(mViewPager.getCurrentItem());
        if (fragment != null) {
            fragment.notifyOnFragmentInvisible();
        }
    }

    ...

}

I have test with attach/detach, show/hide ,and three layers ViewPager nested. It work fine. Maybe I should have done more testing, but for me it was enough.



回答10:

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

original post