解決ViewPager嵌套時Fragment的mUserVisibleHint屬性不一樣步的問題

如今新的問題又來了,當ViewPager嵌套ViewPager的時候子ViewPager中Fragment的mUserVisibleHint屬性卻不會同其父Fragment的mUserVisibleHint同步,這樣一來子ViewPager中Fragment的狀態的統計就不許確了java

舉個栗子,現有以下結構:ide

ViewPagerParent
	Fragment1
		ViewPagerChild1
			Fragment11
			Fragment12
	Fragment2
		ViewPagerChild2
			Fragment21
			Fragment22

初始化完成後的狀態是函數

ViewPagerParent
	Fragment1.mUserVisibleHint=true
		ViewPagerChild1
			Fragment11.mUserVisibleHint=true
			Fragment12.mUserVisibleHint=false
	Fragment2.mUserVisibleHint=false
		ViewPagerChild2
			Fragment21.mUserVisibleHint=true
			Fragment22.mUserVisibleHint=false

能夠明確的看出來這時候Fragment11和Fragment21的mUserVisibleHint屬性都是true,若是咱們經過上一篇中講述的方法用getUserVisibleHint()&&isResume()來記錄頁面顯示日誌的話一會兒會記錄Fragment11和Fragment21都顯示了,this

可這時候咱們能看到的只有Fragment11,因此咱們不但願Fragment21的mUserVisibleHint也是true,可ViewPager並未提供解決此問題的方法,得靠本身來解決.net

接下來滑動到Fragment2,這時候的狀態是日誌

ViewPagerParent
	Fragment1.mUserVisibleHint=false
		ViewPagerChild1
			Fragment11.mUserVisibleHint=true
			Fragment12.mUserVisibleHint=false

	Fragment2.mUserVisibleHint=true
		ViewPagerChild2
			Fragment21.mUserVisibleHint=true
			Fragment22.mUserVisibleHint=false

咱們能夠看到Fragment1的mUserVisibleHint屬性已是false了,但是其子Framgnet11依然仍是true,並不會跟父Fragment1同步,這時候咱們打開新的Activity再回來重走onResume()方法的時候全部Fragment都會重走onResume()方法,那麼這時候全部mUserVisibleHint狀態是true的Fragment都會記錄一次顯示事件code

咱們先來解決初始化時候讓Fragment21.mUserVisibleHint爲false

解決思路就是在子Fragment onAttach的時候檢查其父Fragment的mUserVisibleHint屬性的狀態,若是是false就強制將當前子Fragment的mUserVisibleHint屬性設置爲false並設置一個恢復標記(由於接下來還須要恢復) 而後在父Fragment setUserVisibleHint爲true的時候檢查其全部子Fragment,有恢復標記的子Fragment,設置其mUserVisibleHint屬性爲true,看代碼:接口

public class BaseFragment extends Fragment{
	private boolean waitingShowToUser;

	[@Override](https://my.oschina.net/u/1162528)
	public void onActivityCreated([@Nullable](https://my.oschina.net/u/2896689) Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);

		// 若是本身是顯示狀態,但父Fragment倒是隱藏狀態,就把本身也改成隱藏狀態,而且設置一個等待顯示標記
		if(getUserVisibleHint()){
			Fragment parentFragment = getParentFragment();
			if(parentFragment != null && !parentFragment.getUserVisibleHint()){
				waitingShowToUser = true;
				super.setUserVisibleHint(false);
			}
		}
	}

	[@Override](https://my.oschina.net/u/1162528)
	public void setUserVisibleHint(boolean isVisibleToUser) {
		super.setUserVisibleHint(isVisibleToUser);

		if(getActivity() != null) {
			List<Fragment> childFragmentList = getChildFragmentManager().getFragments();
			if (isVisibleToUser) {
				// 將全部正等待顯示的子Fragment設置爲顯示狀態,並取消等待顯示標記
				if (childFragmentList != null && childFragmentList.size() > 0) {
					for (Fragment childFragment : childFragmentList) {
						if (childFragment instanceof BaseFragment) {
							BaseFragment childBaseFragment = (BaseFragment) childFragment;
							if (childBaseFragment.isWaitingShowToUser()) {
								childBaseFragment.setWaitingShowToUser(false);
								childFragment.setUserVisibleHint(true);
							}
						}
					}
				}
			}
		}
	}

	public boolean isWaitingShowToUser() {
		return waitingShowToUser;
	}

	public void setWaitingShowToUser(boolean waitingShowToUser) {
		this.waitingShowToUser = waitingShowToUser;
	}
}

接下來解決父ViewPager切換的時候同步其子Fragment的mUserVisibleHint屬性

思路就是在父Fragment setUserVisibleHint爲false的時候將其全部mUserVisibleHint爲true的子Fragment都強制改成false,而後設置一個恢復標記,而後在setUserVisibleHint爲true的時候再恢復,看代碼:事件

public class BaseFragment extends Fragment{
	private boolean waitingShowToUser;

	[@Override](https://my.oschina.net/u/1162528)
	public void onActivityCreated([@Nullable](https://my.oschina.net/u/2896689) Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);

		// 若是本身是顯示狀態,但父Fragment倒是隱藏狀態,就把本身也改成隱藏狀態,而且設置一個等待顯示標記
		if(getUserVisibleHint()){
			Fragment parentFragment = getParentFragment();
			if(parentFragment != null && !parentFragment.getUserVisibleHint()){
				waitingShowToUser = true;
				super.setUserVisibleHint(false);
			}
		}
	}

	@Override
	public void setUserVisibleHint(boolean isVisibleToUser) {
		super.setUserVisibleHint(isVisibleToUser);

		// 父Fragment還沒顯示,你着什麼急
		if (isVisibleToUser) {
			Fragment parentFragment = getParentFragment();
			if (parentFragment != null && !parentFragment.getUserVisibleHint()) {
				waitingShowToUser = true;
				super.setUserVisibleHint(false);
				return;
			}
		}

		if(getActivity() != null) {
			List<Fragment> childFragmentList = getChildFragmentManager().getFragments();
			if (isVisibleToUser) {
				// 將全部正等待顯示的子Fragment設置爲顯示狀態,並取消等待顯示標記
				if (childFragmentList != null && childFragmentList.size() > 0) {
					for (Fragment childFragment : childFragmentList) {
						if (childFragment instanceof BaseFragment) {
							BaseFragment childBaseFragment = (BaseFragment) childFragment;
							if (childBaseFragment.isWaitingShowToUser()) {
								childBaseFragment.setWaitingShowToUser(false);
								childFragment.setUserVisibleHint(true);
							}
						}
					}
				}
			} else {
				// 將全部正在顯示的子Fragment設置爲隱藏狀態,並設置一個等待顯示標記
				if (childFragmentList != null && childFragmentList.size() > 0) {
					for (Fragment childFragment : childFragmentList) {
						if (childFragment instanceof BaseFragment) {
							BaseFragment childBaseFragment = (BaseFragment) childFragment;
							if (childFragment.getUserVisibleHint()) {
								childBaseFragment.setWaitingShowToUser(true);
								childFragment.setUserVisibleHint(false);
							}
						}
					}
				}
			}
		}
	}

	public boolean isWaitingShowToUser() {
		return waitingShowToUser;
	}

	public void setWaitingShowToUser(boolean waitingShowToUser) {
		this.waitingShowToUser = waitingShowToUser;
	}
}

最後咱們結合上一篇文章的需求將對Fragment.mUserVisibleHint屬性的管理封裝成一個FragmentUserVisibleController.java,以下:

/**
 * Fragment的mUserVisibleHint屬性控制器,用於準確的監聽Fragment是否對用戶可見
 * <br>
 * <br>mUserVisibleHint屬性有什麼用?
 * <br>* 使用ViewPager時咱們能夠經過Fragment的getUserVisibleHint()&&isResume()方法來判斷用戶是否可以看見某個Fragment
 * <br>* 利用這個特性咱們能夠更精確的統計頁面的顯示事件和標準化頁面初始化流程(真正對用戶可見的時候纔去請求數據)
 * <br>
 * <br>解決BUG
 * <br>* FragmentUserVisibleController還專門解決了在Fragment或ViewPager嵌套ViewPager時子Fragment的mUserVisibleHint屬性與父Fragment的mUserVisibleHint屬性不一樣步的問題
 * <br>* 例如外面的Fragment的mUserVisibleHint屬性變化時,其包含的ViewPager中的Fragment的mUserVisibleHint屬性並不會隨着改變,這是ViewPager的BUG
 * <br>
 * <br>使用方式(假設你的基類Fragment是MyFragment):
 * <br>1. 在你的MyFragment的構造函數中New一個FragmentUserVisibleController(必定要在構造函數中new)
 * <br>2. 重寫Fragment的onActivityCreated()、onResume()、onPause()、setUserVisibleHint(boolean)方法,分別調用FragmentUserVisibleController的activityCreated()、resume()、pause()、setUserVisibleHint(boolean)方法
 * <br>3. 實現FragmentUserVisibleController.UserVisibleCallback接口並實現如下方法
 * <br>&nbsp&nbsp&nbsp&nbsp* void setWaitingShowToUser(boolean):直接調用FragmentUserVisibleController的setWaitingShowToUser(boolean)便可
 * <br>&nbsp&nbsp&nbsp&nbsp* void isWaitingShowToUser():直接調用FragmentUserVisibleController的isWaitingShowToUser()便可
 * <br>&nbsp&nbsp&nbsp&nbsp* void callSuperSetUserVisibleHint(boolean):調用父Fragment的setUserVisibleHint(boolean)方法便可
 * <br>&nbsp&nbsp&nbsp&nbsp* void onVisibleToUserChanged(boolean, boolean):當Fragment對用戶可見或不可見的就會回調此方法,你能夠在這個方法裏記錄頁面顯示日誌或初始化頁面
 * <br>&nbsp&nbsp&nbsp&nbsp* boolean isVisibleToUser():判斷當前Fragment是否對用戶可見,直接調用FragmentUserVisibleController的isVisibleToUser()便可
 */
@SuppressLint("LongLogTag")
public class FragmentUserVisibleController {
	private static final String TAG = "FragmentUserVisibleController";
	public static boolean DEBUG = false;
	@SuppressWarnings("FieldCanBeLocal")
	private String fragmentName;
	private boolean waitingShowToUser;
	private Fragment fragment;
	private UserVisibleCallback userVisibleCallback;
	private List<OnUserVisibleListener> userVisibleListenerList;

	public FragmentUserVisibleController(Fragment fragment, UserVisibleCallback userVisibleCallback) {
		this.fragment = fragment;
		this.userVisibleCallback = userVisibleCallback;
		//noinspection ConstantConditions
		this.fragmentName = DEBUG ? fragment.getClass().getSimpleName() : null;
	}

	public void activityCreated() {
		if (DEBUG) {
			Log.d(TAG, fragmentName + ": activityCreated, userVisibleHint=" + fragment.getUserVisibleHint());
		}
		if (fragment.getUserVisibleHint()) {
			Fragment parentFragment = fragment.getParentFragment();
			if (parentFragment != null && !parentFragment.getUserVisibleHint()) {
				if (DEBUG) {
					Log.d(TAG, fragmentName + ": activityCreated, parent " + parentFragment.getClass().getSimpleName() + " is hidden, therefore hidden self");
				}
				userVisibleCallback.setWaitingShowToUser(true);
				userVisibleCallback.callSuperSetUserVisibleHint(false);
			}
		}
	}

	public void resume() {
		if (DEBUG) {
			Log.d(TAG, fragmentName + ": resume, userVisibleHint=" + fragment.getUserVisibleHint());
		}
		if (fragment.getUserVisibleHint()) {
			userVisibleCallback.onVisibleToUserChanged(true, true);
			callbackListener(true, true);
			if (DEBUG) {
				Log.i(TAG, fragmentName + ": visibleToUser on resume");
			}
		}
	}

	public void pause() {
		if (DEBUG) {
			Log.d(TAG, fragmentName + ": pause, userVisibleHint=" + fragment.getUserVisibleHint());
		}
		if (fragment.getUserVisibleHint()) {
			userVisibleCallback.onVisibleToUserChanged(false, true);
			callbackListener(false, true);
			if (DEBUG) {
				Log.w(TAG, fragmentName + ": hiddenToUser on pause");
			}
		}
	}

	public void setUserVisibleHint(boolean isVisibleToUser) {
		Fragment parentFragment = fragment.getParentFragment();
		if (DEBUG) {
			String parent;
			if (parentFragment != null) {
				parent = "parent " + parentFragment.getClass().getSimpleName() + " userVisibleHint=" + parentFragment.getUserVisibleHint();
			} else {
				parent = "parent is null";
			}
			Log.d(TAG, fragmentName + ": setUserVisibleHint, userVisibleHint=" + isVisibleToUser + ", " + (fragment.isResumed() ? "resume" : "pause") + ", " + parent);
		}

		// 父Fragment還沒顯示,你着什麼急
		if (isVisibleToUser) {
			if (parentFragment != null && !parentFragment.getUserVisibleHint()) {
				if (DEBUG) {
					Log.d(TAG, fragmentName + ": setUserVisibleHint, parent " + parentFragment.getClass().getSimpleName() + " is hidden, therefore hidden self");
				}
				userVisibleCallback.setWaitingShowToUser(true);
				userVisibleCallback.callSuperSetUserVisibleHint(false);
				return;
			}
		}

		if (fragment.isResumed()) {
			userVisibleCallback.onVisibleToUserChanged(isVisibleToUser, false);
			callbackListener(isVisibleToUser, false);
			if (DEBUG) {
				if (isVisibleToUser) {
					Log.i(TAG, fragmentName + ": visibleToUser on setUserVisibleHint");
				} else {
					Log.w(TAG, fragmentName + ": hiddenToUser on setUserVisibleHint");
				}
			}
		}

		if (fragment.getActivity() != null) {
			List<Fragment> childFragmentList = fragment.getChildFragmentManager().getFragments();
			if (isVisibleToUser) {
				// 顯示待顯示的子Fragment
				if (childFragmentList != null && childFragmentList.size() > 0) {
					for (Fragment childFragment : childFragmentList) {
						if (childFragment instanceof UserVisibleCallback) {
							UserVisibleCallback userVisibleCallback = (UserVisibleCallback) childFragment;
							if (userVisibleCallback.isWaitingShowToUser()) {
								if (DEBUG) {
									Log.d(TAG, fragmentName + ": setUserVisibleHint, show child " + childFragment.getClass().getSimpleName());
								}
								userVisibleCallback.setWaitingShowToUser(false);
								childFragment.setUserVisibleHint(true);
							}
						}
					}
				}
			} else {
				// 隱藏正在顯示的子Fragment
				if (childFragmentList != null && childFragmentList.size() > 0) {
					for (Fragment childFragment : childFragmentList) {
						if (childFragment instanceof UserVisibleCallback) {
							UserVisibleCallback userVisibleCallback = (UserVisibleCallback) childFragment;
							if (childFragment.getUserVisibleHint()) {
								if (DEBUG) {
									Log.d(TAG, fragmentName + ": setUserVisibleHint, hidden child " + childFragment.getClass().getSimpleName());
								}
								userVisibleCallback.setWaitingShowToUser(true);
								childFragment.setUserVisibleHint(false);
							}
						}
					}
				}
			}
		}
	}

	private void callbackListener(boolean isVisibleToUser, boolean invokeInResumeOrPause) {
		if (userVisibleListenerList != null && userVisibleListenerList.size() > 0) {
			for (OnUserVisibleListener listener : userVisibleListenerList) {
				listener.onVisibleToUserChanged(isVisibleToUser, invokeInResumeOrPause);
			}
		}
	}

	/**
	 * 當前Fragment是否對用戶可見
	 */
	@SuppressWarnings("unused")
	public boolean isVisibleToUser() {
		return fragment.isResumed() && fragment.getUserVisibleHint();
	}

	public boolean isWaitingShowToUser() {
		return waitingShowToUser;
	}

	public void setWaitingShowToUser(boolean waitingShowToUser) {
		this.waitingShowToUser = waitingShowToUser;
	}

	public void addOnUserVisibleListener(OnUserVisibleListener listener) {
		if (listener != null) {
			if (userVisibleListenerList == null) {
				userVisibleListenerList = new LinkedList<OnUserVisibleListener>();
			}
			userVisibleListenerList.add(listener);
		}
	}

	public void removeOnUserVisibleListener(OnUserVisibleListener listener) {
		if (listener != null && userVisibleListenerList != null) {
			userVisibleListenerList.remove(listener);
		}
	}

	public interface UserVisibleCallback {
		boolean isWaitingShowToUser();

		void setWaitingShowToUser(boolean waitingShowToUser);

		boolean isVisibleToUser();

		void callSuperSetUserVisibleHint(boolean isVisibleToUser);

		void onVisibleToUserChanged(boolean isVisibleToUser, boolean invokeInResumeOrPause);
	}

	public interface OnUserVisibleListener {
		void onVisibleToUserChanged(boolean isVisibleToUser, boolean invokeInResumeOrPause);
	}
}

使用起來很是簡單,可快速集成到你的Fragment中,:rem

1. 在你的基類Fragment的構造函數中New一個FragmentUserVisibleController(必定要在構造函數中new)
2. 重寫Fragment的onActivityCreated()、onResume()、onPause()、setUserVisibleHint(boolean)方法,分別調用FragmentUserVisibleController的activityCreated()、resume()、pause()、setUserVisibleHint(boolean)方法
3. 實現FragmentUserVisibleController.UserVisibleCallback接口並實現如下方法
void setWaitingShowToUser(boolean):直接調用FragmentUserVisibleController的setWaitingShowToUser(boolean)便可
void isWaitingShowToUser():直接調用FragmentUserVisibleController的isWaitingShowToUser()便可
void callSuperSetUserVisibleHint(boolean):調用父Fragment的setUserVisibleHint(boolean)方法便可
void onVisibleToUserChanged(boolean, boolean):當Fragment對用戶可見或不可見的就會回調此方法,你能夠在這個方法裏記錄頁面顯示日誌或初始化頁面
boolean isVisibleToUser():判斷當前Fragment是否對用戶可見,直接調用FragmentUserVisibleController的isVisibleToUser()便可

以下所示:

public class MyFragment extends Fragment implements FragmentUserVisibleController.UserVisibleCallback{
	private FragmentUserVisibleController userVisibleController;

	public MyFragment() {
		userVisibleController = new FragmentUserVisibleController(this, this);
	}

	@Override
	public void onActivityCreated(@Nullable Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		userVisibleController.activityCreated();
	}

	@Override
	public void onResume() {
		super.onResume();
		userVisibleController.resume();
	}

	@Override
	public void onPause() {
		super.onPause();
		userVisibleController.pause();
	}

	@Override
	public void setUserVisibleHint(boolean isVisibleToUser) {
		super.setUserVisibleHint(isVisibleToUser);
		userVisibleController.setUserVisibleHint(isVisibleToUser);
	}

	@Override
	public void setWaitingShowToUser(boolean waitingShowToUser) {
		userVisibleController.setWaitingShowToUser(waitingShowToUser);
	}

	@Override
	public boolean isWaitingShowToUser() {
		return userVisibleController.isWaitingShowToUser();
	}

	@Override
	public boolean isVisibleToUser() {
		return userVisibleController.isVisibleToUser();
	}

	@Override
	public void callSuperSetUserVisibleHint(boolean isVisibleToUser) {
		super.setUserVisibleHint(isVisibleToUser);
	}

	@Override
	public void onVisibleToUserChanged(boolean isVisibleToUser, boolean invokeInResumeOrPause) {

	}
}
相關文章
相關標籤/搜索