天天看點

《Android開發進階:從小工到專家》——第2章,第2.1節重要的View控件

本節書摘來自異步社群《android開發進階:從小工到專家》一書中的第2章,第2.1節重要的view控件,作者 何紅輝,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視

第2章 創造出豐富多彩的ui—view與動畫

android開發進階:從小工到專家

在第一章中,我們說到android的使用者界面構成,實際上就是activity由一個搭載着視圖樹的window構成。作為與使用者直接互動的元素,ui控件變得尤為重要。本章将介紹部分常用且重要的控件、自定義控件、動畫等内容,使我們進一步認識view,進入更豐富多彩的視圖世界。

2.1 重要的view控件

通常來說使用者界面都是由activity組成,activity中關聯了一個phonewindow建立,在這個視窗下則管理了一顆視圖樹。這顆視圖樹的頂級視圖就是一個viewgroup類型的decorview,decorview下就是各個視圖控件。這樣一來就組成了android豐富多彩的ui元素。如圖2-1所示。

《Android開發進階:從小工到專家》——第2章,第2.1節重要的View控件

本章我們并不會從textview、button等最基本控件談起,正如我們前文所說的,會介紹部分重要的ui控件以及它們的基本原理,本章的重點是掌握自定義view以及動畫。通過了解重要控件的基本原理以及自定義view,使我們能夠深入了解view系統,并且能夠有能力建立出自己所需的view。

2.1.1 listview與gridview

對于用android開發來說,最重要的控件應該非listview莫屬。它以清單的形式展示具體内容,并且能夠根據資料的長度自适應顯示。如圖2-2和圖2-3所示。

《Android開發進階:從小工到專家》——第2章,第2.1節重要的View控件

清單資料的顯示需要4個元素,分别為:

(1)用來展示清單的listview;

(2)用來把資料映射到listview上的adapter;

(3)需要展示的資料集;

(4)資料展示的view模闆。

首先自然是listview控件,但是該控件隻負責加載、管理視圖(每項資料稱為item view),至于有多少項資料、每一項資料是如何顯示的它并不關心。而這一切都是交給adapter類實作,通過adapter模式,使用者隻需覆寫特定的幾個函數就可以将listview的每項資料建構出來。需要實作的adapter函數為:

(1)getcount()函數——擷取資料的個數;

(2)getitem(int)函數——擷取position位置的資料;

(3)getitemid(int)函數——擷取position位置的資料id,一般直接傳回position即可;

(4)getview(int, view,viewgroup)函數——擷取position位置上的item view視圖。

因為adapter中含有要顯示的資料集合,資料集合中的元素個數也就是要展示的item view的個數,通過adapter的getcount()函數傳回;而每個資料的擷取則通過adapter的getitem(int)函數實作,根據索引直接通路集合中的元素即可;每個item view則是通過getview函數實作,在這個函數中使用者必須建構item view,然後将該position位置上資料綁定到item view上。這樣一來,資料就和視圖結合在一起了。

當listview加載時會根據資料的個數來建立item view,然後根據該view的索引從資料集合中擷取資料,調用getview擷取具體的視圖,并且與資料綁定。但是,并不是有多少資料項就會産生多少item view,android采用了視圖複用的形式來避免建立過多的item view,這樣能夠非常有效地提升性能和降低記憶體占用率。具體的設計如圖2-4所示。

在處理資料量較大時,listview會建構鋪滿螢幕所需的item view個數,當螢幕向下滾動時,第一項資料将會滾出螢幕的可見範圍之内,并且進入listview的一個recycler中,recycler會将該視圖緩存,如圖2-4中的item 1。而此時第8項也需要加載,listview首先會從recycler中擷取視圖,如果視圖存在,那麼使用者可以直接使用該緩存視圖,或者重新建立新的視圖。當然,這些步驟都是在adapter中完成的,一個典型的getview函數大緻如下:

《Android開發進階:從小工到專家》——第2章,第2.1節重要的View控件

getview函數的position就表示該視圖是第幾項資料,convertview就表示緩存的item view,parent表示該item view的父視圖,對于listview來說這個parent就代表listview本身。這裡最重要的就是convertview參數,如果有緩存那麼該參數不為空,此時直接複用該視圖;否則需要重新建立一個新的視圖,最後綁定資料并且将該item view傳回。

我們說到listview隻會展示有限數量的item view,例如8個item view就能夠鋪滿螢幕,那麼即使資料項有1000個,通過複用機制item view可以隻産生8個,這樣既節約記憶體又能很大程度上提高運作效率。複用item view機制也是優化listview等集合元件最重要的手段。

那麼當某個資料源發生變化之後如何更新listview呢?

我們知道listview運用了adapter模式,但是,在adapter類中卻還運用了觀察者模式,adapter内部有一個可觀察者類,listview則作為它的其中一個觀察者。在将adapter設定給listview時,listview會被注冊到這個觀察者對象中。代碼如下:

從以上程式中我們看到,設定adapter時建立了一個adapterdatasetobserver對象,然後注冊到madapter中。剛才不是說listview是觀察者嗎?這會兒怎麼成了adapterdatasetobserver對象?我們先放下這個疑問,繼續往下看。

首先看我們常用的adapter基類-baseadapter,部分代碼如下:

從以上程式中可以看到,注冊觀察者實際上是調用了datasetobservable對應的函數。datasetobservable擁有一個觀察者集合,當可觀察者發生變更時,就會通知觀察者做出相應的處理。代碼如下:

當adapter的資料源發生變化時,我們會調用adapter的notifydatasetchanged函數,在該函數中又會調用datasetobservable對象的notifychanged函數通知所有觀察者資料發生了變化,使觀察者進行相應的操作。代碼如下:

對于listview來說,這個觀察者就是adapterdatasetobserver對象,該類聲明在adapterview類中,也是listview中的一個父類。adapterdatasetobserver的代碼如下:

從以上程式中可以看到,在adapterdatasetobserver的onchanged函數中會調用viewgroup的requestlayout()函數進行重新政策、布局、繪制整個listview的item view,執行完這個過程之後listview的元素就發生了變化,是以,此時會根據新的資料來加載item view。

現在我們回到上面提到的問題,也就是listview并不是觀察者,而adapterdatasetobserver對象才是觀察者的問題。在adapterdata setobserver的onchanged函數中,實際上調用的卻是adapterview或者viewgroup類中的屬性或者函數完成功能,是以,adapterdataset observer隻是在外層做了一下包裝,真正核心的功能應該是listview,确切地說應該是adapterview。listview就是通過adapter模式、觀察者模式、item view複用機制實作了高效的清單顯示。

與listview 相似,gridview 同樣繼承自 abslistview,而abslistview 又是 adapterview 的子類。gridview是以同樣內建了abslistview的adapter模式、觀察者模式、item view複用機制等特性,它與listview不同的就是布局方式。listview以清單形式展示,而gridview與它的名字一樣則是通過網格布局形式展示。如圖2-5所示。

《Android開發進階:從小工到專家》——第2章,第2.1節重要的View控件

本是同根生使得listview和gridview擁有了很好的相容性,同一個adapter可以設定給listview或者gridview,不需要半點修改。當然也可以同時設定給這兩個視圖,這樣一來,兩個視圖都作為該adapter的觀察者,資料會同時顯示到這兩個視圖上。我想這就是為什麼要運用觀察者模式的緣由吧。

2.1.2 資料展示更好的實作——recyclerview

觀察者模式、adapter模式賦予了listview、gridview等視圖良好的可擴充性,但是從另一個角度看,它們似乎太過于相似了,以至于讓我們不禁思考,這樣做真的是最好的嗎?

從上文中我們知道,listview、gridview基本上隻有布局方式不一樣而已,其他的機制基本一緻。那麼有沒有更好的實作方式呢?答案是肯定的。

recyclerview就是作為listview、gridview的替代者出現的。它的設計與listview、gridview類似,也使用了adapter,不過該adapter并不是listview中的adapter,而是recyclerview的一個靜态内部類。該adapter有一個泛型參數vh,代表的就是viewholder。recyclerview還封裝了一個viewholder類型,該類型中有一個itemview字段,代表的就是每一項資料的根視圖,需要在構造函數中傳遞給viewholder對象。recyclerview這麼設計相當于android團隊将listview的adapter進行了再次封裝,把getview函數中判斷是否含有緩存的代碼段封裝到recyclerview内部,使這部分邏輯對使用者不可見。使用者隻需要告訴recyclerview每項資料是怎麼樣的以及将資料綁定到每項資料上,分别對應的函數為oncreateviewholder函數、onbindviewholder函數,當然還需要通過getitemcount告訴recyclerview有多少項資料,以往适用于listview的 adapter中的getview函數中的邏輯就不需要使用者來處理了。一個recyclerview的adapter大緻如下:

從recycleradapter中可以看到代碼量比listview的adapter要少了很多,尤其是不需要使用者判斷是否使用item view緩存,使用者隻需要完成具體的viewholder構造以及資料綁定即可。

光這點改進還不足以讓recyclerview如此光芒四射,它的另一大特點就是将布局方式抽象為layoutmanager,預設提供了linearlayoutmanager、gridlayoutmanager、staggeredgridlayoutmanager 3種布局,對應為線性布局、網格布局、交錯網格布局,如果這些都無法滿足你的需求,你還可以定制布局管理器實作特定的布局方式。如圖2-6所示分别為線性布局、網格布局、自定義布局。

《Android開發進階:從小工到專家》——第2章,第2.1節重要的View控件

recyclerview通過橋接的方式将布局職責抽離出去,使得recyclerview變得更靈活。例如,如果使用者隻需要修改recyclerview的布局方式,隻需要修改layoutmanager即可,而不需要操作複雜的recyclerview類型。而listview、gridview正好是相反的,它們隻是布局方式不一樣,但卻是兩個類型,它們覆寫了基類abslistview的layoutchildren函數來實作不同的布局。顯然,通過組合的形式要好于通過繼承,是以,recyclerview在設計上也要好于abslistview類族。

除此之外,recyclerview對于item view的控制也更為精細,可以通過itemdecotation為item view添加裝飾,也就是在item view上進行二次加工;又可以用過itemanimator為item view添加動畫。職責分明、結構清晰使得recyclerview具有了非常好的擴充性,這也是它成為未來幾年最重要控件的重要原因。

2.1.3 讓頁面顯示更流暢——viewpager

一個應用中通常都會有頁面導航,使用者根據頁面導航進入到不同的功能界面,這幾乎是每個應用的必備功能。由于android裝置都是觸摸屏,是以,通過滑動來進行頁面導航再适合不過。viewpager就是為這種場景而生的,尤其是它與fragment結合在一起使用時簡直可稱為“黑白雙煞”,android也深知其的重要性,是以,提供了幾個适用于fragment的adapter。

沒錯,又是adapter!通常來說,定制含有item view類型的控件都應該使用adapter模式,因為你不知道使用者的item view是怎樣的,你隻能通過一個adapter來進行抽象,讓使用者将具體的視圖、資料通過adapter進行操作。例如,通過getitem擷取某個資料、通過getview擷取每個item view,這樣一來變化的部分就交給使用者來實作,控件隻需關注自身的邏輯,然後通過adapter的getview來擷取每個item view即可。

viewpager内部同樣也是維護了一個視圖集合,這些視圖集合橫向布局,使用者可以通過左右滑動來進行頁面切換。如圖2-7所示。

《Android開發進階:從小工到專家》——第2章,第2.1節重要的View控件

如前文所說,viewpager通常都用于顯示fragment,而viewpager與fragment組合時通常會有一個訓示器(viewpagerindicator)表明目前顯示的是哪個頁面。如圖2-8所示。

《Android開發進階:從小工到專家》——第2章,第2.1節重要的View控件

訓示器與viewpager實際上是兩個視圖,訓示器根據viewpager的頁面數量以及提供的資料生成特定的訓示器項,當viewpager進行滑動時,訓示器上的目前頁面辨別會随之變化。

圖2-8的視圖布局程式如下:

viewpagerindicator會與viewpager進行管理,并且通過viewpager的adapter擷取到頁面數量、每個頁面的标題等資訊,然後繪制出訓示器視圖。當viewpager滾動時,訓示器視圖也會發生相應的變化,以此達到訓示頁面切換的效果。

繼續閱讀