原創文章,轉載請聯絡作者
前言
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) {
}
}
複製程式碼
結語
以上