1. 程式人生 > >關於fragment到底是否可見的問題 以及 什麼時候呼叫才能真正意義上實現可見載入

關於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顯示的時候在開始載入)
 注意咯重點內容

setUserVisibleHint 只有在Fragment切換的時候才被呼叫,如果我有fragment-A Fragment-B Activity-C 如果我從A->C 這時候setUserVisibleHint不會被呼叫,只能通過onpause 來判斷,在其中加上hide的方法,進入的時候在onresume中加上show的方法來手動觸發,所以我們也不能完全依賴setUserVisibleHint也需要結合activity的onresume與onpause來進行處理
 

    /**
     * 如果是與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);
}