提供一種Fragment可見性改變的監測方案

AiLo發表於2018-06-25

原創文章,轉載請聯絡作者

前言

Fragment,這個讓人又愛又恨“碎片”。
使用它可以讓專案更加輕便--我們可以將功能分割、複用,但其複雜的生命週期和Transaction事務,在極端操作【某些測試人員有一手絕活,三指甚至六指同時觸屏亂彈】下會出現一些不可預期的錯誤--Fragment巢狀Fragment,橫豎屏切換等等。
但無論怎樣,面對解決問題,才是關鍵。這篇文章就是針對Fragment監測可見狀態改變,提供一種解決方案。

Fragment可見性解析

首先,要說明一下,這裡的可見性就是對使用者來說看的見。不僅僅是介面位於頂層那種常規情況,而是即便介面上還存在一層透明介面或是對話方塊,那麼依然判定其對使用者可見,為visible
接下來會分析在特定互動環境下,Fragment內部被觸發的方法。

onResume

Fragment是不能單獨存在的,它所在的檢視樹中,往下追溯,根部一定是一個Activity。在原始碼中,onResume()方法的描述很有意思。

/**
     * 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;
    }
複製程式碼

一般情況下,對使用者可見時觸發。繫結在依賴的Activity生命週期裡

也就是說,一般這個方法,會在可見並且正在活躍時被呼叫。但說到底,還是個“窩裡造”,生命週期完全依賴於父容器----也一定依賴於根Activity
那麼不一般的情況下呢?
有這麼一個例子,在進入一個Activity介面時,直接呼叫了beginTransaction().hide(Fragment)方法。那麼使用者一開始就不會看到這個介面,但生命週期確實也走到了onResume。由此可知,可見性的判斷不能只依賴於這一個方法的判斷。

onHiddenChanged

這個方法在使用beginTransaction().hide(Fragment)會被呼叫,而且是在onResume之前。
先來看看原始碼裡的描述。

 /* @param hidden True if the fragment is now hidden, false otherwise.
     */
    public void onHiddenChanged(boolean hidden) {
    }
複製程式碼

這個方法會回撥出來一個引數,true的時候表示隱藏了,false表示可見。在可見性改變時被呼叫。
這裡要注意一下這個布林值的定義!

setUserVisibleHint

ViewPager搭配Fragment,也是常見的互動模式了。此時左右滑動時,這個方法會被觸發。但有一點要說明一下,當ViewPager初始化時,Fragment相應的生命週期裡。setUserVisibleHint方法是走在Fragment的onCreate之前的。

以上幾個方法,就是常見的互動下,會被觸發的方法了。可見性的監測,主要也依賴於這個方法的相互配合。
這裡還需要說明一下,可見性的監測,監測的是
“改變”*。也就是當Fragment被建立出來時,不會觸發監測方法,不管它是可見還是不可見的狀態。
*

程式碼實現

在BaseFragment內,提供了一個onVisibleToUserChanged(boolean isVisibleToUser)方法作為內部回撥。引數isVisibleToUser如字面所示,True表示可見,false不可見。當你需要在介面不可見,取消網路請求或是釋放一些東西,你就可以使用此方案。
程式碼實現相當簡單,就是一連串邏輯程式碼而已。只是在onResume方法裡,需要判斷一下是否已經觸發了onHiddenChanged或是setuserVisibleHint方法。
程式碼很短,不到100行。這裡直接貼出來。不方便的小可愛們,可以直接去GitHub地址.如果你喜歡的話,不妨點個贊吧。

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 = false
    
    override 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 fun setVisibleToUser(isVisibleToUser: Boolean) {
        if (!isOnCreateView) {
            return
        }
        if (isVisibleToUser == isVisibleToUsers) {
            return
        }
        isVisibleToUsers = isVisibleToUser
        onVisibleToUserChanged(isVisibleToUsers)
    }

    protected open fun onVisibleToUserChanged(isVisibleToUser: Boolean) {
    }
}
複製程式碼

結語

以上

提供一種Fragment可見性改變的監測方案

相關文章