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

众所周知,AnroidFragment 的生命周期十分复杂。并且 Fragment 在不同的组建中甚至有不同的可见性实现,比如早期的 ViewPager 通过调用 FragmentsetUserVisibleHint 方法来控制其可见性,而其他一些组建也会通过 FragmentTransactionhide 或者 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)
}