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

原創文章,轉載請聯繫做者java

前言

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

Fragment可見性解析

首先,要說明一下,這裏的可見性就是對用戶來講看的見。不單單是界面位於頂層那種常規狀況,而是即使界面上還存在一層透明界面或是對話框,那麼依然斷定其對用戶可見,爲visible
接下來會分析在特定交互環境下,Fragment內部被觸發的方法。github

onResume

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

/**
     * 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。由此可知,可見性的判斷不能只依賴於這一個方法的判斷。app

onHiddenChanged

這個方法在使用beginTransaction().hide(Fragment)會被調用,並且是在onResume以前。
先來看看源碼裏的描述。ide

/* @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以前的。ui

以上幾個方法,就是常見的交互下,會被觸發的方法了。可見性的監測,主要也依賴於這個方法的相互配合。
這裏還須要說明一下,可見性的監測,監測的是
「改變」*。也就是當Fragment被建立出來時,不會觸發監測方法,無論它是可見仍是不可見的狀態。
*spa

代碼實現

在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) {
    }
}
複製代碼

結語

以上

相關文章
相關標籤/搜索