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方法会在添加后才调用。
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);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/Maiduoudo/article/details/82760875