本文仅适用于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。*/@Overridepublic 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); -
} -
} -
}