關於fragment到底是否可見的問題 以及 什麼時候呼叫才能真正意義上實現可見載入
今天面試的時候被問到,viewpager+fragment組合使用以及不組合 將Fragemnt與activity 直接使用,組合使用的預載入問題如何處理,不組合使用fragment又有什麼方法來判斷是否可見,我之前碰到過的就是組合使用在 setUserVisibleHint方法中判斷所以就闡述了一種 後來被告知如果不使用viewpager setUserVisibleHint是不會執行的,會執行另外一個onHiddenChanged的方法來判斷,後回來測試,在補充一點見解了。。。不喜勿噴
首先上述說到的情況要針對不行的情況進行分析
1.在activity中使用
也就是在把Fragment在xml佈局中引入,或者說用addFragment or replace方法,這樣我們可以根據Fragment的Onresume和onPause方法就可以判斷顯示隱藏
2。不同的情況來了,當我們使用FragmentManager的show hide方法來顯示和隱藏fragment的時候
提升了切換速率,Fragment沒有被銷燬,雖然不可見 但是不會執行pause方法,這時候可以來監聽onhiddenchanged方法,
注意咯重點內容 開始監聽著Fragment的生命週期執行變化,手賤碰了home 此時發現Fragment已經在螢幕上不可見了,但是並沒有走onHiddenChanged的方法反而走了onpause這時候我們要在onpause中呼叫hidefragmet方法來觸發onHiddenChanged 下面是onHiddenChanged方法
根據上面的描述我們不能完全依賴onHiddenChanged來判斷顯示隱藏要結合onpause來使用
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if(hidden){
//TODO now visible to user
} else {
//TODO now invisible to user
}
}
好了 下面我們說第三種情況
3 ViewPager+Fragment
ViewPager有一個特性 預載入左右兩側的頁面,如果有三個的話(A,B,C)當A->B 這時候雖然C 不可見-但是已經在載入了C的OnResume方法被呼叫了,在這時候我們就要判斷是否當前的Fragment是不是展示給使用者可見,也就是經常用到的fragment懶載入…..(當Fragment顯示的時候在開始載入)
注意咯重點內容
/**
* 如果是與ViewPager一起使用,呼叫的是setUserVisibleHint
*
* @param isVisibleToUser 是否顯示出來了
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
onVisible();
} else {
isVisible = false;
onInvisible();
}
}
下面我提供一個已經實現懶載入的Fragment 如果要實現只要切換fragment就要重新整理資料
在子Fragmen中重寫onVIsiable 載入資料的方法放到裡面就行後面碰到的問題 mRootView 是空 在下面有具體的解決方法
package com.xmagicj.android.lazyfragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* <pre>
* 若把初始化內容放到initData實現
* 就是採用Lazy方式載入的Fragment
* 若不需要Lazy載入則initData方法內留空,初始化內容放到initViews即可
*
* 注1:
* 如果是與ViewPager一起使用,呼叫的是setUserVisibleHint。
*
* 注2:
* 如果是通過FragmentTransaction的show和hide的方法來控制顯示,呼叫的是onHiddenChanged.
* 針對初始就show的Fragment 為了觸發onHiddenChanged事件 達到lazy效果 需要先hide再show
* eg:
* transaction.hide(aFragment);
* transaction.show(aFragment);
*
* Created by MnyZhao
* on 2015/11/2.
* </pre>
*/
public abstract class BaseFragment extends Fragment {
/**
* Fragment title
*/
public String fragmentTitle;
/**
* 是否可見狀態
*/
private boolean isVisible;
/**
* 標誌位,View已經初始化完成。
* 2016/04/29
* 用isAdded()屬性代替
* 2016/05/03
* isPrepared還是準一些,isAdded有可能出現onCreateView沒走完但是isAdded了
*/
private boolean isPrepared;
/**
* 是否第一次載入
*/
private boolean isFirstLoad = true;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// 若 viewpager 不設定 setOffscreenPageLimit 或設定數量不夠
// 銷燬的Fragment onCreateView 每次都會執行(但實體類沒有從記憶體銷燬)
// 導致initData反覆執行,所以這裡註釋掉
// isFirstLoad = true;
// 2016/04/29
// 取消 isFirstLoad = true的註釋 , 因為上述的initData本身就是應該執行的
// onCreateView執行 證明被移出過FragmentManager initData確實要執行.
// 如果這裡有資料累加的Bug 請在initViews方法裡初始化您的資料 比如 list.clear();
isFirstLoad = true;
View view = initViews(inflater, container, savedInstanceState);
isPrepared = true;
lazyLoad();
return view;
}
/**
* 如果是與ViewPager一起使用,呼叫的是setUserVisibleHint
*
* @param isVisibleToUser 是否顯示出來了
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
isVisible = true;
onVisible();
} else {
isVisible = false;
onInvisible();
}
}
/**
* 如果是通過FragmentTransaction的show和hide的方法來控制顯示,呼叫的是onHiddenChanged.
* 若是初始就show的Fragment 為了觸發該事件 需要先hide再show
*
* @param hidden hidden True if the fragment is now hidden, false if it is not
* visible.
*/
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
isVisible = true;
onVisible();
} else {
isVisible = false;
onInvisible();
}
}
/**
*可見時呼叫
*/
protected void onVisible() {
lazyLoad();
}
/**
*可見時呼叫
*/
protected void onInvisible() {
}
/**
* 不可見呼叫
*/
protected void lazyLoad() {
if (!isPrepared || !isVisible || !isFirstLoad) {
//if (!isAdded() || !isVisible || !isFirstLoad) {
return;
}
isFirstLoad = false;
initData();
}
protected abstract View initViews(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
protected abstract void initData();
public String getTitle() {
if (null == fragmentTitle) {
setDefaultFragmentTitle(null);
}
return TextUtils.isEmpty(fragmentTitle) ? "" : fragmentTitle;
}
public void setTitle(String title) {
fragmentTitle = title;
}
/**
* 設定fragment的Title直接呼叫 {@link BaseFragment#setTitle(String)},若不顯示該title 可以不做處理
*
* @param title 一般用於顯示在TabLayout的標題
*/
protected abstract void setDefaultFragmentTitle(String title);
}
後續 碰到一些列的開發問題
對於上面的BaseFragment 直接呼叫onVisable 這種情況適用於 當需要可見載入資料的Fragment 不在首個位置 否則 onVisable中載入資料繫結控制元件 mrootView 是空的 應為 在BaseFragment 中 判斷的是setUserVisableHide 此方法是在oncreateview’ 之前執行 所以在剛可見就呼叫會出現空的問題,ok 好 對於現在這種問題 我在onResume中再次呼叫了setUserVisableHide ,這樣可以達到預期的效果,看到這裡 有心的 會想到 後面的Fragment載入資料是不是也會執行兩次,這裡我回答一下各位,ViewPager 預載入是存在的取消什麼的 也行 但是很麻煩
我在這裡通過生命週期的變化來實現資料的延遲載入 也就是說你後面的Framgent的o’ncreateView 的方法在預載入的過程裡面已經被執行了
所以 從效果上來說除了第一個Fragment 會執行兩次 剩下相鄰的都是執行一次 下面各位看下程式碼估計就能理解了
package com.baisi.boost.cleaner.manager.fragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
* <pre>
* 若把初始化內容放到initData實現
* 就是採用Lazy方式載入的Fragment
* 若不需要Lazy載入則initData方法內留空,初始化內容放到initViews即可
*
* 注1:
* 如果是與ViewPager一起使用,呼叫的是setUserVisibleHint。
*
* 注2:
* 如果是通過FragmentTransaction的show和hide的方法來控制顯示,呼叫的是onHiddenChanged.
* 針對初始就show的Fragment 為了觸發onHiddenChanged事件 達到lazy效果 需要先hide再show
* eg:
* transaction.hide(aFragment);
* transaction.show(aFragment);
*
* Created by MnyZhao
* on 2015/11/2.
* </pre>
*/
public abstract class BaseFragment extends Fragment {
/**
* Fragment title
*/
public String fragmentTitle;
/**
* 是否可見狀態
*/
private boolean isVisible;
/**
* 標誌位,View已經初始化完成。
* 2016/04/29
* 用isAdded()屬性代替
* 2016/05/03
* isPrepared還是準一些,isAdded有可能出現onCreateView沒走完但是isAdded了
*/
private boolean isPrepared;
/**
* 是否第一次載入
*/
private boolean isFirstLoad = true;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// 若 viewpager 不設定 setOffscreenPageLimit 或設定數量不夠
// 銷燬的Fragment onCreateView 每次都會執行(但實體類沒有從記憶體銷燬)
// 導致initData反覆執行,所以這裡註釋掉
// isFirstLoad = true;
// 2016/04/29
// 取消 isFirstLoad = true的註釋 , 因為上述的initData本身就是應該執行的
// onCreateView執行 證明被移出過FragmentManager initData確實要執行.
// 如果這裡有資料累加的Bug 請在initViews方法裡初始化您的資料 比如 list.clear();
isFirstLoad = true;
View view = initViews(inflater, container, savedInstanceState);
isPrepared = true;
lazyLoad();
return view;
}
/**
* 如果是與ViewPager一起使用,呼叫的是setUserVisibleHint
*
* @param isVisibleToUser 是否顯示出來了
*/
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (getUserVisibleHint()) {
System.out.println("執行一次>>>>>>>>>>>>>>>>>");
isVisible = true;
onVisible();
} else {
isVisible = false;
onInvisible();
}
}
/**
* 如果是通過FragmentTransaction的show和hide的方法來控制顯示,呼叫的是onHiddenChanged.
* 若是初始就show的Fragment 為了觸發該事件 需要先hide再show
*
* @param hidden hidden True if the fragment is now hidden, false if it is not
* visible.
*/
@Override
public void onHiddenChanged(boolean hidden) {
super.onHiddenChanged(hidden);
if (!hidden) {
isVisible = true;
onVisible();
} else {
isVisible = false;
onInvisible();
}
}
/*再次呼叫可見方法*/
@Override
public void onResume() {
super.onResume();
if (getUserVisibleHint()) {
setUserVisibleHint(true);
}
}
protected void onVisible() {
lazyLoad();
}
protected void onInvisible() {
}
/**
* 要實現延遲載入Fragment內容,需要在 onCreateView
* isPrepared = true;
*/
protected void lazyLoad() {
if (!isPrepared || !isVisible || !isFirstLoad) {
//if (!isAdded() || !isVisible || !isFirstLoad) {
return;
}
isFirstLoad = false;
initData();
}
protected abstract View initViews(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
protected abstract void initData();
public String getTitle() {
if (null == fragmentTitle) {
setDefaultFragmentTitle(null);
}
return TextUtils.isEmpty(fragmentTitle) ? "" : fragmentTitle;
}
public void setTitle(String title) {
fragmentTitle = title;
}
/**
* 設定fragment的Title直接呼叫 {@link BaseFragment#setTitle(String)},若不顯示該title 可以不做處理
*
* @param title 一般用於顯示在TabLayout的標題
*/
protected abstract void setDefaultFragmentTitle(String title);
}