編輯:Android資訊
這篇文章是前Firefox Android工程師(現在跳槽去Facebook了) Lucas Rocha所寫,文中對Android中常用的四種自定義布局方案進行了很好地分析,並結合這四種Android自定義布局方案所寫的示例項目講解了它們各自的優劣以及四種方案之間的比較。看完這篇文章,也讓我對Android 自定義布局有了進一步的了解,於是趁著興頭,我把它翻譯成中文,原文鏈接在此。
只要你寫過Android程序,你肯定使用過Android平台內建的幾個布局——RelativeLayout, LinearLayout, FrameLayout等等。 它們能幫助我們很好的構建Android UI。
這些內建的布局已經提供了很多方便的構件,但很多情況下你還是需要來定制自己的布局。
總結起來,自定義布局有兩大優點:
在這篇博文中,我將實現四種不同的自定義布局,並對它們的優缺點進行比較。它們分別是: composite view, custom composite view,flat custom view, 和 async custom views。
這些代碼實現可以在我的github上的 android-layout-samples 項目裡找到。這個app使用上面說到的四種自定義布局實現了相同的UI效果。它們使用 Picasso 來加載圖片。這個app的UI只是twitter timeline的簡化版本——沒有交互,只有布局。
好啦,我們先從最常見的自定義布局開始吧: composite view。
Composite views (也被稱為 compound views) 是眾多將多個view結合成為一個可重用UI組件的方法中最簡單的。這種方法的實現過程是這樣的:
TweetCompositeViewcode 就是一個 composite view。它繼承於 RelativeLayout,並填充了 tweet_composite_layout.xmlcode 布局文件,最後向外界暴露了 update()方法來更新它在adaptercode裡面的狀態。
上面提到的TweetCompositeView 這種實現方式能滿足大部分的情況。但是碰到某些情況就不靈了。假設你現在想要減少子視圖的數量,讓布局元素的便利更加有效。
這個時候我們可以回過頭來看看,盡管 composite views 實現起來比較簡單,但是使用這些內建的布局還是有不少的開銷的——特別是 LinearLayout 和RelativeLayout這種比較復雜的容器。由於Android平台內建布局的實現,在一次布局元素遍歷中,系統需要處理許多布局的結合和子視圖的多次測量——LinearLayout的 layout_weight 的屬性就是常見例子。
因此你可以為你的app量身定做一套子視圖的計算和定位邏輯,這樣的話你就可以極大的優化你的UI了。這種做法就是我接下來要介紹的 custom composite view.
顧名思義,一個 custom composite view 就是一個重寫了onMeasure() 和onLayout() 方法的 composite view 。因此相比之前的composite view繼承了 RelativeLayout,現在我們需要更進一步——繼承更抽象的ViewGroup。
TweetLayoutViewcode 就是通過這種技術實現的。注意現在這個實現不像 TweetComposiveView 繼承了LinearLayout ,這也就避免了 layout_weightcode這個屬性的使用了。
這個大費周折的過程通過ViewGroup’s 的measureChildWithMargins() 方法和背後的 getChildMeasureSpec() 方法計算出了每個子視圖的 MeasureSpec 。
TweetLayoutView 不能正確地處理所有可能的 layout 組合但是它也不必這樣。我們肯定需要根據特定需求來優化我們的自定義布局,這種方式可以讓我們寫出簡單高效的布局代碼。
如你所見,custom composite views 可以簡單地通過使用ViewGroup 的API就可以實現了。大部分時候,這種實現是可以滿足我們的需求的。
然而我們想更進一步的話——優化我們應用中的關鍵部分UI,比如 ListViews ,ViewPager等等。如果我們把所有的 TweetLayoutView 子視圖合並成一個單一的自定義視圖然後統一管理會怎麼樣呢?這就是我們接下來要討論的 flat custom view——參看下面的圖片。
flat custom view 就是一個完全自定義的 view ,它完全負責內部的子視圖的計算,位置安排,繪制。所以它就直接繼承了View 而不是ViewGroup。
如果你想找找現實生活中app是否存在這樣的例子,很簡單——開啟你手機“開發者模式”裡面的 “顯示布局邊界”選項,然後打開 Twitter, Gmail, 或者 Pocket這些app,它們在列表UI裡面都采用了 flat custom view。
使用 flat custom view最主要的好處就是可以極大地壓縮app 的視圖層級,進而可以進行更快的布局元素遍歷,最終可以減少內存占用。
Flat custom view 可以給你最大的自由,就好像你在一張白紙上面作畫。但是這樣的自由是有代價的:你不能使用已有的那些視圖元素了,比如 TextView 和 ImageView。沒錯,在 Canvas 上面描繪文本 的確很簡單,但要你實現 ellipsizing(就是對過長的文本截斷)呢?同樣, 在 Canvas 上面 描繪圖片確很簡單,但是如何縮放呢?這些限制同樣適用於touch events, accessibility, keyboard navigation等等。
所以使用flat custom view的底線就是:只將flat custom view應用於你的app的UI核心部分,其他的就直接依賴Android平台提供的view了。
TweetElementViewcode 就是 flat custom view。為了更容易的實現它,我創建了一個小小的自定義視圖框架叫做UIElement。你可以在 canvascode 這個包裡找到它。
UIElement 提供了和Android平台類似的 measure/layout API 。它包含了沒有圖像界面的 TextView 和 ImageView ,這兩個元素包含了幾個必需的特性——分別參看 TextElementcode 和ImageElementcode 。它還擁有自己的 inflatercode ,幫助從 布局資源文件code裡面實例化UIElement 。
注意: UIElement 還處於非常早期的開發階段,所以還有很多缺陷,不過將來隨著不斷的改進UIElement 可能會變得非常有用。
你可能覺得TweetElementView 的代碼看起來很簡單,這是因為實際代碼都在 TweetElementcode裡面——實際上TweetElementView 扮演托管的角色code。
TweetElement 裡面的布局代碼和TweetLayoutView‘非常類似,但是它使用 Picasso 請求圖片時卻不一樣code ,因為TweetElement 沒有使用ImageView。
總所周知,Android UI 框架時單線程的 。 這樣的單線程會帶來一些限制。比如,你不能在主線程之外遍歷布局元素——然而這對復雜、動態的UI是很有益處的。
假如你的app 在一個ListView 中很布局比較復雜的條目(就像大多數社交app一樣),那麼你在滑動ListView 就很有可能出現跳幀的現象,因為ListView 需要為列表中即將出現的新內容計算它們的視圖大小code和布局code。同樣的問題也會出現在GridViews,ViewPagers等等。
如果我們可以在主線程之外的線程上面對那些還沒有出現的子視圖進行布局遍歷是不是就可以解決上面的問題了?也就是說,在子視圖上面調用 measure() 和layout() 方法都不會占用主線程的時間了。
所以 async custom view 就是一個允許子視圖布局遍歷過程發生在主線程之外的實驗,這個idea是受到Facebook的Paperteam async node framework 這個視頻激發所想到的。
既然我們在主線程之外永遠接觸不到Android平台的UI組件,因此我們需要一個API在不能直接接觸到這個視圖的前提下對這個視圖的內容進行測量、布局。這恰恰就是 UIElement 框架提供給我的功能。
AsyncTweetViewcode 就是一個 async custom view。它使用了一個線程安全的 AsyncTweetElementcode 工廠類code 來定義它的內容。具體過程是一個 Smoothie 子項加載器code 在一個後台線程上對暫時不可見的AsyncTweetElement 進行創建、預測量和緩存(在內存裡面,以便後來直接使用)。
當然在實現這個異步UI的過程中我還是妥協了一些,因為你不知道如何顯示任意高度的布局占位符。比如,當布局異步傳遞過來的時候你只能在後台線程對它們的大小進行一次更改。因此當一個 AsyncTweetView 就要顯示的時候卻無法在內存裡面找到合適的AsyncTweetElement ,這個時候框架就會強制在主線程上面創建一個AsyncTweetElement code。
還有,預先加載的邏輯和內存緩存過期時間設置都需要比較好的實現來保證在主線程盡可能多地利用內存裡面的緩存布局。比如,這個方案中使用 LRU 緩存code 就不是一個明智的選擇。
盡管還存在這些限制,但是使用 async custom view 的得到的初步結果還是很有前途的。當然我也會通過重構這個UIElement 框架和使用其他類別的UI在這個領域繼續探索。讓我們靜觀其變吧。
在我們涉及到布局的時候,我們自定義的越深,我們能從Android平台所能獲得的依賴就越少。所以我們也要避免過早優化,只在確實能實實在在改善app質量和性能的區域進行完全的布局自定義。
這不是一個非黑即白的決定。在使用平台提供的UI元素和完全自定義的兩種極端之間還有很多方案——從簡單的composite views 到復雜的 async views。實際項目中,你可能會結合文中的幾種方案寫出優秀的app。
在Android Support Library19.1版本中,Android工具小組引入了幾個很酷的注解類型,供開發者在工程中使用。Support Librar
DBFlow,綜合了 ActiveAndroid, Schematic, Ollie,Sprinkles 等庫的優點。同時不是基於反射,所以性能也是非常高,效率緊
由於我們很容易習慣公式化的預置代碼,有時我們會忽略很優雅的細節。LayoutInflater以及它在Fragment的onCreateView()中填充View的
在開發中UI布局是我們都會遇到的問題,隨著UI越來越多,布局的重復性、復雜度也會隨之增長。Android官方給了幾個優化的方法,但是網絡上的資料基本上都是對官方資