Original article, reprint please contact the author

preface

Fragment, love or hate Fragment. Using it makes projects lighter — we can split and reuse functionality, but the complexity of the lifecycle and Transaction is extreme. There are some unexpected errors with three or even six fingers -Fragment nested Fragment, vertical/horizontal switch, etc. But either way, facing the problem is the key. This article provides a solution for fragments to detect visible state changes.

Fragment visibility parsing

First, it should be noted that visibility is what the user sees. It’s not just the normal case where the interface is at the top, but even if there is a layer of transparent interface or dialog box on the interface, it’s still visible to the user. The next step is to examine the methods that are triggered inside the Fragment in a particular interaction environment.

onResume

A Fragment cannot exist on its own. If you look down the view tree, the root of the Fragment must be an Activity. The onResume() method has an interesting description in the source code.

/**
     * Called when the fragment is visible to the user and actively running.
     * This is generally
     * tied to {@link Activity#onResume() Activity.onResume} of the containing
     * Activity's lifecycle. */ @CallSuper public void onResume() { mCalled = true; }Copy the code

Generally, this parameter is triggered when it is visible to the user. Bound to the dependent Activity lifecycle

That is, normally this method will be called when it is visible and active. At the end of the day, however, it is a “nest”, with a life cycle entirely dependent on the parent container —- and definitely on the root Activity. What about the unusual case? In one example, the beginTransaction().hide(Fragment) method is called directly when entering an Activity screen. The user would not have seen the interface in the first place, but the life cycle did go onResume. It follows that the judgment of visibility cannot rely only on the judgment of this method.

onHiddenChanged

This method is called when beginTransaction().hide(Fragment) is used, and before onResume. Take a look at the description in the source code.

 /* @param hidden True if the fragment is now hidden, false otherwise.
     */
    public void onHiddenChanged(boolean hidden) {
    }
Copy the code

This method returns an argument, true meaning hidden, false meaning visible. Called when visibility changes. Notice the definition of this Boolean here!

setUserVisibleHint

ViewPager with fragments is also a common interaction mode. This method is triggered when you swipe left or right. One thing to note, however, is that when the ViewPager is initialized, the Fragment’s corresponding life cycle is. The setUserVisibleHint method precedes the Fragment’s onCreate.

These are the methods that are triggered in common interactions. Visibility monitoring, too, depends primarily on the synergy of this approach. It should also be noted that visibility monitoring monitors “change” *. That is, when a Fragment is created, no monitoring method is triggered, whether it is visible or invisible. *

Code implementation

In BaseFragment, an onVisibleToUserChanged(Boolean isVisibleToUser) method is provided as an internal callback. The isVisibleToUser argument is literal, True means visible, false not visible. You can use this scheme when you need to be invisible, cancel a network request or release something. The code implementation is fairly simple, just a bunch of logical code. Only in the onResume method, you need to check whether the onHiddenChanged or setuserVisibleHint method has been triggered. The code is short, less than 100 lines. I’ll post it right here. For those who don’t, go directly to GitHub. If you like it, give it a thumbs up.

abstract class BaseFragment : Fragment(){
    lateinit var mRootView: View
    private var isVisibleToUsers = false
    private var isOnCreateView = false
    private var isSetUserVisibleHint = false
    private var isHiddenChanged = false
    private var isFirstResume = falseoverride fun onCreateView(inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle?) : View? { isOnCreateView =true
        mRootView = LayoutInflater.from(activity).inflate(getResId(), null, false)
        return mRootView
    }

    abstract fun getResId(): Int

    override fun onResume() {
        super.onResume()
        if(! isHiddenChanged && ! isSetUserVisibleHint) {if (isFirstResume) {
                setVisibleToUser(true)}}if(isSetUserVisibleHint || (! isFirstResume && ! isHiddenChanged)) { isVisibleToUsers =true
        }
        isFirstResume = true
    }

    override fun onPause() {
        super.onPause()
        isHiddenChanged = false
        isSetUserVisibleHint = false
        setVisibleToUser(false)
    }
    
    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        isSetUserVisibleHint = true
        setVisibleToUser(isVisibleToUser)
    }

    override fun onHiddenChanged(hidden: Boolean) {
        super.onHiddenChanged(hidden)
        isHiddenChanged = true
        setVisibleToUser(! hidden) } private funsetVisibleToUser(isVisibleToUser: Boolean) {
        if(! isOnCreateView) {return
        }
        if (isVisibleToUser == isVisibleToUsers) {
            return
        }
        isVisibleToUsers = isVisibleToUser
        onVisibleToUserChanged(isVisibleToUsers)
    }

    protected open fun onVisibleToUserChanged(isVisibleToUser: Boolean) {
    }
}
Copy the code

conclusion

The above