Android Fragment可见性的判断与监听完全实现

本文仅适用于support包中的Fragment,没有对Android新的接口android.app.Fragment做测试。

实际开发时,常需要在Fragment可见时,做重新加载数据等操作,但系统没有提供可以直接使用的方法。这里通过改造BaseFragment实现Fragment可见性变化的监听。

Fragment可见的定义

  1. Parent可见。ParentActivity处于前台(Parent为Activity);或ParentFragment可见(Parent为Fragment)。
  2. 如果Fragment在ViewPager中,所在Tab被选中。
  3. Fragment被添加到Parent中、Fragment没有被隐藏。
  4. Fragment.View已经AttachToWindow(View被加到Window中),且View可见。

实现机制

1、ParentActivity可见

  • Fragment.onStart/onStop一般在Activity.onStart/onStop时被调用。
  • 但如果在Activity.onStart之后Fragment才被添加,其onStart方法会在添加后才调用。
  1. public class BaseVisibilityFragment extends Fragment {

  2. /**

  3. * ParentActivity是否可见

  4. */

  5. private boolean mParentActivityVisible = false;

  6. @Override

  7. public void onStart() {

  8. info("onStart");

  9. super.onStart();

  10. onActivityVisibilityChanged(true);

  11. }

  12. @Override

  13. public void onStop() {

  14. info("onStop");

  15. super.onStop();

  16. onActivityVisibilityChanged(false);

  17. }

  18. /**

  19. * ParentActivity可见性改变

  20. */

  21. protected void onActivityVisibilityChanged(boolean visible) {

  22. mParentActivityVisible = visible;

  23. }

  24. }

2、如果Fragment在ViewPager中,所在Tab被选中

  • Tab选中态改变事件,通过setUserVisibleHint回调可以监听
  • 通过getUserVisibleHint()可以读取当前所在Tab是否处于选中态
  • 对于没有Tab的页面,getUserVisibleHint()默认为true。
  1. public class BaseVisibilityFragment extends Fragment {
  2. /**
  3. * Tab切换时会回调此方法。对于没有Tab的页面,{@link Fragment#getUserVisibleHint()}默认为true。
  4. */
  5. @Override
  6. public void setUserVisibleHint(boolean isVisibleToUser) {
  7. info("setUserVisibleHint = " + isVisibleToUser);
  8. super.setUserVisibleHint(isVisibleToUser);
  9. }
  10. }

3、Fragment被添加、Fragment没有隐藏

  • 调用FragmentManager.beginTransaction().add()等相关方法,会导致Fragment被添加和移除。
  • 在回调onAttach和onDetach中可以监听Fragment被添加和移除事件。
  • 调用FragmentManager.showFragment/hideFragment会导致Fragment可见性变化,同时还会设置Fragment中顶层View的visibility。
  • 在回调onHiddenChanged中可监听可见性变化。

判断状态:

  1. boolean Fragment.isAdded();
  2. boolean Fragment.isHidden();

监听事件:

  1. public class BaseVisibilityFragment extends Fragment {

  2. @Override

  3. public void onAttach(Activity activity) {

  4. super.onAttach(activity);

  5. }

  6. @Override

  7. public void onDetach() {

  8. super.onDetach();

  9. }

  10. @Override

  11. public void onHiddenChanged(boolean hidden) {

  12. super.onHiddenChanged(hidden);

  13. }

  14. }

4. Fragment.View已经AttachToWindow,且View可见

  • View创建完成时,在onViewCreated回调中给View添加OnAttachStateChangeListener,可以监听其WindowAttach信息的变化。
  • View的可见性监听,可以通过重写View的方式实现。由于开发时一般很少直接调用Fragment.getView().setVisibility(),可以不考虑这种情况的监听。

判断状态:

  1. View view = Fragment.getView();
  2. view != null && view.isAttachedToWindow() && view.getVisibility() == View.VISIBLE;

监听事件:

  1. public class BaseVisibilityFragment extends Fragment implements View.OnAttachStateChangeListener {

  2. @Override

  3. public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {

  4. super.onViewCreated(view, savedInstanceState);

  5. view.addOnAttachStateChangeListener(this);

  6. }

  7. @Override

  8. public void onViewAttachedToWindow(View v) {

  9. LogUtils.i(getClass().getSimpleName(), "onViewAttachedToWindow");

  10. }

  11. @Override

  12. public void onViewDetachedFromWindow(View v) {

  13. LogUtils.i(getClass().getSimpleName(), "onViewDetachedFromWindow");

  14. v.removeOnAttachStateChangeListener(this);

  15. }

  16. }

5、ParentFragment可见

  • 定义一个接口,当Fragment可见性改变时,回调Listener。
  • Fragment在onAttach时检查是否有ParentFragment,如果有,则设置Listener监听ParentFragment的可见性。
  1. public interface OnFragmentVisibilityChangedListener {

  2. void onFragmentVisibilityChanged(boolean visible);

  3. }

  4. public class BaseVisibilityFragment extends Fragment {

  5. private OnFragmentVisibilityChangedListener mListener;

  6. public void setOnVisibilityChangedListener(OnFragmentVisibilityChangedListener listener) {

  7. mListener = listener;

  8. }

  9. @Override

  10. public void onAttach(Context context) {

  11. info("onAttach");

  12. super.onAttach(context);

  13. final Fragment parentFragment = getParentFragment();

  14. if (parentFragment != null && parentFragment instanceof BaseVisibilityFragment) {

  15. mParentFragment = ((BaseVisibilityFragment) parentFragment);

  16. mParentFragment.setOnVisibilityChangedListener(this);

  17. }

  18. }

  19. /**

  20. * 可见性改变

  21. */

  22. protected void onVisibilityChanged(boolean visible) {

  23. info("==> onFragmentVisibilityChanged = " + visible);

  24. if (mListener != null) {

  25. mListener.onFragmentVisibilityChanged(visible);

  26. }

  27. }

  28. }

完整方案

系统提供了一个Fragment.isVisible(),用于判断可见性,源码如下:

  1. /**
  2. * Return true if the fragment is currently visible to the user. This means
  3. * it: (1) has been added, (2) has its view attached to the window, and
  4. * (3) is not hidden.
  5. */
  6. final public boolean isVisible() {
  7. return isAdded() && !isHidden() && mView != null
  8. && mView.getWindowToken() != null && mView.getVisibility() == View.VISIBLE;
  9. }

下面是判断和监听Fragment可见性完整的代码(不考虑直接调用Fragment.getView().setVisibility时的监听,因为不容易实现且必要性不大)。要求所有Fragment继承BaseVisibilityFragment基类。

完整的Demo可在此下载 https://github.com/jzj1993/FragmentLifeCycle

  1. public interface OnFragmentVisibilityChangedListener {

  2. void onFragmentVisibilityChanged(boolean visible);

  3. }

  4. import android.content.Context;

  5. import android.os.Bundle;

  6. import android.support.annotation.Nullable;

  7. import android.support.v4.app.Fragment;

  8. import android.util.Log;

  9. import android.view.View;

  10. /**

  11. * Created by jzj on 16/9/5.

  12. */

  13. public class BaseVisibilityFragment extends Fragment implements View.OnAttachStateChangeListener, OnFragmentVisibilityChangedListener {

  14. /**

  15. * ParentActivity是否可见

  16. */

  17. private boolean mParentActivityVisible = false;

  18. /**

  19. * 是否可见(Activity处于前台、Tab被选中、Fragment被添加、Fragment没有隐藏、Fragment.View已经Attach)

  20. */

  21. private boolean mVisible = false;

  22. private BaseVisibilityFragment mParentFragment;

  23. private OnFragmentVisibilityChangedListener mListener;

  24. public void setOnVisibilityChangedListener(OnFragmentVisibilityChangedListener listener) {

  25. mListener = listener;

  26. }

  27. @Override

  28. public void onAttach(Context context) {

  29. info("onAttach");

  30. super.onAttach(context);

  31. final Fragment parentFragment = getParentFragment();

  32. if (parentFragment != null && parentFragment instanceof BaseVisibilityFragment) {

  33. mParentFragment = ((BaseVisibilityFragment) parentFragment);

  34. mParentFragment.setOnVisibilityChangedListener(this);

  35. }

  36. checkVisibility(true);

  37. }

  38. @Override

  39. public void onDetach() {

  40. info("onDetach");

  41. if (mParentFragment != null) {

  42. mParentFragment.setOnVisibilityChangedListener(null);

  43. }

  44. super.onDetach();

  45. checkVisibility(false);

  46. mParentFragment = null;

  47. }

  48. @Override

  49. public void onStart() {

  50. info("onStart");

  51. super.onStart();

  52. onActivityVisibilityChanged(true);

  53. }

  54. @Override

  55. public void onStop() {

  56. info("onStop");

  57. super.onStop();

  58. onActivityVisibilityChanged(false);

  59. }

  60. /**

  61. * ParentActivity可见性改变

  62. */

  63. protected void onActivityVisibilityChanged(boolean visible) {

  64. mParentActivityVisible = visible;

  65. checkVisibility(visible);

  66. }

  67. /**

  68. * ParentFragment可见性改变

  69. */

  70. @Override

  71. public void onFragmentVisibilityChanged(boolean visible) {

  72. checkVisibility(visible);

  73. }

  74. @Override

  75. public void onCreate(@Nullable Bundle savedInstanceState) {

  76. info("onCreate");

  77. super.onCreate(savedInstanceState);

  78. }

  79. @Override

  80. public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {

  81. super.onViewCreated(view, savedInstanceState);

  82. view.addOnAttachStateChangeListener(this);

  83. }

  84. @Override

  85. public void onHiddenChanged(boolean hidden) {

  86. super.onHiddenChanged(hidden);

  87. checkVisibility(hidden);

  88. }

  89. /**

  90. * Tab切换时会回调此方法。对于没有Tab的页面,{@link Fragment#getUserVisibleHint()}默认为true。

  91. */

  92. @Override

  93. public void setUserVisibleHint(boolean isVisibleToUser) {

  94. info("setUserVisibleHint = " + isVisibleToUser);

  95. super.setUserVisibleHint(isVisibleToUser);

  96. checkVisibility(isVisibleToUser);

  97. }

  98. @Override

  99. public void onViewAttachedToWindow(View v) {

  100. info("onViewAttachedToWindow");

  101. checkVisibility(true);

  102. }

  103. @Override

  104. public void onViewDetachedFromWindow(View v) {

  105. info("onViewDetachedFromWindow");

  106. v.removeOnAttachStateChangeListener(this);

  107. checkVisibility(false);

  108. }

  109. /**

  110. * 检查可见性是否变化

  111. *

  112. * @param expected 可见性期望的值。只有当前值和expected不同,才需要做判断

  113. */

  114. private void checkVisibility(boolean expected) {

  115. if (expected == mVisible) return;

  116. final boolean parentVisible = mParentFragment == null ? mParentActivityVisible : mParentFragment.isFragmentVisible();

  117. final boolean superVisible = super.isVisible();

  118. final boolean hintVisible = getUserVisibleHint();

  119. final boolean visible = parentVisible && superVisible && hintVisible;

  120. info(String.format("==> checkVisibility = %s ( parent = %s, super = %s, hint = %s )",

  121. visible, parentVisible, superVisible, hintVisible));

  122. if (visible != mVisible) {

  123. mVisible = visible;

  124. onVisibilityChanged(mVisible);

  125. }

  126. }

  127. /**

  128. * 可见性改变

  129. */

  130. protected void onVisibilityChanged(boolean visible) {

  131. info("==> onFragmentVisibilityChanged = " + visible);

  132. if (mListener != null) {

  133. mListener.onFragmentVisibilityChanged(visible);

  134. }

  135. }

  136. /**

  137. * 是否可见(Activity处于前台、Tab被选中、Fragment被添加、Fragment没有隐藏、Fragment.View已经Attach)

  138. */

  139. public boolean isFragmentVisible() {

  140. return mVisible;

  141. }

  142. private void info(String s) {

  143. if (BuildConfig.DEBUG) {

  144. Log.i(getClass().getSimpleName() + " (" + hashCode() + ")", s);

  145. }

  146. }

  147. }