「Do.034」探索兼容的Fragment懶加載模式

首發公衆號: 黑客五六七
做者: 賢榆的榆
若是喜歡,請 關注、讚揚、點在看
閱讀時間:1738字 6分鐘

Fragment的懶加載,一般都是在一個Activity中經過ViewPager管理了多個Fragment界面時,會用到的一種模式。當咱們每一個Fragment都很複雜的時候,爲了保證整個Activity的流暢度,咱們一般會將第一個Fragment先加載出來,後面的Fragment在其可見時再加載。每當咱們想到Fragment懶加載的時候一般都是使用setUserVisiable()方法配合onViewCreated方法來作懶加載,可是你會發現當使用ViewPager2+Fragment的時候,它就並不起做用了。因爲種種的歷史緣由,如今的Fragment嵌套的方式比較多了,因此這裏研究一下這些嵌套方式,看看是否可以找到一個合適方式懶加載方式做爲我這個BaseLazyFragment的實現,同時兼容這幾種嵌套方式。git

固然,個人出發點是還是從生命週期開始探索。這裏先溫故一下Fragment的生命週期吧:

爲了可以更好的觀察幾種嵌套方式在滑動過程當中的引用,除了上面的生命週期以外,我還在日誌中打印了onViewCreated和setUserVisibleHint()方法的調用時機。下面看一下個人日誌代碼:github

class LifeCycleFragment : Fragment() {
    companion object {
        fun create(position: Int): LifeCycleFragment {
            val fragment = LifeCycleFragment()
            fragment.arguments = bundleOf(Pair("p", position))
            return fragment
        }
    }

    var position: String = ""

    override fun onAttach(context: Context) {
        position = arguments?.getInt("p").toString()
        super.onAttach(context)
        Log.d("ResumeOnly", "Fragment${position}onAttach")
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("ResumeOnly", "Fragment${position}onCreate")
    }


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        Log.d("ResumeOnly", "Fragment${position}onCreateView")
        return TextView(activity).apply {
            text = position
            layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)
            textSize = 100f
            typeface = Typeface.DEFAULT_BOLD
            gravity = Gravity.CENTER
        }
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        Log.d("ResumeOnly", "Fragment${position}onActivityCreated")
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d("ResumeOnly", "Fragment${position}onViewCreated")
    }


    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        Log.d("ResumeOnly", "Fragment${position}isVisibleToUser$isVisibleToUser")
    }

    override fun onStart() {
        super.onStart()
        Log.d("ResumeOnly", "Fragment${position}onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d("ResumeOnly", "Fragment${position}onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d("ResumeOnly", "Fragment${position}onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d("ResumeOnly", "Fragment${position}onStop")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d("ResumeOnly", "Fragment${position}onDestroyView")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d("ResumeOnly", "Fragment${position}onDestroy")
    }

}

第一種嵌套:ViewPager+FragmentPagerAdapter(fa,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)的生命週期


經過上面的Log日誌,其實不難看出經過ViewPager+FragmentPagerAdapter(fm,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)進行管理Fragment,在默認沒有設置offscreenPageLimit的值時,也會幫咱們多預加載一個,而預加載的這個是走到了onStart方法,當左滑時,原來預加載的這個會走onResume,同時再預加載下一個。很明顯咱們的lazyLoad方法能夠放到onResume去作。當咱們滑動到當前頁的時候走當前頁的onResume方法而後再onResume中調用lazyLoad方法。app

第二種嵌套:ViewPager+FragmentPagerAdapter(fm)


根據上面的日誌能夠看到,再進入當前頁面的時候和第一種同樣,在默認沒有設置offscreenPageLimit的值時,也會幫咱們多預加載一個Fragment,但不一樣的時候,兩個Fragment的生命週期都會直接走到onResume的生命週期,而且它們都多了一個isVisibleToUser布爾值來控制Fragment是否可見。ide

第三種嵌套:ViewPager2+Fragment


根據上面的log能夠看到,進入界面時它只會初始化當前的Activity,它的可見與否也是由onResume和onPause進行回調的。它的生命週期就和第一種很像了,只是在沒有設置offscreenPageLimit的狀況下,它不會去預加載下一個的生命週期。因此咱們仍然能夠經過onResume來控制懶加載。佈局

製做一個通用的BaseLazyFragment

經過上面的三種嵌套的日誌。咱們只須要最後肯定一件事:就是mUserVisibleHint的默認值
google

如咱們所料,至此咱們只須要在onResume和setUserVisibleHint方法中都調用一個lazyLoad方法,並在lazyLoad中去判斷mUserVisibleHint的值以及自定義的一個loaded的值便可,另外須要注意的一點是setUserVisibleHint的方法調用的時機可能並不在Fragment的生命週期內spa

This method may be called outside of the fragment lifecycle.
and thus has no ordering guarantees with regard to fragment lifecycle method calls

因此咱們還須要判斷根佈局是否爲null,最後的實現代碼以下:日誌

abstract class BaseLazyFragment : Fragment() {
    private var cacheView: View? = null
    private var loaded: Boolean = false


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        if (cacheView == null) {
            cacheView = inflater.inflate(layoutId(), container, false)
        }
        return cacheView
    }


    override fun setUserVisibleHint(isVisibleToUser: Boolean) {
        super.setUserVisibleHint(isVisibleToUser)
        lazyLoad()
    }

    override fun onResume() {
        super.onResume()
        lazyLoad()

    }

    private fun lazyLoad() {
        if (userVisibleHint && !loaded&& cacheView != null) {
            initView()
            initData()
            loaded = true
        }
    }

    abstract fun layoutId(): Int

    abstract fun initData()

    abstract fun initView()
}

最後咱們一塊兒看一下這三嵌套模式的一個懶加載實現的效果:code

1588834300301

這個代碼是在google的ViewPager2的代碼基礎上新增了三個頁面進行日誌打印,
代碼地址:https://github.com/luorenyu/ExplorLazyFragment
歡迎關注黑客五六七orm

相關文章
相關標籤/搜索