1. View源碼——fitSystemWindows詳解
該方法在窗口的insets發生變化時,被調用。View調用該方法,以調整內容來適應窗口的變化。窗口的insets變化,包括status bar、軟鍵盤、navigation bar等的顯隱。
一般情況下我們不需要關心這個方法。但如果設置 SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN、SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 等標識開啟沉浸式,默認情況下,我們的內容區域就會被status bar、軟鍵盤等遮擋。
該方法的默認實現會根據insets值來設置view的padding,並返回true,防止該事件繼續傳遞(即只有一個view會真正fitSystemWindows)。要開啟該方法,需要執行 setFitsSystemWindows(true) ,或在XML中設置 android:fitsSystemWindows="true" 。
如果只需要為XML文件的根布局設置fitSystemWindows,該方法的默認實現就能滿足。如果需要適配更加復雜的布局(比如有兩個子View,一個在頂部,一個在底部,則頂部的需要根據insets設置paddingTop,底部的需要根據insets設置paddingBottom),你就需要重寫該方法,自行處理insets。
需要說明的是,如果不做任何處理,所有view接收到的insets都是一樣的(比如top是status bar的高度,bottom是軟鍵盤的高度)。該方法的執行在layout之前。
WindowInsets
該類封裝了幾種不同的insets。 mSystemWindowInsets 對應status bar、軟鍵盤等引起的insets。可用方法如下:
獲取四個邊的inset
消費掉insets,使之不再傳遞
生成新的WindowInsets對象
該方法會被第一個調用,如果設置了listener,則執行自定義的listener,否則執行 onApplyWindowInsets 。
默認情況下該方法會執行第一個分支,即執行上面的 fitSystemWindows 。api20以上,android建議覆寫該方法,而不是已廢棄的 fitSystemWindows 。
監聽fitSystemWindow事件。
listener類如下:
ViewGroup:
可以看到,從根布局開始,先執行本身的 super.dispatchApplyWindowInsets 方法,然後遍歷執行子View的 dispatchApplyWindowInsets 方法,如果被消費掉,則停止傳遞。
布局如下:
設置沉浸式:
設置軟鍵盤適配方式:
現在布局是這個樣子的:
圖1標題欄被狀態欄遮擋,圖2頁面被軟鍵盤遮擋。
再次強調一個概念,默認情況下,設置 android:fitsSystemWindows="true" 只有一個View會生效。
為根布局設置 android:fitsSystemWindows="true" ,同時為了方便觀察,給根布局設置一個灰色背景:
可以看到已經適配了軟鍵盤,但頂部toolbar區域也顯示了根布局的灰色背景,顯然默認實現滿足不了我們的需求。
解決方式有很多,這里介紹兩種比較優雅的方式。
首先需要為Toolbar也設置 android:fitsSystemWindows="true"
達到了預期效果。
自定義根布局
自定義toolbar
兩種方法實際上是等價,不過顯然還是第一種方式更友好,只需要設置一個listener就能搞定,但因為api版本限制,所以很多情況下還是要使用第二種方式。
如果覆寫了 fitSystemWindows(insets) 或者 onApplyWindowInsets(WindowInsets) ,覆寫方法中不調用對應的super方法,則不需要設置 setFitsSystemWindows(true) 或者 android:fitsSystemWindows="true" 。
原因如下:
2. ViewPager系列文章(一)- ViewPager源碼分析及載入頁面原理圖
1>:點擊 viewPager.setAdapter進入下邊源碼,會調用 populate() 方法,這個方法作用是創建和銷毀子條目(子item):
在populate()方法中:
創建ItemView:mAdapter.instantiateItem(this, position);
銷毀ItemView:mAdapter.destroyItem(this, pos, ii.object);
所以由ViewPager的源碼可以看出,ViewPager里邊無論放多少個頁面都不會內存溢出,它會不斷的去創建和銷毀view;
和 ListView、RecyclerView不一樣,ListView、RecyclerView是會不斷的復用view,而viewpager是不斷的創建和銷毀view
輪播圖剛打開默認顯示當前頁,是第一頁,默認會緩存左右兩個頁面,如果左邊沒有,只有右邊有,那麼右邊是第0頁,當前頁是第一頁;
如果你滑動到第1頁,ViewPager會默認把 左邊第0頁 和 右邊第2頁 創建出來;
如果你滑動到第2頁,ViewPager會默認把第1頁和第3頁創建出來,而原來的第0頁就會變成需要銷毀的頁面;
如果想要緩存多頁,可以調用setOffscreenPageLimit()方法:
setOffscreenPageLimit(1):ViewPager機制默認就是緩存1,表示左邊、右邊各緩存1頁,加上自己,總共是3頁,其餘頁面全部銷毀;
setOffscreenPageLimit(2):表示默認給左右各緩存2頁,共4頁,加上自己,總共緩存5頁,其餘頁面全部銷毀;
setOffscreenPageLimit(3):表示默認給左右各緩存3頁,共6頁,加上自己,總共緩存7頁,其餘頁面全部銷毀;
因為 smoothScrollTo()滑動方法也調用populate(),而populate()方法維護了當前顯示頁面和 左右緩存的頁面,就能做到無限滑動而不出問題;
A:從populate()源碼中可知:先判斷頁面是否在緩存范圍內:如果在,則addNewItem添加進來,否則在destroyItem掉;
B:ViewPager會緩存左右兩邊頁面+1(當前顯示頁面),默認認為當前頁面的 左右兩邊各有1個,用戶可以手動調用setOffscreenPageLimit()方法設置數量,如果傳的值小於1,就默認設置為1;
ViewPager實際示意圖如下: