Android Fragment在用户视角可见性监听

众所周知,Anroid 的 Fragment 的生命周期十分复杂。并且 Fragment 在不同的组建中甚至有不同的可见性实现,比如早期的 ViewPager 通过调用 Fragment 的 setUserVisibleHint 方法来控制其可见性,而其他一些组建也会通过 FragmentTransaction 的 hide 或者 show 方法实现对 Fragment 的隐藏和显示。
除此之外,其本身的生命周期 onPause() 和 onResume() 也会影响其可见性。在 onResume 之前 Fragment 都是不可见的。没有了吗?不是的,还有一种情况会影响 Fragment 的生命周期,那就是其父 Fragment 的可见性。当其父 Fragment 不可见时,其子 Fragment 不一定会收到任何回调,但是其依然会不可见。
由此可见,想要实时监听 Fragment 在用户视角的可见性是一件挺复杂的事情。需要考虑很多种情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
open class VisibleChangeListenerFragment : Fragment(), IPareVisibilityObserver {
private var parentFragmentVisible = false

private val listeners: MutableList<OnVisibleChangeListener> = mutableListOf()

// 此时 Fragment 是否展示在用户视角中
var visibleForUser = false
private set(value) {
if (field == value) {
return
}
Log.d("tangrui", "${this.javaClass.simpleName} visible = $value")
field = value
listeners.forEach {
it.onVisibleChange(this, value)
}
notifyChildVisibleChange(value)
}

override fun onResume() {
super.onResume()
parentFragmentVisible = queryParentFragmentVisible()
checkAndSetVisible()
}

override fun onPause() {
visibleForUser = false
super.onPause()
}

override fun onDestroy() {
listeners.clear()
super.onDestroy()
}

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

override fun onHiddenChanged(hidden: Boolean) {
super.onHiddenChanged(hidden)
checkAndSetVisible()
}

private fun checkAndSetVisible() {
visibleForUser = isResumed && !isHidden && userVisibleHint && parentFragmentVisible
}

fun addOnVisibleChangeListener(listener: OnVisibleChangeListener) {
listeners.add(listener)
}

fun removeOnVisibleChangeListener(listener: OnVisibleChangeListener) {
listeners.remove(listener)
}

override fun onParentFragmentVisibleChanged(visible: Boolean) {
if (!visible) {
parentFragmentVisible = false
checkAndSetVisible()
} else {
parentFragmentVisible = queryParentFragmentVisible()
checkAndSetVisible()
}
}

private fun queryParentFragmentVisible(): Boolean {
var parent = parentFragment
var parentVisibleForUser = true
while (parent != null) {
if (parent is VisibleChangeListenerFragment) {
if (!parent.visibleForUser) {
parentVisibleForUser = false
break
}
} else {
if (parent.isHidden || !parent.isResumed || !parent.userVisibleHint) {
parentVisibleForUser = false
break
}
}
parent = parent.parentFragment
}
return parentVisibleForUser
}

//当自己的显示隐藏状态改变时,调用这个方法通知子Fragment
private fun notifyChildVisibleChange(visible: Boolean) {
if (isDetached || !isAdded) {
return
}
val fragmentManager = childFragmentManager
val fragments = fragmentManager.fragments
for (fragment in fragments) {
if (fragment !is IPareVisibilityObserver) {
continue
}
(fragment as IPareVisibilityObserver).onParentFragmentVisibleChanged(visible)
}
}

interface OnVisibleChangeListener {
fun onVisibleChange(fragment: VisibleChangeListenerFragment, visible: Boolean)
}
}

interface IPareVisibilityObserver {
fun onParentFragmentVisibleChanged(visible: Boolean)
}