android項(xiàng)目開發(fā)過程中和ListView打交道幾乎是大家日常工作的內(nèi)容。 除了典型的行風(fēng)格UI,還有嵌入種各樣控件的UI。比如嵌入GridView、ListView、HListView、ViewPager等等。表格嵌入表格實(shí)現(xiàn)二維數(shù)據(jù)結(jié)構(gòu),豐富了UI的表現(xiàn)能力。但隨著界面增加的復(fù)雜度,也影響界面的加載和交互響應(yīng)速度。比如像下面的界面:
根據(jù)個(gè)人實(shí)際項(xiàng)目開發(fā)經(jīng)驗(yàn),面對復(fù)雜的界面介紹幾種優(yōu)化方法:
1,只更新可見區(qū)域
這種方式利用getFirstVisiblePosition、getLastVisiblePosition獲取到ListView當(dāng)前顯示的item范圍,從而有條件的對這些item更新。如果要展示一個(gè)下載界面,里面的item實(shí)時(shí)顯示下載進(jìn)度條。如果通過adapter不停的notifyDataSetChanged實(shí)現(xiàn)刷新,那么則需要監(jiān)聽下載進(jìn)度不停的刷新整張列表。而ListView局部刷新,通過匹配Item是否要更新視圖,避免刷新整個(gè)列表也節(jié)省了更多無畏的動作。下面是實(shí)現(xiàn)局部更新的偽碼
void updateProgress(AbsListView lv,Data data) { final int fvp = lv.getFirstVisiblePosition(); final int lvp = lv.getLastVisiblePosition(); fianl ListAdapter la = lv.getAdapter(); for (int i = fvp; i <= lvp; i++) { Object item = la.getItem(i); if(checkItemNeedUpdate(item,data)){//是否需要更新 View view = lv.getChildAt(i - fvp); updateItemView(view,data);//更新item視圖 } }}
2,配合手勢更新UI
注冊O(shè)nScrollListener配合手勢FLING、SCROLL、IDLE加載顯示視圖。在圖片加載中典型的做法是在FLING的時(shí)候不加載圖片,在IDLE時(shí)加載圖片。
上圖的ListView每行內(nèi)嵌入三頁的列表,可意料的是構(gòu)造UI將非常耗時(shí)或者導(dǎo)致卡死的情況。通過配合手勢,我們就可以首次加載數(shù)據(jù)時(shí)只加載可見行里的第一頁,當(dāng)手勢在IDLE或者行內(nèi)撥動頁面時(shí)再加載其它頁。這里IDLE時(shí)去加載其他行內(nèi)頁是為了優(yōu)化體驗(yàn),從第一頁撥動到第二頁會出現(xiàn)第二頁突然出現(xiàn)一堆UI的感覺。
3,預(yù)加載視圖
由于ListView加載時(shí)復(fù)用視圖、只加載要顯示視圖,所以如果getView加載很復(fù)雜的視圖就會導(dǎo)致卡頓的情況。像下面的界面,行內(nèi)有各種不同風(fēng)格類型的卡片。那么在這種情形下預(yù)先加載要顯示的item是很好的解決方法。
所謂預(yù)加載即是在adapter構(gòu)造完在調(diào)用getView之前先把view加載解析好,在getView時(shí)直接返回view即可了。比如顯示一段loading界面執(zhí)行預(yù)加載,執(zhí)行完后直接顯示。下面是偽碼:
class PreLoader{ Map<Integer,View> mViews=new HashMap<Integer,View>(); private void realLoadView(int position, ViewGroup parent) { if(mViews.containsKey(position)){ View view=....//實(shí)際加載view動作,相當(dāng)于getView mViews.put(position,view); } } public View get(int position){ return mViews.get(position); } public void preLoadView(int startPos,int endPos,ViewGroup parent){ for(int i=startPos;i<=endPos;i++){ realLoadView(i,parent); } }}class XAdapter extends BaseAdapter{ PreLoader mPreLoader; @Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView==null){ convertView=mPreLoader.get(position); } if(convertView==null){ //實(shí)際加載view動作,相當(dāng)于原getView } return convertView; }}PreLoader preLoader=new PreLoader();XAdapter adapter= new XAdapter(data,preLoader);//構(gòu)造data數(shù)據(jù)的適配displayLoading();//顯示loading界面preLoader.preLoadView(0,adapter.getCount()-1,listView);//預(yù)加載hideLoading();//取消loading界面//下面顯示listView
4,異步加載xml布局
LayoutInflater.inflate解析xml加載視圖是同步執(zhí)行的。如果在ui線程里調(diào)用該api可能會出現(xiàn)阻塞ui的情況,遇到復(fù)雜的ui構(gòu)造動作卡死是必須滴。
翻閱LayoutInflater源碼,inflate函數(shù)主要包括兩個(gè)動作:解析xml、通過xml反射實(shí)例化視圖。那么照葫蘆畫瓢實(shí)現(xiàn)一個(gè)異步的LayoutInflater類不就是了嗎?沒錯(cuò),這是可以的。但是請注意:android各種設(shè)備滿天飛,怎么保證兼容!
LayoutInflater是個(gè)抽象類,實(shí)際的實(shí)現(xiàn)在Service里(通過getSystemService獲取LayoutInflater實(shí)例),所以不同的廠商可能會有不同的實(shí)現(xiàn)。
那么是否就沒有解決方法了呢?當(dāng)然不是,沒有辦法我寫出這個(gè)方式干嘛??紤]到兼容性問題,那么就用getSystemService(Context.LAYOUTINFLATERSERVICE)返回的LayoutInflater實(shí)例,包裝一個(gè)異步處理的實(shí)現(xiàn)。不過這個(gè)實(shí)現(xiàn)方法有點(diǎn)局限, 必須保證inflate函數(shù)的attachToRoot參數(shù)為false,因?yàn)槿绻鸻ttachToRoot=true的話,inflate會將解析實(shí)例化的視圖add到root里,由于是異步操作導(dǎo)致add方法不在ui線程里執(zhí)行可能會拋異常,不過通常情況下attachToRoot=false的場景比較多的。下面是實(shí)現(xiàn)異步的偽碼
new Thread(){ public void run(){ final View view=layoutInflater.inflate(resource,root,false); postUIDosomething(view,root);//在ui線程里干活 }}.start()
5,自定義控件
自定義控件一方面可以減少組合控件之間的層級,另外也可避免控件組合時(shí)迎合某種控件而花空心思構(gòu)造各種數(shù)據(jù)結(jié)構(gòu)。不過這個(gè)方法應(yīng)用的場景非常小。對于界面經(jīng)常變化的項(xiàng)目來說無疑增加開發(fā)和維護(hù)成本,還不如用原生控件組合而成。還受限于項(xiàng)目開發(fā)的進(jìn)度和項(xiàng)目成員的具體水平。通常的優(yōu)化也只能是對復(fù)雜UI中的容器獨(dú)立做一個(gè)控件,減少控件的層級嵌套。完全對具體的UI展示開發(fā)一個(gè)數(shù)據(jù)展示控件成本是非常高的,時(shí)間、穩(wěn)定性都需時(shí)間來考驗(yàn)。
上面介紹了本人在實(shí)際項(xiàng)目開發(fā)者中遇到各種奇葩的界面時(shí)用到的優(yōu)化方法。各種方法可能在實(shí)際使用中會交叉的派上用場。實(shí)際項(xiàng)目開發(fā)中遇到復(fù)雜到奇葩的UI,讓工程師發(fā)費(fèi)大量的時(shí)間來優(yōu)化,還不如讓設(shè)計(jì)師重新考慮設(shè)計(jì)的UI是否合理。用戶真的能接受到如此之多的界面元素和繁雜的操作嗎?
最后去下廣告時(shí)間,介紹個(gè)人開源項(xiàng)目easyadapter
通用的adapter實(shí)現(xiàn),避免重復(fù)造類似的輪子??芍苯佑糜诶^承AbsListView的View(GridView、ListVIew)。
抽象出ListView行UI構(gòu)造(GridView格子UI)。將關(guān)注點(diǎn)聚焦到行UI的構(gòu)造,輕松實(shí)現(xiàn)多樣復(fù)雜的行UI(格子UI),而且還可以通過面向?qū)ο蟮睦^承方式來復(fù)用UI。
在每行UI中可以靈活的根據(jù)當(dāng)前手勢展示視圖。比如在FLING時(shí)不顯示圖片、在SCROLL時(shí)不注冊O(shè)nClickListener等等。
歡迎下載下面的示例easyadapter-sample !