本篇文章已受權微信公衆號 dasu_Android(大蘇)獨家發佈html
老規矩,先來看看效果java
ANDROID和福利兩個Fragment是設置的Fragment可見時加載數據,也就是懶加載。圓形的旋轉加載圖標只有一個,因此,若是當前Fragment正處於加載狀態,在離開該Fragment時須要隱藏加載動畫,由於另外一個Fragment並不必定處於加載狀態,當返回Fragment時,若是仍是處於加載狀態,則要能夠實現自動顯示加載動畫,若是數據已經加載完畢則不須要再顯示出來。android
以上效果就是今天要介紹和分享的,那麼開始往下看吧。git
懶加載意思也就是當須要的時候纔會去加載github
那麼,爲何Fragment須要懶加載呢,通常咱們都會在onCreate()或者onCreateView()裏去啓動一些數據加載操做,好比從本地加載或者從服務器加載。大部分狀況下,這樣並不會出現什麼問題,可是當你使用ViewPager + Fragment的時候,問題就來了,這時就應該考慮是否須要實現懶加載了。api
ViewPager爲了讓滑動的時候能夠有很好的用戶的體驗,也就是防止出現卡頓現象,所以它有一個緩存機制。默認狀況下,ViewPager會提早建立好當前Fragment旁的兩個Fragment,舉個例子說也就是若是你當前顯示的是編號3的Fragment,那麼其實編號2和4的Fragment也已經建立好了,也就是說這3個Fragment都已經執行完 onAttach() -> onResume() 這之間的生命週期函數了。緩存
原本Fragment的 onResume()表示的是當前Fragment處於可見且可交互狀態,但因爲ViewPager的緩存機制,它已經失去了意義,也就是說咱們只是打開了「福利」這個Fragment,但其實「休息視頻」和「拓展資源」這兩個Fragment的數據也都已經加載好了。服務器
若是加載數據的操做都比較耗時或者都是相似圖片的佔用大量內存,這時就應該考慮想一想是否該實現懶加載。也就是,當我打開哪一個Fragment的時候,它纔會去加載數據。微信
當你去網上查找相關資料時,你會發現不少人推薦說把加載數據的操做放在這個函數裏,isVisibleToUser表示當前Fragment是否可見。那麼,是否真的能夠就這樣作呢?先來看個日誌:架構
題主是從 DayDataFragment 跳轉到 MeiziDataFragment 的,因此能夠看到日誌裏面:DayDataFragment打出了false,表示它不可見了。而MeiziDataFragment卻先打出了false,而後纔打出true,這是由於setUserVisibleHint()在Fragment實例化時會先調用一次,而且默認值是false,當選中當前顯示的Fragment時還會再調用一次。
因此,看上面的日誌,除了DayDataFragment外,其餘三個Fragment均沒有實例化,因此當打開MeiziDataFragment時,由於ViewPager的緩存機制,會同時建立三個Fragment的實例,因此打印了三條isVisibleToUeser: false的日誌,由於選中的是MeiziDataFragment,因此它還會觸發一次setUserVisibleHint(),而且打印出true。
那麼,是否能夠在setUserVisibleHint(boolean isVisibleToUser)裏進行數據加載操做來實現懶加載呢?
能夠是能夠,若是你只是須要數據的懶加載的話,但若是你還有如下的需求,那麼這種方式就不行了:
這邊再提一下,setUserVisibleHint()可能會在Fragment的生命週期以外被調用,也就是可能在view建立前就被調用,也可能在destroyView後被調用,因此若是涉及到一些控件的操做的話,可能會報 null 異常,由於控件還沒初始化,或者已經摧毀了。
題主稍微進行了一些封裝,自定義了一個新的回調函數onFragmentVisibleChange(boolean isVisible),能夠實現的效果有:
題主此次仍舊是從DayDataFragment 跳轉到 MeiziDataFragment, 但跟上上面的日誌圖片不一樣,這裏只打印了兩條日誌,也就是說即便有三個Fragment被實例化了,但只有顯示的那個Fragment和離開的那個Fragment纔會觸發回調函數,這樣就能夠支持咱們在可見狀態變化時進行一些操做,由於不會有多餘的false觸發。
另外,由於ViewPager緩存機制,因此題主進行了view的複用,防止onCreateView()重複的建立view,其實也就是將view設置爲成員變量,建立view時先判斷是否爲null。由於ViewPager裏對Fragment的回收和建立時,若是Fragment已經建立過了,那麼只會調用 onCreateView() -> onDestroyView() 生命函數,onCreate()和onDestroy並不會觸發,因此關於變量的初始化和賦值操做能夠在onCreate()裏進行,這樣就能夠避免重複的操做。
2016-04-21 更新:該博客封裝的懶加載實現有些不足,好比不支持數據只有第一次打開Fragment時才進行加載的應用場景,所以從新寫了篇博客,能夠移步至此觀看:再來一篇Fragment的懶加載(只加載一次哦)
最後附上代碼,另外注意一下,題主是從項目裏抽出代碼,進行一些修改,讓它儘可能能夠直接複製粘貼使用,但並無進行過測試,因此若是不行的話能夠留言,題主會查看。或者你直接到我原項目裏去查看,代碼已託管至Github上,由於項目是針對具體需求的,因此類裏面會增長不少其餘無關的代碼。再或者,你能夠嘗試本身進行封裝下,代碼不多,不到50行,理解思路就好了。
/** * Created by dasu on 2016/9/27. * https://github.com/woshidasusu/Meizi * * Viewpager + Fragment狀況下,fragment的生命週期因Viewpager的緩存機制而失去了具體意義 * 該抽象類自定義一個新的回調方法,當fragment可見狀態改變時會觸發的回調方法,介紹看下面 * * @see #onFragmentVisibleChange(boolean) */ public abstract class ViewPagerFragment extends Fragment { /** * rootView是否初始化標誌,防止回調函數在rootView爲空的時候觸發 */ private boolean hasCreateView; /** * 當前Fragment是否處於可見狀態標誌,防止因ViewPager的緩存機制而致使回調函數的觸發 */ private boolean isFragmentVisible; /** * onCreateView()裏返回的view,修飾爲protected,因此子類繼承該類時,在onCreateView裏必須對該變量進行初始化 */ protected View rootView; @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); Log.d(getTAG(), "setUserVisibleHint() -> isVisibleToUser: " + isVisibleToUser); if (rootView == null) { return; } hasCreateView = true; if (isVisibleToUser) { onFragmentVisibleChange(true); isFragmentVisible = true; return; } if (isFragmentVisible) { onFragmentVisibleChange(false); isFragmentVisible = false; } } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); initVariable(); } @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); if (!hasCreateView && getUserVisibleHint()) { onFragmentVisibleChange(true); isFragmentVisible = true; } } private void initVariable() { hasCreateView = false; isFragmentVisible = false; } /************************************************************** * 自定義的回調方法,子類可根據需求重寫 *************************************************************/ /** * 當前fragment可見狀態發生變化時會回調該方法 * 若是當前fragment是第一次加載,等待onCreateView後纔會回調該方法,其它狀況回調時機跟 {@link #setUserVisibleHint(boolean)}一致 * 在該回調方法中你能夠作一些加載數據操做,甚至是控件的操做,由於配合fragment的view複用機制,你不用擔憂在對控件操做中會報 null 異常 * * @param isVisible true 不可見 -> 可見 * false 可見 -> 不可見 */ protected void onFragmentVisibleChange(boolean isVisible) { Log.w(getTAG(), "onFragmentVisibleChange -> isVisible: " + isVisible); } }
新建類ViewPagerFragment,將上面代碼複製粘貼進去,添加須要的import語句 -> 新建你須要的Fragment類,繼承ViewPagerFragment,在onCreateView()裏對rootView進行初始化 -> 重寫onFragmentVisibleChange(),在這裏進行你須要的操做,好比數據加載,控制顯示等。
@Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { if (rootView == null) { rootView = inflater.inflate(R.layout.fragment_android, container, false); } return rootView; } @Override protected void onFragmentVisibleChange(boolean isVisible) { super.onFragmentVisibleChange(isVisible); if (isVisible) { // do things when fragment is visible // if (ListUtils.isEmpty(mDataList) && !isRefreshing()) { // setRefresh(true); // loadServiceData(false); } else { // setRefresh(false); } } }
以上就是此次的內容了,最近題主想利用 Gank公開的api,作一個相似於Meizi的應用出來,雖然這個App已經有無數人都作過了,但確實是一個很值得學習的項目,題主仍然是小白一個,因此仍是好好學習下。drakeet的Meizi項目用到了不少高級技術,好比Rxjava之類的,題主看不懂,其餘Github上一些比較出名的Meizi App要麼是MVP架構,要麼仍是用到了目前小白的我看懂的技術,因此此次就決定本身用最基礎的MVC,一些簡單經常使用的第三方庫來作這個App,畢竟路要一步一步走,若是這個完成了,收穫和體驗應該會不少(這不就收穫了這篇隨筆了嗎O(∩_∩)O),因此,若是有興趣的話,歡迎Start,歡迎指點,歡迎拍磚,你們一塊兒學習進步。
最近剛開通了公衆號,想激勵本身堅持寫做下去,初期主要分享原創的Android或Android-Tv方面的小知識,感興趣的能夠點一波關注,謝謝支持~~