本文仅适用于support包中的Fragment,没有对Android新的接口android.app.Fragment
做测试。
实际开发时,常需要在Fragment可见时,做重新加载数据等操作,但系统没有提供可以直接使用的方法。这里通过改造BaseFragment实现Fragment可见性变化的监听。
Fragment可见的定义
- Parent可见。ParentActivity处于前台(Parent为Activity);或ParentFragment可见(Parent为Fragment)。
- 如果Fragment在ViewPager中,所在Tab被选中。
- Fragment被添加到Parent中、Fragment没有被隐藏。
- Fragment.View已经AttachToWindow(View被加到Window中),且View可见。
实现机制
1、ParentActivity可见
Fragment.onStart/onStop
一般在Activity.onStart/onStop
时被调用。- 但如果在
Activity.onStart
之后Fragment才被添加,其onStart方法会在添加后才调用。
public class BaseVisibilityFragment extends Fragment {
/**
* ParentActivity是否可见
*/
private boolean mParentActivityVisible = false;
@Override
public void onStart() {
info("onStart");
super.onStart();
onActivityVisibilityChanged(true);
}
@Override
public void onStop() {
info("onStop");
super.onStop();
onActivityVisibilityChanged(false);
}
/**
* ParentActivity可见性改变
*/
protected void onActivityVisibilityChanged(boolean visible) {
mParentActivityVisible = visible;
}
}
2、如果Fragment在ViewPager中,所在Tab被选中
- Tab选中态改变事件,通过setUserVisibleHint回调可以监听
- 通过getUserVisibleHint()可以读取当前所在Tab是否处于选中态
- 对于没有Tab的页面,getUserVisibleHint()默认为true。
public class BaseVisibilityFragment extends Fragment {
/**
* Tab切换时会回调此方法。对于没有Tab的页面,{@link Fragment#getUserVisibleHint()}默认为true。
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
info("setUserVisibleHint = " + isVisibleToUser);
super.setUserVisibleHint(isVisibleToUser);
}
}
3、Fragment被添加、Fragment没有隐藏
- 调用
FragmentManager.beginTransaction().add()
等相关方法,会导致Fragment被添加和移除。 - 在回调onAttach和onDetach中可以监听Fragment被添加和移除事件。
- 调用
FragmentManager.showFragment/hideFragment
会导致Fragment可见性变化,同时还会设置Fragment中顶层View的visibility。 - 在回调onHiddenChanged中可监听可见性变化。
判断状态:
boolean Fragment.isAdded();
boolean Fragment.isHidden();
监听事件:
public class BaseVisibilityFragment extends Fragment {
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
}
@Override
public void onDetach() {
super.onDetach();
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
}
}
4. Fragment.View已经AttachToWindow,且View可见
- View创建完成时,在onViewCreated回调中给View添加OnAttachStateChangeListener,可以监听其WindowAttach信息的变化。
- View的可见性监听,可以通过重写View的方式实现。由于开发时一般很少直接调用Fragment.getView().setVisibility(),可以不考虑这种情况的监听。
判断状态:
View view = Fragment.getView();
view != null && view.isAttachedToWindow() && view.getVisibility() == View.VISIBLE;
监听事件:
public class BaseVisibilityFragment extends Fragment implements View.OnAttachStateChangeListener {
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.addOnAttachStateChangeListener(this);
}
@Override
public void onViewAttachedToWindow(View v) {
LogUtils.i(getClass().getSimpleName(), "onViewAttachedToWindow");
}
@Override
public void onViewDetachedFromWindow(View v) {
LogUtils.i(getClass().getSimpleName(), "onViewDetachedFromWindow");
v.removeOnAttachStateChangeListener(this);
}
}
5、ParentFragment可见
- 定义一个接口,当Fragment可见性改变时,回调Listener。
- Fragment在onAttach时检查是否有ParentFragment,如果有,则设置Listener监听ParentFragment的可见性。
public interface OnFragmentVisibilityChangedListener {
void onFragmentVisibilityChanged(boolean visible);
}
public class BaseVisibilityFragment extends Fragment {
private OnFragmentVisibilityChangedListener mListener;
public void setOnVisibilityChangedListener(OnFragmentVisibilityChangedListener listener) {
mListener = listener;
}
@Override
public void onAttach(Context context) {
info("onAttach");
super.onAttach(context);
final Fragment parentFragment = getParentFragment();
if (parentFragment != null && parentFragment instanceof BaseVisibilityFragment) {
mParentFragment = ((BaseVisibilityFragment) parentFragment);
mParentFragment.setOnVisibilityChangedListener(this);
}
}
/**
* 可见性改变
*/
protected void onVisibilityChanged(boolean visible) {
info("==> onFragmentVisibilityChanged = " + visible);
if (mListener != null) {
mListener.onFragmentVisibilityChanged(visible);
}
}
}
完整方案
系统提供了一个Fragment.isVisible()
,用于判断可见性,源码如下:
/**
* Return true if the fragment is currently visible to the user. This means
* it: (1) has been added, (2) has its view attached to the window, and
* (3) is not hidden.
*/
final public boolean isVisible() {
return isAdded() && !isHidden() && mView != null
&& mView.getWindowToken() != null && mView.getVisibility() == View.VISIBLE;
}
下面是判断和监听Fragment可见性完整的代码(不考虑直接调用Fragment.getView().setVisibility
时的监听,因为不容易实现且必要性不大)。要求所有Fragment继承BaseVisibilityFragment基类。
完整的Demo可在此下载 https://github.com/jzj1993/FragmentLifeCycle
public interface OnFragmentVisibilityChangedListener {
void onFragmentVisibilityChanged(boolean visible);
}
import android.content.Context;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.View;
/**
* Created by jzj on 16/9/5.
*/
public class BaseVisibilityFragment extends Fragment implements View.OnAttachStateChangeListener, OnFragmentVisibilityChangedListener {
/**
* ParentActivity是否可见
*/
private boolean mParentActivityVisible = false;
/**
* 是否可见(Activity处于前台、Tab被选中、Fragment被添加、Fragment没有隐藏、Fragment.View已经Attach)
*/
private boolean mVisible = false;
private BaseVisibilityFragment mParentFragment;
private OnFragmentVisibilityChangedListener mListener;
public void setOnVisibilityChangedListener(OnFragmentVisibilityChangedListener listener) {
mListener = listener;
}
@Override
public void onAttach(Context context) {
info("onAttach");
super.onAttach(context);
final Fragment parentFragment = getParentFragment();
if (parentFragment != null && parentFragment instanceof BaseVisibilityFragment) {
mParentFragment = ((BaseVisibilityFragment) parentFragment);
mParentFragment.setOnVisibilityChangedListener(this);
}
checkVisibility(true);
}
@Override
public void onDetach() {
info("onDetach");
if (mParentFragment != null) {
mParentFragment.setOnVisibilityChangedListener(null);
}
super.onDetach();
checkVisibility(false);
mParentFragment = null;
}
@Override
public void onStart() {
info("onStart");
super.onStart();
onActivityVisibilityChanged(true);
}
@Override
public void onStop() {
info("onStop");
super.onStop();
onActivityVisibilityChanged(false);
}
/**
* ParentActivity可见性改变
*/
protected void onActivityVisibilityChanged(boolean visible) {
mParentActivityVisible = visible;
checkVisibility(visible);
}
/**
* ParentFragment可见性改变
*/
@Override
public void onFragmentVisibilityChanged(boolean visible) {
checkVisibility(visible);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
info("onCreate");
super.onCreate(savedInstanceState);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
view.addOnAttachStateChangeListener(this);
}
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
checkVisibility(hidden);
}
/**
* Tab切换时会回调此方法。对于没有Tab的页面,{@link Fragment#getUserVisibleHint()}默认为true。
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
info("setUserVisibleHint = " + isVisibleToUser);
super.setUserVisibleHint(isVisibleToUser);
checkVisibility(isVisibleToUser);
}
@Override
public void onViewAttachedToWindow(View v) {
info("onViewAttachedToWindow");
checkVisibility(true);
}
@Override
public void onViewDetachedFromWindow(View v) {
info("onViewDetachedFromWindow");
v.removeOnAttachStateChangeListener(this);
checkVisibility(false);
}
/**
* 检查可见性是否变化
*
* @param expected 可见性期望的值。只有当前值和expected不同,才需要做判断
*/
private void checkVisibility(boolean expected) {
if (expected == mVisible) return;
final boolean parentVisible = mParentFragment == null ? mParentActivityVisible : mParentFragment.isFragmentVisible();
final boolean superVisible = super.isVisible();
final boolean hintVisible = getUserVisibleHint();
final boolean visible = parentVisible && superVisible && hintVisible;
info(String.format("==> checkVisibility = %s ( parent = %s, super = %s, hint = %s )",
visible, parentVisible, superVisible, hintVisible));
if (visible != mVisible) {
mVisible = visible;
onVisibilityChanged(mVisible);
}
}
/**
* 可见性改变
*/
protected void onVisibilityChanged(boolean visible) {
info("==> onFragmentVisibilityChanged = " + visible);
if (mListener != null) {
mListener.onFragmentVisibilityChanged(visible);
}
}
/**
* 是否可见(Activity处于前台、Tab被选中、Fragment被添加、Fragment没有隐藏、Fragment.View已经Attach)
*/
public boolean isFragmentVisible() {
return mVisible;
}
private void info(String s) {
if (BuildConfig.DEBUG) {
Log.i(getClass().getSimpleName() + " (" + hashCode() + ")", s);
}
}
}