一、介绍
Fragment是Android3.0以后引进,称为碎片。它与Activity非常相似,用一个Activity中描述一些行为或者一部分用户界面,使用多个Fragment可以在一个单独的Activity中建立多个UI面板,也可以在多个Activity中使用Fragment。
Fragment的引入主要是处理UI展示,所以在理解的时候,可以将它理解成布局控件,常见的fragment搭配有:
1.引入布局,作为布局中控件,通过fragmentmanager来控制
2.导航内容:Navigation组件
3.作为viewpage的子view:viewpage
二、功能介绍
1.作为布局
作为布局控件,加载到布局中展示,通过我们先把fragment开发完毕,然后在Activity中加入即可。
通过fragmentManage提交进去。
getSupportFragmentManager().beginTransaction().add(R.id.fragment,fragments).commitNow();
这种做法是最常见的,但是如何判断当前fragment的是否已添加进去呢?通过isAdd()方法。
注意:
很多人在使用这种方法,特别是延迟处理和耗时处理的回调,发现当前fragment被销毁了,如果处理这些可以通过isAdd()来判断一下。避免对象销毁,业务还在进行。
2.ViewPager
Viewpage是我们比较常见的,fragment作为viewItem使用,但是viewpage的滑动功能,导致会对fragment进行提前加载到内存的机制,如果不提前添加进行,在滑动计算是一个不好控制的局面。
正常的Activity都是当前窗口只有一个,如果是fragment可能出现多个,这就会导致提前加载会引起View的提前曝光,数据提前请求等,这和所见即所得还有有区别的。这个就需要我们去理解这个懒加载机制,当页面真的添加,并展示出来才会处理。这里面只是介绍了懒加载与viewpage存在的问题,为什么会,接下我们会核心的去介绍懒加载。
3.导航Navigation
关于导航的使用,可以查看我的以前文章:
Android JetPack底部导航Navigation 组件的介绍与使用_android 导航
三、懒加载
想知道懒加载,先要了解fragment的一些特性。
1.require:
在获取变量的时候,不能调用这个方法,否则会抛异常
会主动给你检查一下,对空指针直接抛异常,这就会导致在复杂的场景下,应用的兼容性更差,特别是在kotlin语言中,不懂的以为都是调用一样,其实不然。
2.页面可见
2.1添加:
fragment是作为view控件来使用的,虽然 看似与Activity有着一样的方法与功能,但是不能单独存在,它自身是没有窗口,依托Activity来完成。
选配:Viewpage+FragmentPagerAdapter
这里面的adapter选取了FragmentPagerAdapter,FragmentPagerAdapter对PagrAdapter进行了扩展,在页面的提交和保活做了支持。
FragmentPagerAdapter的创建:
创建的时候behavior有两种
public static final int BEHAVIOR_SET_USER_VISIBLE_HINT = 0; public static final int BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT = 1;
第一种,behavior=0在页面创建通过调用setUserVisibleHint
第一种,behavior=1在页面创建通过调用setMaxLifecycle(fragment, State.STARTED)
正常,我们通过setUserVisibleHint来判断页面,那么在创建adapter的时候,behavior默认是0.
fragment:
在fragment中,我们需要处理
override fun setUserVisibleHint(isVisibleToUser: Boolean)
的回调。
FragmentPagerAdapter
2.2源码分析
由于fragment在viewpage中相当于view,viewpage又是一个支持滑动的,所以viewpage提供了一个
1.public void setOffscreenPageLimit(int limit)
的方法,默认至少需要提前初始化一个。
所以,在初始化的时候,回调的
override fun setUserVisibleHint(isVisibleToUser: Boolean)
默认都是false。只有在adapter
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object)
中才会去确定做回调
当adapter回调时,这时,我们的fragment在ViewGroup中处于可见状态。
至于setUserVisibleHint(isVisibleToUser: Boolean)回调多少次false,取决于
setOffscreenPageLimit(int limit)的大小。
2.初始化规则是:
从选中位置开始,左右两边初始化limit个,如果左右两边的数量小于limit
左边:Math.min(currentIndex,limit),右边Math.min(count-currentIndex,limit)
最多一次初始化=limit*2+1;最后会落在currentIndex的fragment上。
懒加载核心
第一种:behavior=0
setUserVisibleHint(isVisibleToUser: Boolean),在fragment创建的时候,都会收到回调为false。
懒加载的核心是所见即所得,显然isVisibleToUser为false不是我们想要的结果。所以我们应该在fragment记录上一次的状态,如果状态不一致,可以肯定当前页面没有发生变化,页面没变化,直接不处理任何数据逻辑。即,当前页面是不可见。
override fun UserVisibleHint(isVisibleToUser: Boolean) {
if (visibleToUser == isVisibleToUser)
return
MyLog.log("lazy", "===${isVisibleToUser}")
visibleToUser = isVisibleToUser
arguments?.let {
MyLog.log("lazy", "${it.get("key")}===${isVisibleToUser}")
}
}
所以日志输出,只有当前选中的页面,其他都被拒了
override fun initView() {
adapter= MyViewPageAdapter(list,supportFragmentManager)
bind.myviewpage.offscreenPageLimit=2
bind.myviewpage.adapter=adapter
bind.myviewpage.currentItem=5
}
这样,当 UserVisibleHint(isVisibleToUser: Boolean)回调为true,当前fragment是可见的。
第二种:behavior=1
当behavior=1是,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
Adapter走的LifecycleRegistry进行监听,状态也是通过lifecycle进行回调。
所以:
在fragment的源码中也有进行解释。
fragment
在adapter中,如果当前fragment可见,回到的状态是:RESUME
所以:我们只要进行监听:
(lifecycle as LifecycleRegistry).addObserver(object : LifecycleEventObserver{
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
}
})
从日志可以看出Event
- CREATE,START:当页面被创建,首先回调的是这两个状态
- RESUME:当前页面可见
- PAUSE:从可见到不可见
- STOP:页面被销毁
所以,当你采用behavior=1时,只需要判断RESUME这个即可。
参考Demo:
abstract class BaseLazyFragment<V : ViewDataBinding> : Fragment() {
var visibleToUser: Boolean = false
lateinit var bind: V
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
bind = DataBindingUtil.inflate(inflater, getLayoutResId(), container, false)
return bind.root
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let { initData(it) }
(lifecycle as LifecycleRegistry).addObserver(object : LifecycleEventObserver{
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
onStateChangeds(source,event)
}
})
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
UserVisibleHint(isVisibleToUser)
}
@LayoutRes
abstract fun getLayoutResId(): Int
abstract fun UserVisibleHint(isVisibleToUser: Boolean)
abstract fun initData(bund: Bundle)
abstract fun onStateChangeds(source: LifecycleOwner, event: Lifecycle.Event)
}
class MyLazyChildFragment : BaseLazyFragment<MyLazyViewBind>() {
override fun getLayoutResId(): Int {
return R.layout.fragment_lazy_test
}
var key = ""
var mEvent: Lifecycle.Event? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.runOnUiThread {
arguments?.let {
bind.textTitle.text = "当前页面序号${it.getString("key")}"
}
}
}
override fun initData(bund: Bundle) {
bund?.let {
key = it.getString("key", "")
}
}
override fun UserVisibleHint(isVisibleToUser: Boolean) {
if (visibleToUser == isVisibleToUser)
return
visibleToUser = isVisibleToUser
arguments?.let {
MyLog.log("lazy", "${it.get("key")}===${isVisibleToUser}")
}
}
override fun onStateChangeds(source: LifecycleOwner, event: Lifecycle.Event) {
MyLog.log("lazy", "${key}===Event=${event.name}")
if (mEvent == event)
return
mEvent = event
if (mEvent == Lifecycle.Event.ON_RESUME) {
MyLog.log("lazy", "${key}===Event=${event.name},可见")
}else{
MyLog.log("lazy", "${key}===Event=${event.name},不可见")
}
}
companion object {
@Synchronized
fun getInstance(index: Int): Fragment {
var fragment = MyLazyChildFragment()
var bundle = Bundle()
bundle.putString("key", "${index}")
fragment.arguments = bundle
return fragment
}
}
}
Adapter:
class MyViewPageAdapter : FragmentPagerAdapter {
var list = mutableListOf<Fragment>()
constructor(list: MutableList<Fragment>, fm: FragmentManager) : super(fm,BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
this.list = list
}
override fun getCount(): Int {
return list?.size
}
override fun getItem(p0: Int): Fragment {
return list?.get(p0)
}
}
Page的初始化:
override fun initView() {
adapter= MyViewPageAdapter(list,supportFragmentManager)
bind.myviewpage.offscreenPageLimit=1
bind.myviewpage.adapter=adapter
bind.myviewpage.currentItem=5
}
注意:
Demo的代码是kotlin与DataBinding写法,在代码demo整理的时候需要小心,不会这两种语法的可以查看我的文章。