Fragment 可见性监听方案 - 完美兼容多种 case,阿里三面

这时候,我们可以通过 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,会有不同的表现

  1. 当 behavior 为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 时,

ViewPager 中切换 Fragment,setUserVisibleHint 方法将不再被调用,他会确保 onResume 的正确调用时机

  1. 当 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 事件呢?

方法肯定是有的。

  1. 第一种方法,宿主 Fragment 提供可见性的回调,子 Fragment 监听改回调,有点类似于观察者模式。难点在于子 Fragment 要怎么拿到宿主 Fragment

  2. 第二种 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()

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 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 可以兼容

    1. 直接 fragment 直接 add, hide 主要是通过 onHiddenChanged
    1. 直接 fragment 直接 replace ,主要是在 onResume 做判断
    1. 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()

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可见性改变

*/
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司21年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)*

最后

其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。

虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

这里附上上述的面试题相关的几十套字节跳动,京东,小米,腾讯、头条、阿里、美团等公司21年的面试题。把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

由于篇幅有限,这里以图片的形式给大家展示一小部分。

[外链图片转存中…(img-LWwj9VF9-1712536402472)]

网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 30
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值