我的公众号程序员徐公,四年中大厂工作教训,回复黑马,支付 Android 学习视频一份,回复徐公666,能够取得我精心整顿的简历模板,带你走近大厂。
前言
本篇文章次要提供一种监听 Fragment 可见性监听的计划,完满多种 case,有趣味的能够看看。废话不多说,开始进入注释。
在开发当中, fragment 常常应用到。在很多利用场景中,咱们须要监听到 fragment 的显示与暗藏,来进行一些操作。比方,统计页面的停留时长,页面暗藏的时候进行播放视频。
有些同学可能会说了,这还不容易,间接监听 Fragment 的 onResume,onPause。我只能说,兄弟,too young,too simple。
上面,让咱们一起来实现 fragment 的监听。次要分为几种 case
- 一个页面只有一个 fragment 的,应用 replace
- Hide 和 Show 操作
- ViewPager 嵌套 Fragment
- 宿主 Fragment 再嵌套 Fragment,比方 ViewPager 嵌套 ViewPager,再嵌套 Fragment
Replace 操作
replace 操作这种比较简单,因为他会失常调用 onResume 和 onPause 办法,咱们只须要在 onResume 和 onPause 做 check 操作即可
override fun onResume() {
info("onResume")
super.onResume()
onActivityVisibilityChanged(true)
}
override fun onPause() {
info("onPause")
super.onPause()
onActivityVisibilityChanged(false)
}
Hide 和 Show 操作
Hide 和 show 操作,会促发生命周期的回调,然而 hide 和 show 操作并不会,那么咱们能够通过什么办法来监听呢?其实很简略,能够通过 onHiddenChanged 办法
/**
* 调用 fragment show hide 的时候回调用这个办法
*/
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
checkVisibility(hidden)
}
ViewPager 嵌套 Fragment
ViewPager 嵌套 Fragment,这种也是很常见的一种构造。因为 ViewPager 的预加载机制,在 onResume 监听是不精确的。
这时候,咱们能够通过 setUserVisibleHint 办法来监听,当办法传入值为true的时候,阐明Fragment可见,为false的时候阐明Fragment被切走了
public void setUserVisibleHint(boolean isVisibleToUser)
有一点须要留神的是,个办法可能先于Fragment的生命周期被调用(在FragmentPagerAdapter中,在Fragment被add之前这个办法就被调用了),所以在这个办法中进行操作之前,可能须要先判断一下生命周期是否执行了。
/**
* Tab切换时会回调此办法。对于没有Tab的页面,[Fragment.getUserVisibleHint]默认为true。
*/
@Suppress("DEPRECATION")
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
info("setUserVisibleHint = $isVisibleToUser")
super.setUserVisibleHint(isVisibleToUser)
checkVisibility(isVisibleToUser)
}
/**
* 查看可见性是否变动
*
* @param expected 可见性冀望的值。只有以后值和expected不同,才须要做判断
*/
private fun checkVisibility(expected: Boolean) {
if (expected == visible) return
val parentVisible = if (localParentFragment == null) {
parentActivityVisible
} else {
localParentFragment?.isFragmentVisible() ?: false
}
val superVisible = super.isVisible()
val hintVisible = userVisibleHint
val visible = parentVisible && superVisible && hintVisible
info(
String.format(
"==> checkVisibility = %s ( parent = %s, super = %s, hint = %s )",
visible, parentVisible, superVisible, hintVisible
)
)
if (visible != this.visible) {
this.visible = visible
onVisibilityChanged(this.visible)
}
}
AndroidX 的适配(也是一个坑)
在 AndroidX 当中,FragmentAdapter 和 FragmentStatePagerAdapter 的构造方法,增加一个 behavior 参数实现的。
如果咱们指定不同的 behavior,会有不同的体现
- 当 behavior 为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 时,ViewPager 中切换 Fragment,setUserVisibleHint 办法将不再被调用,他会确保 onResume 的正确调用机会
- 当 behavior 为 BEHAVIOR_SET_USER_VISIBLE_HINT,跟之前的形式是统一的,咱们能够通过 setUserVisibleHint 联合 fragment 的生命周期来监听
//FragmentStatePagerAdapter构造方法
public FragmentStatePagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
//FragmentPagerAdapter构造方法
public FragmentPagerAdapter(@NonNull FragmentManager fm,
@Behavior int behavior) {
mFragmentManager = fm;
mBehavior = behavior;
}
@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})
private @interface Behavior { }
既然是这样,咱们就很好适配呢,间接在 onResume 中调用 checkVisibility 办法,判断以后 Fragment 是否可见。
回过头,Behavior 是如何实现的呢?
已 FragmentStatePagerAdapter 为例,咱们一起开看看源码
@SuppressWarnings({"ReferenceEquality", "deprecation"})
@Override
public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
Fragment fragment = (Fragment)object;
if (fragment != mCurrentPrimaryItem) {
if (mCurrentPrimaryItem != null) {
//以后显示Fragment
mCurrentPrimaryItem.setMenuVisibility(false);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
//最大生命周期设置为STARTED,生命周期回退到onPause
mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem, Lifecycle.State.STARTED);
} else {
//可见性设置为false
mCurrentPrimaryItem.setUserVisibleHint(false);
}
}
//将要显示的Fragment
fragment.setMenuVisibility(true);
if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
//最大 生命周期设置为RESUMED
mCurTransaction.setMaxLifecycle(fragment, Lifecycle.State.RESUMED);
} else {
//可见性设置为true
fragment.se tUserVisibleHint(true);
}
//赋值
mCurrentPrimaryItem = fragment;
}
}
代码比较简单很好了解
- 当 mBehavior 设置为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 会通过 setMaxLifecycle 来批改以后Fragment和将要显示的Fragment的状态,使得只有正在显示的 Fragmen t执行到 onResume() 办法,其余 Fragment 只会执行到 onStart() 办法,并且当 Fragment 切换到不显示状态时触发 onPause() 办法。
- 当 mBehavior 设置为 BEHAVIOR_SET_USER_VISIBLE_HINT 时,会当 frament 可见性发生变化时调用 setUserVisibleHint() ,也就是跟咱们下面提到的第一种懒加载实现形式一样。
更多详情,能够参考这一篇博客Android Fragment + ViewPager的懒加载实现
宿主 Fragment 再嵌套 Fragment
这种 case 也是比拟常见的,比方 ViewPager 嵌套 ViewPager,再嵌套 Fragment。
宿主Fragment在生命周期执行的时候会相应的散发到子Fragment中,然而setUserVisibleHint和onHiddenChanged却没有进行相应的回调。试想一下,一个ViewPager中有一个FragmentA的tab,而FragmentA中有一个子FragmentB,FragmentA被滑走了,FragmentB并不能接管到setUserVisibleHint事件,onHiddenChange事件也是一样的。
那有没有方法监听到宿主的 setUserVisibleHint 和 ,onHiddenChange 事件呢?
办法必定是有的。
- 第一种办法,宿主 Fragment 提供可见性的回调,子 Fragment 监听改回调,有点相似于观察者模式。难点在于子 Fragment 要怎么拿到宿主 Fragment
- 第二种 case,宿主 Fragment 可见性变动的时候,被动去遍历所有的 子 Fragment,调用 子 Fragment 的相应办法
第一种办法
总体思路是这样的,宿主 Fragment 提供可见性的回调,子 Fragment 监听改回调,有点相似于观察者模式。也有点相似于 Rxjava 中下游持有
第一,咱们先定义一个接口
interface OnFragmentVisibilityChangedListener {
fun onFragmentVisibilityChanged(visible: Boolean)
}
第二步,在 BaseVisibilityFragment 中提供 addOnVisibilityChangedListener 和 removeOnVisibilityChangedListener 办法,这里须要留神的是,咱们须要用一个 ArrayList 来保留所有的 listener,因为一个宿主 Fragment 可能有多个子 Fragment。
当 Fragment 可见性变动的时候,会遍历 List 调用 OnFragmentVisibilityChangedListener 的 onFragmentVisibilityChanged 办法
**
open class BaseVisibilityFragment : Fragment(), View.OnAttachStateChangeListener,
OnFragmentVisibilityChangedListener {
private val listeners = ArrayList<OnFragmentVisibilityChangedListener>()
fun addOnVisibilityChangedListener(listener: OnFragmentVisibilityChangedListener?) {
listener?.apply {
listeners.add(this)
}
}
fun removeOnVisibilityChangedListener(listener: OnFragmentVisibilityChangedListener?) {
listener?.apply {
listeners.remove(this)
}
}
private fun checkVisibility(expected: Boolean) {
if (expected == visible) return
val parentVisible =
if (localParentFragment == null) parentActivityVisible
else localParentFragment?.isFragmentVisible() ?: false
val superVisible = super.isVisible()
val hintVisible = userVisibleHint
val visible = parentVisible && superVisible && hintVisible
if (visible != this.visible) {
this.visible = visible
listeners.forEach { it ->
it.onFragmentVisibilityChanged(visible)
}
onVisibilityChanged(this.visible)
}
}
第三步,在 Fragment attach 的时候,咱们通过 getParentFragment 办法,拿到宿主 Fragment,进行监听。这样,当宿主 Fragment 可见性变动的时候,子 Fragment 能感应到。
override fun onAttach(context: Context) {
super.onAttach(context)
val parentFragment = parentFragment
if (parentFragment != null && parentFragment is BaseVisibilityFragment) {
this.localParentFragment = parentFragment
info("onAttach, localParentFragment is $localParentFragment")
localParentFragment?.addOnVisibilityChangedListener(this)
}
checkVisibility(true)
}
第二种办法
第二种办法,它的实现思路是这样的,宿主 Fragment 生命周期发生变化的时候,遍历子 Fragment,调用相应的办法,告诉生命周期发生变化
//当本人的显示暗藏状态扭转时,调用这个办法告诉子Fragment
private void notifyChildHiddenChange(boolean hidden) {
if (isDetached() || !isAdded()) {
return;
}
FragmentManager fragmentManager = getChildFragmentManager();
List<Fragment> fragments = fragmentManager.getFragments();
if (fragments == null || fragments.isEmpty()) {
return;
}
for (Fragment fragment : fragments) {
if (!(fragment instanceof IPareVisibilityObserver)) {
continue;
}
((IPareVisibilityObserver) fragment).onParentFragmentHiddenChanged(hidden);
}
}
具体的实现计划,能够看这一篇博客。获取和监听Fragment的可见性
残缺代码
/**
* Created by jun xu on 2020/11/26.
*/
interface OnFragmentVisibilityChangedListener {
fun onFragmentVisibilityChanged(visible: Boolean)
}
/**
* Created by jun xu on 2020/11/26.
*
* 反对以下四种 case
* 1. 反对 viewPager 嵌套 fragment,次要是通过 setUserVisibleHint 兼容,
* FragmentStatePagerAdapter BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 的 case,因为这时候不会调用 setUserVisibleHint 办法,在 onResume check 能够兼容
* 2. 间接 fragment 间接 add, hide 次要是通过 onHiddenChanged
* 3. 间接 fragment 间接 replace ,次要是在 onResume 做判断
* 4. Fragment 外面用 ViewPager, ViewPager 外面有多个 Fragment 的,通过 setOnVisibilityChangedListener 兼容,前提是一级 Fragment 和 二级 Fragment 都必须继承 BaseVisibilityFragment, 且必须用 FragmentPagerAdapter 或者 FragmentStatePagerAdapter
* 我的项目当中一级 ViewPager adapter 比拟非凡,不是 FragmentPagerAdapter,也不是 FragmentStatePagerAdapter,导致这种形式用不了
*/
open class BaseVisibilityFragment : Fragment(), View.OnAttachStateChangeListener,
OnFragmentVisibilityChangedListener {
companion object {
const val TAG = "BaseVisibilityFragment"
}
/**
* ParentActivity是否可见
*/
private var parentActivityVisible = false
/**
* 是否可见(Activity处于前台、Tab被选中、Fragment被增加、Fragment没有暗藏、Fragment.View曾经Attach)
*/
private var visible = false
private var localParentFragment: BaseVisibilityFragment? =
null
private val listeners = ArrayList<OnFragmentVisibilityChangedListener>()
fun addOnVisibilityChangedListener(listener: OnFragmentVisibilityChangedListener?) {
listener?.apply {
listeners.add(this)
}
}
fun removeOnVisibilityChangedListener(listener: OnFragmentVisibilityChangedListener?) {
listener?.apply {
listeners.remove(this)
}
}
override fun onAttach(context: Context) {
info("onAttach")
super.onAttach(context)
val parentFragment = parentFragment
if (parentFragment != null && parentFragment is BaseVisibilityFragment) {
this.localParentFragment = parentFragment
localParentFragment?.addOnVisibilityChangedListener(this)
}
checkVisibility(true)
}
override fun onDetach() {
info("onDetach")
localParentFragment?.removeOnVisibilityChangedListener(this)
super.onDetach()
checkVisibility(false)
localParentFragment = null
}
override fun onResume() {
info("onResume")
super.onResume()
onActivityVisibilityChanged(true)
}
override fun onPause() {
info("onPause")
super.onPause()
onActivityVisibilityChanged(false)
}
/**
* ParentActivity可见性扭转
*/
protected fun onActivityVisibilityChanged(visible: Boolean) {
parentActivityVisible = visible
checkVisibility(visible)
}
/**
* ParentFragment可见性扭转
*/
override fun onFragmentVisibilityChanged(visible: Boolean) {
checkVisibility(visible)
}
override fun onCreate(savedInstanceState: Bundle?) {
info("onCreate")
super.onCreate(savedInstanceState)
}
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?
) {
super.onViewCreated(view, savedInstanceState)
// 解决间接 replace 的 case
view.addOnAttachStateChangeListener(this)
}
/**
* 调用 fragment add hide 的时候回调用这个办法
*/
override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
checkVisibility(hidden)
}
/**
* Tab切换时会回调此办法。对于没有Tab的页面,[Fragment.getUserVisibleHint]默认为true。
*/
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
info("setUserVisibleHint = $isVisibleToUser")
super.setUserVisibleHint(isVisibleToUser)
checkVisibility(isVisibleToUser)
}
override fun onViewAttachedToWindow(v: View?) {
info("onViewAttachedToWindow")
checkVisibility(true)
}
override fun onViewDetachedFromWindow(v: View) {
info("onViewDetachedFromWindow")
v.removeOnAttachStateChangeListener(this)
checkVisibility(false)
}
/**
* 查看可见性是否变动
*
* @param expected 可见性冀望的值。只有以后值和expected不同,才须要做判断
*/
private fun checkVisibility(expected: Boolean) {
if (expected == visible) return
val parentVisible =
if (localParentFragment == null) parentActivityVisible
else localParentFragment?.isFragmentVisible() ?: false
val superVisible = super.isVisible()
val hintVisible = userVisibleHint
val visible = parentVisible && superVisible && hintVisible
info(
String.format(
"==> checkVisibility = %s ( parent = %s, super = %s, hint = %s )",
visible, parentVisible, superVisible, hintVisible
)
)
if (visible != this.visible) {
this.visible = visible
onVisibilityChanged(this.visible)
}
}
/**
* 可见性扭转
*/
protected fun onVisibilityChanged(visible: Boolean) {
info("==> onVisibilityChanged = $visible")
listeners.forEach {
it.onFragmentVisibilityChanged(visible)
}
}
/**
* 是否可见(Activity处于前台、Tab被选中、Fragment被增加、Fragment没有暗藏、Fragment.View曾经Attach)
*/
fun isFragmentVisible(): Boolean {
return visible
}
private fun info(s: String) {
Log.i(TAG, "${this.javaClass.simpleName} ; $s ; this is $this")
}
}
题外话
往年有好长时间没有更新技术博客了,次要是比较忙。拖着拖着,就懒得更新了。
这边博客的技术含量其实不高,次要是适配。
- AndroidX FragmentAdapter behavior 的适配
- 宿主 Fragment 嵌套 Fragment,提供了两种形式解决,一种是自上而下的,一种是自上而下的。借鉴了 Rxjava 的设计思维,上游持有上游的援用,从而管制 Obverable 的回调线程。Obsever 会有上游 Observer 的援用,从而进行一些转换操作,比方 map,FlatMap 操作符
- 如果你应用中遇到坑,也欢送随时 call 我,咱们一起解决。如果你有更好的计划,也欢送随时跟我交换
往期文章
面试官系列- 你真的理解 http 吗
面试官问, https 真的平安吗,能够抓包吗,如何避免抓包吗
java 版剑指offer算法集锦
启动优化相干
这几篇文章从 0 到 1,解说 DAG 有向无环图是怎么实现的,以及在 Android 启动优化的利用。
举荐理由:当初挺多文章一谈到启动优化,动不动就聊拓扑构造,这篇文章从数据结构到算法、到设计都给大家说分明了,开源我的项目也有十分强的借鉴意义。
[Android 启动优化(一) – 有向无环图
](https://juejin.cn/post/692679…)
Android 启动优化(二) – 拓扑排序的原理以及解题思路
Android 启动优化(三) – AnchorTask 应用阐明
Android 启动优化(四)- 手把手教你实现 AnchorTask
Android 启动优化(五)- AnchorTask 1.0.0 版本更新了
发表回复