導航:首頁 > 操作系統 > androidview繪制完成監聽

androidview繪制完成監聽

發布時間:2022-10-03 00:53:08

android自定義View——從零開始實現書籍翻頁效果(一)

前言 :本篇是系列博客的第三篇,這次我們要研究 書籍翻頁效果 。不知道大家平時有沒用過iReader、掌閱這些小說軟體,裡面的翻頁效果感覺十分的酷炫。有心想研究研究如何實現,於是網上找了找,發現這方面的教學資料非常少,所幸能找到 何明桂大大 的 Android 實現書籍翻頁效果----原理篇 這樣的入門博客(感謝大大 Orz),我們就以這篇博客為切入點從零實現我們自己的翻頁效果。由於這次坑比較深,預計會寫好幾期,感興趣的小夥伴可以點下關注以便及時收到更新提醒,謝謝大家的支持 ~

本篇只著重於思路和實現步驟,裡面用到的一些知識原理不會非常細地拿來講,如果有不清楚的api或方法可以在網上搜下相應的資料,肯定有大神講得非常清楚的,我這就不獻丑了。本著認真負責的精神我會把相關知識的博文鏈接也貼出來(其實就是懶不想寫那麼多哈哈),大家可以自行傳送。為了照顧第一次閱讀系列博客的小夥伴,本篇會出現一些在之前 系列博客 就講過的內容,看過的童鞋自行跳過該段即可

國際慣例,先上效果圖,本次主要實現了 基本的上下翻頁效果 右側最大翻頁距離的限制

在看這篇博客之前,希望大家能先了解一下書籍翻頁的實現原理,博客鏈接我已經貼出來了。通過原理講解我們知道,整個書籍翻頁效果界面分成了三個區域, A 為當前頁區域, B 為下一頁區域, C 為當前頁背面,如圖所示

書籍翻頁效果的實現就是要以我們 觸摸屏幕位置的坐標 為基礎繪制出這三個區域,形成模擬翻頁的特效。要繪制這三個區域,我們需要通過一組 特定的點 來完成,這些點的坐標需要通過兩個已知的點( 觸摸點 相對邊緣角 )計算得到,下圖我將各個特定點的位置和計算公式貼出來,大家對照著原理一起理解(渣畫工望體諒 ╮(╯▽╰)╭ ),其中 b 點是由 ae cj 的交點, k 點是由 ah cj 的交點

簡單總結一下, a 是觸摸點, f 是觸摸點相對的邊緣角, eh 我們設置為 af 的垂直平分線,則 g af 的中點, ab ak dj 直線 曲線cdb 是起點為 c ,控制點為 e ,終點為 b 二階貝塞爾曲線 曲線kij 是起點為 k ,控制點為 h ,終點為 j 二階貝塞爾曲線 ,區域 A B C 就由這些點和線劃分開來。我們將這些點稱為標識點,下一步就是模擬設定 a f 點的位置,將這組標識點繪制到屏幕上來驗證我們的計算公式是否正確,創建 BookPageView

實體類 MyPoint 用來存放我們的標識點坐標

界面布局:

在Activity中進行注冊

效果如圖

前文我們提到 ab ak dj 直線 曲線cdb 是起點為 c ,控制點為 e ,終點為 b 二階貝塞爾曲線 曲線kij 是起點為 k ,控制點為 h ,終點為 j 二階貝塞爾曲線 。通過觀察分析得知, 區域A 是由View 左上角 左下角 曲線cdb , 直線 ab ak 曲線kij 右上角 連接而成的區域,修改 BookPageView ,利用 path 繪制處 區域A

效果如圖

區域C 理論上應該是由點 a , b , d , i , k 連接而成的閉合區域,但由於 d i 是曲線上的點,我們沒辦法直接從 d 出發通過 path 繪制路徑連接 b 點( i , k 同理),也就不能只用 path 的情況下直接繪制出 區域C ,我們需要用 PorterDuffXfermode 方面的知識「曲線救國」。我們試著先將點 a , b , d , i , k 連接起來,觀察閉合區域與 區域A 之間的聯系。修改 BookPageView

效果如圖

我們將兩條曲線也畫出來對比觀察

觀察分析後可以得出結論, 區域C 由直線ab,bd,dj,ik,ak連接而成的區域 減去 與區域A交集部分 後剩餘的區域。於是我們設置 區域C 畫筆 Xfermode 模式為 DST_ATOP

效果如圖

最後是 區域B ,因為 區域B 處於最底層,我們直接將 區域B 畫筆 Xfermode 模式設為 DST_ATOP ,在 區域A、C 之後繪制即可,修改 BookPageView

效果如圖

翻頁可以從右下方翻自然也可以從右上方翻,我們將 f 點設在右上角,由於View上下兩部分是呈 鏡像 的,所以各標識點的位置也應該是鏡像對應的,因為 區域B和C 的繪制與 f 點沒有關系,所以我們只需要修改 區域A 的繪制邏輯,新增 getPathAFromTopRight() 方法

效果如圖

之前由於測試效果沒有對View的大小進行重新測量,在實現觸摸翻頁之前先把這個結了。重寫View的 onMeasure() 方法

我們的需求是,在上半部分翻頁時 f 點在右上角,在下半部分翻頁時 f 則在右下角,當手指離開屏幕時回到 初始狀態 ,根據需求,修改 BookPageView

在Activity中監聽View的 onTouch 狀態

注意,要設置 android:clickable true ,否則無法監聽到 ACTION_MOVE ACTION_UP 狀態

效果如圖

到這里我們已經實現了基本的翻頁效果,但要還原真實的書籍翻頁效果,我們還需要設置一些限制條件來完善我們的項目

對於一般的書本來說,最左側應該是釘起來的,也就是說如果我們從右側翻頁,翻動的距離是 有限制的 ,最下方翻頁形成的曲線起點( c 點)的x坐標不能小於0(上方同理),按照這個限定條件,修改我們的 BookPageView

效果如圖

至此本篇教程就告一段落了,當然還有許多功能需要繼續完善,例如橫向翻頁、翻頁動畫、陰影效果等等,這些都會在後面的教程中一一解決。如果大家看了感覺還不錯麻煩點個贊,你們的支持是我最大的動力~

❷ 如何構建Android MVVM 應用框架

我們先來看看什麼是MVVM,然後再一步一步來設計整個MVVM框架。
MVC、MVP、MVVM
首先,我們先大致了解下Android開發中常見的模式。
MVC
View:XML布局文件。
Model:實體模型(數據的獲取、存儲、數據狀態變化)。
Controllor:對應於Activity,處理數據、業務和UI。
從上面這個結構來看,Android本身的設計還是符合MVC架構的,但是Android中純粹作為View的XML視圖功能太弱,我們大量處理View的邏輯只能寫在Activity中,這樣Activity就充當了View和Controller兩個角色,直接導致Activity中的代碼大爆炸。相信大多數Android開發者都遇到過一個Acitivty數以千行的代碼情況吧!所以,更貼切的說法是,這個MVC結構最終其實只是一個Model-View(Activity:View&Controller)的結構。
MVP
View:對應於Activity和XML,負責View的繪制以及與用戶的交互。
Model:依然是實體模型。
Presenter:負責完成View與Model間的交互和業務邏輯。
前面我們說,Activity充當了View和Controller兩個角色,MVP就能很好地解決這個問題,其核心理念是通過一個抽象的View介面(不是真正的View層)將Presenter與真正的View層進行解耦。Persenter持有該View介面,對該介面進行操作,而不是直接操作View層。這樣就可以把視圖操作和業務邏輯解耦,從而讓Activity成為真正的View層。
但MVP也存在一些弊端:
Presenter(以下簡稱P)層與View(以下簡稱V)層是通過介面進行交互的,介面粒度不好控制。粒度太小,就會存在大量介面的情況,使代碼太過碎版化;粒度太大,解耦效果不好。同時對於UI的輸入和數據的變化,需要手動調用V層或者P層相關的介面,相對來說缺乏自動性、監聽性。如果數據的變化能自動響應到UI、UI的輸入能自動更新到數據,那該多好!
MVP是以UI為驅動的模型,更新UI都需要保證能獲取到控制項的引用,同時更新UI的時候要考慮當前是否是UI線程,也要考慮Activity的生命周期(是否已經銷毀等)。
MVP是以UI和事件為驅動的傳統模型,數據都是被動地通過UI控制項做展示,但是由於數據的時變性,我們更希望數據能轉被動為主動,希望數據能更有活性,由數據來驅動UI。
V層與P層還是有一定的耦合度。一旦V層某個UI元素更改,那麼對應的介面就必須得改,數據如何映射到UI上、事件監聽介面這些都需要轉變,牽一發而動全身。如果這一層也能解耦就更好了。
復雜的業務同時也可能會導致P層太大,代碼臃腫的問題依然不能解決。
MVVM
View:對應於Activity和XML,負責View的繪制以及與用戶交互。
Model:實體模型。
ViewModel:負責完成View與Model間的交互,負責業務邏輯。
MVVM的目標和思想與MVP類似,利用數據綁定(Data Binding)、依賴屬性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一個更加靈活高效的架構。
數據驅動
在常規的開發模式中,數據變化需要更新UI的時候,需要先獲取UI控制項的引用,然後再更新UI。獲取用戶的輸入和操作也需要通過UI控制項的引用。在MVVM中,這些都是通過數據驅動來自動完成的,數據變化後會自動更新UI,UI的改變也能自動反饋到數據層,數據成為主導因素。這樣MVVM層在業務邏輯處理中只要關心數據,不需要直接和UI打交道,在業務處理過程中簡單方便很多。
低耦合度
MVVM模式中,數據是獨立於UI的。
數據和業務邏輯處於一個獨立的ViewModel中,ViewModel只需要關注數據和業務邏輯,不需要和UI或者控制項打交道。UI想怎麼處理數據都由UI自己決定,ViewModel不涉及任何和UI相關的事,也不持有UI控制項的引用。即便是控制項改變了(比如:TextView換成EditText),ViewModel也幾乎不需要更改任何代碼。它非常完美的解耦了View層和ViewModel,解決了上面我們所說的MVP的痛點。
更新UI
在MVVM中,數據發生變化後,我們在工作線程直接修改(在數據是線程安全的情況下)ViewModel的數據即可,不用再考慮要切到主線程更新UI了,這些事情相關框架都幫我們做了。
團隊協作
MVVM的分工是非常明顯的,由於View和ViewModel之間是鬆散耦合的:一個是處理業務和數據、一個是專門的UI處理。所以,完全由兩個人分工來做,一個做UI(XML和Activity)一個寫ViewModel,效率更高。
可復用性
一個ViewModel可以復用到多個View中。同樣的一份數據,可以提供給不同的UI去做展示。對於版本迭代中頻繁的UI改動,更新或新增一套View即可。如果想在UI上做A/B Testing,那MVVM是你不二選擇。
單元測試
有些同學一看到單元測試,可能腦袋都大。是啊,寫成一團漿糊的代碼怎麼可能做單元測試?如果你們以代碼太爛無法寫單元測試而逃避,那可真是不好的消息了。這時候,你需要MVVM來拯救。
我們前面說過了,ViewModel層做的事是數據處理和業務邏輯,View層中關注的是UI,兩者完全沒有依賴。不管是UI的單元測試還是業務邏輯的單元測試,都是低耦合的。在MVVM中數據是直接綁定到UI控制項上的(部分數據是可以直接反映出UI上的內容),那麼我們就可以直接通過修改綁定的數據源來間接做一些Android UI上的測試。
通過上面的簡述以及模式的對比,我們可以發現MVVM的優勢還是非常明顯的。雖然目前Android開發中可能真正在使用MVVM的很少,但是值得我們去做一些探討和調研。
如何構建MVVM應用框架
如何分工
構建MVVM框架首先要具體了解各個模塊的分工。接下來我們來講解View、ViewModel、Model它們各自的職責所在。
View
View層做的就是和UI相關的工作,我們只在XML、Activity和Fragment寫View層的代碼,View層不做和業務相關的事,也就是我們在Activity不寫業務邏輯和業務數據相關的代碼,更新UI通過數據綁定實現,盡量在ViewModel裡面做(更新綁定的數據源即可),Activity要做的事就是初始化一些控制項(如控制項的顏色,添加RecyclerView的分割線),View層可以提供更新UI的介面(但是我們更傾向所有的UI元素都是通過數據來驅動更改UI),View層可以處理事件(但是我們更希望UI事件通過Command來綁定)。 簡單地說:View層不做任何業務邏輯、不涉及操作數據、不處理數據,UI和數據嚴格的分開。
ViewModel
ViewModel層做的事情剛好和View層相反,ViewModel只做和業務邏輯和業務數據相關的事,不做任何和UI相關的事情,ViewModel 層不會持有任何控制項的引用,更不會在ViewModel中通過UI控制項的引用去做更新UI的事情。ViewModel就是專注於業務的邏輯處理,做的事情也都只是對數據的操作(這些數據綁定在相應的控制項上會自動去更改UI)。同時DataBinding框架已經支持雙向綁定,讓我們可以通過雙向綁定獲取View層反饋給ViewModel層的數據,並對這些數據上進行操作。關於對UI控制項事件的處理,我們也希望能把這些事件處理綁定到控制項上,並把這些事件的處理統一化,為此我們通過BindingAdapter對一些常用的事件做了封裝,把一個個事件封裝成一個個Command,對於每個事件我們用一個ReplyCommand 去處理就行了,ReplyCommand 會把你可能需要的數據帶給你,這使得我們在Vie,具體見 MVVM Light Toolkit 使用指南的 Command 部分 。再強調一遍:ViewModel 不做和UI相關的事。
Model
Model層最大的特點是被賦予了數據獲取的職責,與我們平常Model層只定義實體對象的行為截然不同。實例中,數據的獲取、存儲、數據狀態變化都是Model層的任務。Model包括實體模型(Bean)、Retrofit的Service ,獲取網路數據介面,本地存儲(增刪改查)介面,數據變化監聽等。Model提供數據獲取介面供ViewModel調用,經數據轉換和操作並最終映射綁定到View層某個UI元素的屬性上。
如何協作
關於協作,我們先來看下面的一張圖:

上圖反應了MVVM框架中各個模塊的聯系和數據流的走向,我們從每個模塊一一拆分來看。那麼我們重點就是下面的三個協作。
ViewModel與View的協作 。
ViewModel與Model的協作 。
ViewModel與ViewModel的協作 。
ViewModel與View的協作

圖2中ViewModel和View是通過綁定的方式連接在一起的,綁定分成兩種:一種是數據綁定,一種是命令綁定。數據的綁定DataBinding已經提供好了,簡單地定義一些ObservableField就能把數據和控制項綁定在一起了(如TextView的text屬性),但是DataBinding框架提供的不夠全面,比如說如何讓一個URL綁定到一個ImageView,讓這個ImageView能自動去載入url指定的圖片,如何把數據源和布局模板綁定到一個ListView,讓ListView可以不需要去寫Adapter和ViewHolder相關的東西?這些就需要我們做一些工作和簡單的封裝。MVVM Light Toolkit 已經幫我們做了一部分的工作,關於事件綁定也是一樣,MVVM Light Toolkit 做了簡單的封裝,對於每個事件我們用一個ReplyCommand去處理就行了,ReplyCommand 會把可能需要的數據帶給你,這樣我們處理事件的時候也只關心處理數據就行了.
由 圖 1 中ViewModel的模塊中我們可以看出ViewModel類下面一般包含下面5個部分:
Context (上下文)
Model (數據源 java Bean)
Data Field (數據綁定)
Command (命令綁定)
Child ViewModel (子ViewModel)
我們先來看下示例代碼,然後再一一講解5個部分是幹嘛用的:
//context
private Activity context;
//model(數據源 Java Bean)
private NewsService.News news;
private TopNewsService.News topNews;
//數據綁定,綁定到UI的欄位(data field)
public final ObservableField<String> imageUrl = new ObservableField<>();
public final ObservableField<String> html = new ObservableField<>();
public final ObservableField<String> title = new ObservableField<>();
// 一個變數包含了所有關於View Style 相關的欄位
public final ViewStyle viewStyle = new ViewStyle();
//命令綁定(command)
public final ReplyCommand onRefreshCommand = new ReplyCommand<>(() -> {

})
public final ReplyCommand<Integer> onLoadMoreCommand = new ReplyCommand<>((itemCount) -> {

});

//Child ViewModel
public final ObservableList<NewItemViewModel> itemViewModel = new ObservableArrayList<>();

/** * ViewStyle 關於控制項的一些屬性和業務數據無關的Style 可以做一個包裹,這樣代碼比較美觀,
ViewModel 頁面也不會有太多太雜的欄位。 **/
public static class ViewStyle {
public final ObservableBoolean isRefreshing = new ObservableBoolean(true);
public final ObservableBoolean progressRefreshing = new ObservableBoolean(true);
}

Context
Context是幹嘛用的呢,為什麼每個ViewModel都最好需要持了一個Context的引用呢?ViewModel不處理和UI相關的事也不操作控制項,更不更新UI,那為什麼要有Context呢?原因
Model是什麼呢?其實就是數據源,可以簡單理解是我們用JSON轉過來的Bean。ViewModel要把數據映射到UI中可能需要大量對Model的數據拷貝和操作,拿Model的欄位去生成對應的ObservableField然後綁定到UI(我們不會直接拿Model的數據去做綁定展示),這里是有必要在一個ViewModel保留原始的Model引用,這對於我們是非常有用的,因為可能用戶的某些操作和輸入需要我們去改變數據源,可能我們需要把一個Bean在列表頁點擊後傳給詳情頁,可能我們需要把這個Model當做表單提交到伺服器。這些都需要我們的ViewModel持有相應的Model(數據源)。
Data Field(數據綁定)
Data Field就是需要綁定到控制項上的ObservableField欄位,這是ViewModel的必需品,這個沒有什麼好說。但是這邊有一個建議:
這些欄位是可以稍微做一下分類和包裹的。比如說可能一些欄位是綁定到控制項的一些Style屬性上(如長度、顏色、大小),對於這類針對View Style的的欄位可以聲明一個ViewStyle類包裹起來,這樣整個代碼邏輯會更清晰一些,不然ViewModel裡面可能欄位泛濫,不易管理和閱讀性較差。而對於其他一些欄位,比如說title、imageUrl、name這些屬於數據源類型的欄位,這些欄位也叫數據欄位,是和業務數據和邏輯息息相關的,這些欄位可以放在一塊。
Command(命令綁定)
Command(命令綁定)簡言之就是對事件的處理(下拉刷新、載入更多、點擊、滑動等事件處理)。我們之前處理事件是拿到UI控制項的引用,然後設置Listener,這些Listener其實就是Command。但是考慮到在一個ViewModel寫各種Listener並不美觀,可能實現一個Listener就需要實現多個方法,但是我們可能只想要其中一個有用的方法實現就好了。更重要一點是實現一個Listener可能需要寫一些UI邏輯才能最終獲取我們想要的。簡單舉個例子,比如你想要監聽ListView滑到最底部然後觸發載入更多的事件,這時候就要在ViewModel裡面寫一個OnScrollListener,然後在裡面的onScroll方法中做計算,計算什麼時候ListView滑動底部了。其實ViewModel的工作並不想去處理這些事件,它專注做的應該是業務邏輯和數據處理,如果有一個東西不需要你自己去計算是否滑到底部,而是在滑動底部自動觸發一個Command,同時把當前列表的總共的item數量返回給你,方便你通過 page=itemCount/LIMIT+1去計算出應該請求伺服器哪一頁的數據那該多好啊。MVVM Light Toolkit 幫你實現了這一點:
public final ReplyCommand<Integer> onLoadMoreCommand = new ReplyCommand<>((itemCount) -> {
int page=itemCount/LIMIT+1;
loadData(page.LIMIT)
});

接著在XML布局文件中通過bind:onLoadMoreCommand綁定上去就行了。
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
bind:onLoadMoreCommand="@{viewModel.loadMoreCommand}"/>
x

當然Command並不是必須的,你完全可以依照自己的習慣和喜好在ViewModel寫Listener,不過使用Command可以使ViewModel更簡潔易讀。你也可以自己定義更多的、其他功能的Command,那麼ViewModel的事件處理都是託管ReplyCommand 來處理,這樣的代碼看起來會比較美觀和清晰。Command只是對UI事件的一層隔離UI層的封裝,在事件觸發時把ViewModel層可能需要的數據傳給ViewModel層,對事件的處理做了統一化,是否使用的話,還是看你個人喜好了。
Child ViewModel(子ViewModel)
子ViewModel的概念就是在ViewModel裡面嵌套其他的ViewModel,這種場景還是很常見的。比如說你一個Activity裡面有兩個Fragment,ViewModel是以業務劃分的,兩個Fragment做的業務不一樣,自然是由兩個ViewModel來處理,這時候Activity對應的ViewModel裡面可能包含了兩個Fragment各自的ViewModel,這就是嵌套的子ViewModel。還有另外一種就是對於AdapterView,如ListView RecyclerView、ViewPager等。
//Child ViewModelpublic final
ObservableList<ItemViewModel> itemViewModel = new ObservableArrayList<>();

它們的每個Item其實就對應於一個ViewModel,然後在當前的ViewModel通過ObservableList 持有引用(如上述代碼),這也是很常見的嵌套的子ViewModel。我們其實還建議,如果一個頁面業務非常復雜,不要把所有邏輯都寫在一個ViewModel,可以把頁面做業務劃分,把不同的業務放到不同的ViewModel,然後整合到一個總的ViewModel,這樣做起來可以使我們的代碼業務清晰、簡短意賅,也方便後人的維護。
總的來說,ViewModel和View之前僅僅只有綁定的關系,View層需要的屬性和事件處理都是在XML裡面綁定好了,ViewModel層不會去操作UI,只是根據業務要求處理數據,這些數據自動映射到View層控制項的屬性上。關於ViewModel類中包含哪些模塊和欄位,這個需要開發者自己去衡量,我們建議ViewModel不要引入太多的成員變數,成員變數最好只有上面的提到的5種(context、model……),能不引入其他類型的變數就盡量不要引進來,太多的成員變數對於整個代碼結構破壞很大,後面維護的人要時刻關心成員變數什麼時候被初始化、什麼時候被清掉、什麼時候被賦值或者改變,一個細節不小心可能就出現潛在的Bug。太多不清晰定義的成員變數又沒有注釋的代碼是很難維護的。
另外,我們會把UI控制項的屬性和事件都通過XML(如bind:text=@{...})綁定。如果一個業務邏輯要彈一個Dialog,但是你又不想在ViewModel裡面做彈窗的事(ViewModel不希望做UI相關的事)或者說改變ActionBar上面的圖標的顏色,改變ActionBar按鈕是否可點擊,這些都不是寫在XML裡面(都是用Java代碼初始化的),如何對這些控制項的屬性做綁定呢?我們先來看下代碼:
public class MainViewModel implements ViewModel {
....
//true的時候彈出Dialog,false的時候關掉dialog
public final ObservableBoolean isShowDialog = new ObservableBoolean();
....
.....
}
// 在View層做一個對isShowDialog改變的監聽
public class MainActivity extends RxBasePmsActivity {

private MainViewModel mainViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
.....
mainViewModel.isShowDialog.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
if (mainViewModel.isShowDialog.get()) {
dialog.show();
} else {
dialog.dismiss();
}
}
});
}
...
}

簡單地說你可以對任意的ObservableField做監聽,然後根據數據的變化做相應UI的改變,業務層ViewModel只要根據業務處理數據就行,以數據來驅動UI。

❸ android 怎麼在oncreate中獲得view的坐標

這個問題大家肯定遇到過不止一次,其實很簡單,解決它也很容易,但是咱們追求的畢竟不是解決它,而是找到幾種方法去解決,並且這么解決的原理是什麼。
這里列出4種解決方案:
Activity/View#onWindowFocusChanged
這個函數的含義是:view已經初始化完畢了,寬/高已經准備好了,這個時候去獲取寬高是可以成功獲取的。但是需要注意的是onWindowFocusChanged函數會被調用多次,當Activity的窗口得到焦點和失去焦點時均會被調用一次,如果頻繁地進行onResume和onPause,那麼onWindowFocusChanged也會被頻繁地調用。
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}123456

view.post(runnable)
通過post可以將一個runnable投遞到消息隊列的尾部,然後等待UI線程Looper調用此runnable的時候,view也已經初始化好了。
v_view1.post(new Runnable() {
@Override
public void run() {
L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});1234567

ViewTreeObserver
使用ViewTreeObserver的眾多回調可以完成這個功能,比如使用OnGlobalLayoutListener這個介面,當view樹的狀態發生改變或者view樹內部的view的可見性發生改變時,onGlobalLayout方法將被回調,因此這是獲取view的寬高一個很好的時機。需要注意的是,伴隨著view樹的狀態改變等,onGlobalLayout會被調用多次。
v_view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});1234567

再來詳細介紹一下ViewTreeObserver這個類,這個類是用來注冊當view tree全局狀態改變時的回調監聽器,這些全局事件包括很多,比如整個view tree視圖的布局,視圖繪制的開始,點擊事件的改變等等。還有千萬不要在應用程序中實例化ViewTreeObserver對象,因為該對象僅是由視圖提供的。
ViewTreeObserver類提供了幾個相關函數用來添加view tree的相關監聽器:
public void addOnDrawListener (ViewTreeObserver.OnDrawListener listener)
該函數為api 16版本中添加,作用是注冊在該view tree將要繪制時候的回調監聽器,注意該函數和相關的remove函數不能在監聽器回調的onDraw()中調用。
public void (ViewTreeObserver.OnGlobalFocusChangeListener listener)
該函數用來注冊在view tree焦點改變時候的回調監聽器。
public void addOnGlobalLayoutListener (ViewTreeObserver.OnGlobalLayoutListener listener)
該函數用來注冊在該view tree中view的全局布局屬性改變或者可見性改變時候的回調監聽器。
public void addOnPreDrawListener (ViewTreeObserver.OnPreDrawListener listener)
該函數用來注冊當view tree將要被繪制時候(view 的 onDraw 函數之前)的回調監聽器。
public void addOnScrollChangedListener (ViewTreeObserver.OnScrollChangedListener listener)
該函數用來注冊當view tree滑動時候的回調監聽器,比如用來監聽ScrollView的滑動狀態。
public void addOnTouchModeChangeListener (ViewTreeObserver.OnTouchModeChangeListener listener)
該函數用來注冊當view tree的touch mode改變時的回調監聽器,回調函數onTouchModeChanged (boolean isInTouchMode)中的isInTouchMode為該view tree的touch mode狀態。
public void addOnWindowAttachListener (ViewTreeObserver.OnWindowAttachListener listener)
api 18添加,該函數用來注冊當view tree被附加到一個window上時的回調監聽器。
public void (ViewTreeObserver.OnWindowFocusChangeListener listener)
api 18添加,該函數用來注冊當window中該view tree焦點改變時候的回調監聽器。
而且對應每一個add方法都會有一個remove方法用來刪除相應監聽器。

view.measure(int widthMeasureSpec, int heightMeasureSpec)
通過手動對view進行measure來得到view的寬/高,這種情況比較復雜,這里要分情況處理,根據view的layoutparams來分:
match_parent
直接放棄,無法measure出具體的寬/高。原因很簡單,根據view的measure過程,構造此種MeasureSpec需要知道parentSize,即父容器的剩餘空間,而這個時候我們無法知道parentSize的大小,所以理論上不可能測量處view的大小。
wrap_content
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);123

注意到(1<<30)-1,我們知道MeasureSpec的前2位為mode,後面30位為size,所以說我們使用最大size值去匹配該最大化模式,讓view自己去計算需要的大小。
具體的數值(dp/px)
這種模式下,只需要使用具體數值去measure即可,比如寬/高都是100px:
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);123

源碼和結果
demo代碼如下
xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/v_view1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/ic_launcher"/>

<View
android:id="@+id/v_view2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@mipmap/ic_launcher"/>

</LinearLayout>12345678910111213141516171819

activity:
public class MainActivity extends BaseActivity{
private View v_view1;
private View v_view2;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

v_view1 = findViewById(R.id.v_view1);
v_view2 = findViewById(R.id.v_view2);

L.i("normal: v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());

v_view1.post(new Runnable() {
@Override
public void run() {
L.i("post(Runnable) : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});

v_view1.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
L.i("ViewTreeObserver : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
});

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
v_view1.measure(widthMeasureSpec, heightMeasureSpec);
L.i("measure : v_view1.getMeasuredWidth():" + v_view1.getMeasuredWidth()
+ " v_view1.getMeasuredHeight():" + v_view1.getMeasuredHeight());
v_view2.measure(widthMeasureSpec, heightMeasureSpec);
L.i("measure : v_view2.getMeasuredWidth():" + v_view2.getMeasuredWidth()
+ " v_view2.getMeasuredHeight():" + v_view2.getMeasuredHeight());

}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
L.i("onWindowFocusChanged : v_view1.getWidth():" + v_view1.getWidth()
+ " v_view1.getHeight():" + v_view1.getHeight());
}
}
041424344454647484950

log日誌:
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:28): normal: v_view1.getWidth():0 v_view1.getHeight():0
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:50): measure : v_view1.getMeasuredWidth():144 v_view1.getMeasuredHeight():144
I/[PID:2659]: [TID:1] MainActivity.onCreate(line:53): measure : v_view2.getMeasuredWidth():16777215 v_view2.getMeasuredHeight():16777215
I/[PID:2659]: [TID:1] 2.onGlobalLayout(line:42): ViewTreeObserver : v_view1.getWidth():144 v_view1.getHeight():144
I/[PID:2659]: [TID:1] 1.run(line:34): post(Runnable) : v_view1.getWidth():144 v_view1.getHeight():144
I/[PID:2659]: [TID:1] MainActivity.onWindowFocusChanged(line:61): onWindowFocusChanged : v_view1.getWidth():144 v_view1.getHeight():144123456

界面:

小的為view_1,大的為view_2,從log日誌中就發現有問題了:view_2視圖使用measure之後計算出來的寬高是錯誤的,所以View類的視圖使用measure計算出來的結果是不準確的,這點需要特別特別注意。

❹ SurfaceFlinger 原理分析

SurfaceFlinger是Android multimedia的一個部分,在Android 的實現中它是一個service,提供系統范圍內的surface composer功能,它能夠將各種應用程序的2D、3D surface進行組合。

每個應用程序可能對應著一個或者多個圖形界面,而每個界面我們就稱之為一個surface ,或者說是window ,在上面的圖中我們能看到4 個surface ,一個是home 界面,還有就是紅、綠、藍分別代表的3個surface ,而兩個button 實際是home surface 裡面的內容。我們需要考慮一下情況:

在實際中對這些Surface 進行merge 可以採用兩種方式,一種就是採用軟體的形式來merge ,還一種就是採用硬體的方式,軟體的方式就是我們的 SurfaceFlinger ,而硬體的方式就是 Overlay

因為硬體merge 內容相對簡單,我們首先來看overlay 。以IMX51 為例子,當IPU 向內核申請FB 的時候它會申請3 個FB ,一個是主屏的,還一個是副屏的,還一個就是Overlay 的。 簡單地來說,Overlay就是我們將硬體所能接受的格式數據和控制信息送到這個Overlay FrameBuffer,由硬體驅動來負責merge Overlay buffer和主屏buffer中的內容。

一般來說現在的硬體都只支持一個Overlay,主要用在視頻播放以及camera preview上,因為視頻內容的不斷變化用硬體Merge比用軟體Merge要有效率得多,下面就是使用Overlay和不使用Overlay的過程:

surfaceFlinger 只是負責 merge Surface 的控制,比如說計算出兩個 Surface 重疊的區域,至於 Surface 需要顯示的內容,則通過 skia,opengl 和 pixflinger 來計算。
創建過程

SurfaceFlinger 是一個線程類,它繼承了 Thread 類。當創建 SurfaceFlinger 這個服務的時候會啟動一個 SurfaceFlinger 監聽線程,這個線程會一直等待事件的發生,比如說需要進行 sruface flip ,或者說窗口位置大小發生了變化等,一旦產生這些事件,SurfaceComposerClient 就會通過 IBinder 發出信號,這個線程就會結束等待處理這些事件,處理完成以後會繼續等待,如此循環。
SurfaceComposerClient 和 SurfaceFlinger 是通過 SurfaceFlingerSynchro 這個類來同步信號的,其實說穿了就是一個條件變數。監聽線程等待條件的值一旦變成 OPEN 就結束等待並將條件置成 CLOSE 然後進行事件處理,處理完成以後再繼續等待條件的值變成 OPEN ,而 Client 的Surface 一旦改變就通過 IBinder 通知 SurfaceFlinger 將條件變數的值變成 OPEN ,並喚醒等待的線程,這樣就通過線程類和條件變數實現了一個動態處理機制。

窗口狀態變化的處理是一個很復雜的過程,SurfaceFlinger 只是執行 Windows Manager 的指令,由 Windows manager 來決定什麼是偶改變大小、位置、透明度、以及如何調整layer 之間的順序, SurfaceFlinger 僅僅只是執行它的指令。

普通的Android控制項,例如TextView、Button和CheckBox等,它們都是將自己的UI繪制在宿主窗口的繪圖表面之上,這意味著它們的UI是在應用程序的主線程中進行繪制的。由於應用程序的主線程除了要繪制UI之外,還需要及時地響應用戶輸入,否則系統就會認為應用程序沒有響應了。而對於一些游戲畫面,或者攝像頭預覽、視頻播放來說,它們的UI都比較復雜,而且要求能夠進行高效的繪制。這時候就必須要給那些需要復雜而高效UI的視圖生成一個獨立的繪圖表面,以及使用一個獨立的線程來繪制這些視圖的UI。

SurfaceFlinger服務運行在Android系統的System進程中,它負責管理Android系統的幀緩沖區(Frame Buffer)。Android應用程序為了能夠將自己的UI繪制在系統的幀緩沖區上,它們就必須要與SurfaceFlinger服務進行通信。

在APP端執行draw的時候,數據很明顯是要繪制到APP的進程空間,但是視圖窗口要經過SurfaceFlinger圖層混排才會生成最終的幀,而SurfaceFlinger又運行在另一個獨立的服務進程,那麼View視圖的數據是如何在兩個進程間傳遞的呢,普通的Binder通信肯定不行,因為Binder不太適合這種數據量較大的通信,那麼View數據的通信採用的是什麼IPC手段呢?答案就是共享內存,更精確的說是匿名共享內存。共享內存是Linux自帶的一種IPC機制,Android直接使用了該模型,不過做出了自己的改進,進而形成了Android的匿名共享內存(Anonymous Shared Memory-Ashmem)。通過Ashmem,APP進程同SurfaceFlinger共用一塊內存,如此,就不需要進行數據拷貝,APP端繪制完畢,通知SurfaceFlinger端合成,再輸出到硬體進行顯示即可。

在每一個Android應用程序與SurfaceFlinger服務之間的連接上加上一塊用來傳遞UI元數據的匿名共享內存,這個共享內存就是 SharedClient

在每一個SharedClient裡面,有至多31個SharedBufferStack。SharedBufferStack就是Android應用程序和SurfaceFlinger 的緩沖區堆棧。用來緩沖 UI 元數據。
一般我們就繪制UI的時候,都會採用一種稱為「雙緩沖」的技術。雙緩沖意味著要使用兩個緩沖區,其中一個稱為Front Buffer,另外一個稱為Back Buffer。UI總是先在Back Buffer中繪制,然後再和Front Buffer交換,渲染到顯示設備中。這下就可以理解SharedBufferStack的含義了吧?SurfaceFlinger服務只不過是將傳統的「雙緩沖」技術升華和抽象為了一個SharedBufferStack。可別小看了這個升華和抽象,有了SharedBufferStack之後,SurfaceFlinger 服務就可以使用N個緩沖區技術來繪制UI了。N值的取值范圍為2到16。例如,在Android 2.3中,N的值等於2,而在Android 4.1中,據說就等於3了。

在SurfaceFlinger服務中,每一個SharedBufferStack都對應一個Surface,即一個窗口。這樣,我們就可以知道為什麼每一個SharedClient裡麵包含的是一系列SharedBufferStack而不是單個SharedBufferStack: 一個SharedClient對應一個Android應用程序,而一個Android應用程序可能包含有多個窗口 ,即Surface。從這里也可以看出,一個Android應用程序至多可以包含31個Surface。

SharedBufferStack中的 緩沖區只是用來描述UI元數據的 ,這意味著它們不包含真正的UI數據。 真正的UI數據保存在GraphicBuffer中 ,後面我們再描述GaphicBuffer。因此,為了完整地描述一個UI,SharedBufferStack中的每一個已經使用了的緩沖區都對應有一個GraphicBuffer,用來描述真正的UI數據。當SurfaceFlinger服務緩制Buffer-1和Buffer-2的時候,就會找到與它們所對應的GraphicBuffer,這樣就可以將對應的UI繪制出來了。

當Android應用程序需要 更新一個Surface 的時候,它就會找到與它所對應的SharedBufferStack,並且從它的空閑緩沖區列表的尾部取出一個空閑的Buffer。我們假設這個取出來的空閑Buffer的編號為index。接下來Android應用程序就請求SurfaceFlinger服務為這個編號為index的 Buffer分配一個圖形緩沖區GraphicBuffer

SurfaceFlinger 服務分配好圖形緩沖區 GraphicBuffer 之後,會將它的編號設置為 index,然後再將這個圖形緩沖區 GraphicBuffer 返回給 Android 應用程序訪問。Android應用程序得到了 SurfaceFlinger 服務返回的圖形緩沖區 GraphicBuffer 之後,就在裡面 寫入UI數據 。寫完之後,就將與它所對應的緩沖區,即編號為 index 的 Buffer,插入到對應的 SharedBufferStack 的已經使用了的 緩沖區列表的頭部 去。這一步完成了之後,Android 應用程序就通知 SurfaceFlinger 服務去繪制那些保存在已經使用了的緩沖區所描述的圖形緩沖區GraphicBuffer了。用上面例子來說,SurfaceFlinger服務需要繪制的是編號為1和2的Buffer所對應的圖形緩沖區GraphicBuffer。由於SurfaceFlinger服務知道編號為1和2的 Buffer 所對應的圖形緩沖區 GraphicBuffer 在哪裡,因此,Android 應用程序只需要告訴 SurfaceFlinger 服務要繪制的 Buffer 的編號就OK了。 當一個已經被使用了的Buffer被繪制了之後,它就重新變成一個空閑的 Buffer 了

SharedBufferStack 是在 Android 應用程序和 SurfaceFlinger 服務之間共享的,但是,Android 應用程序和 SurfaceFlinger 服務使用 SharedBufferStack 的方式是不一樣的,具體來說,就是 Android 應用程序關心的是它裡面的空閑緩沖區列表,而 SurfaceFlinger 服務關心的是它裡面的已經使用了的緩沖區列表。從SurfaceFlinger服務的角度來看,保存在 SharedBufferStack中 的已經使用了的緩沖區其實就是在排隊等待渲染。

為了方便 SharedBufferStack 在 Android 應用程序和 SurfaceFlinger 服務中的訪問,Android 系統分別使用 SharedBufferClient 和 SharedBufferServer 來描述 SharedBufferStack ,其中,SharedBufferClient 用來在Android 應用程序這一側訪問 SharedBufferStack 的空閑緩沖區列表,而 SharedBufferServer 用來在SurfaceFlinger 服務這一側訪問 SharedBufferStack 的排隊緩沖區列表。

只要 SharedBufferStack 中的 available 的 buffer 的數量大於0, SharedBufferClient 就會將指針 tail 往前移一步,並且減少 available 的值,以便可以獲得一個空閑的 Buffer。當 Android 應用程序往這個空閑的 Buffer 寫入好數據之後,它就會通過 SharedBufferClient 來將它添加到 SharedBufferStack 中的排隊緩沖區列表的尾部去,即指針 queue_head 的下一個位置上。

當 Android 應用程序通知 SurfaceFlinger 服務更新UI的時候,只要對應的 SharedBufferStack 中的 queued 的緩沖區的數量大於0,SharedBufferServer 就會將指針 head 的下一個Buffer繪制出來,並且將指針 head 向前移一步,以及將 queued 的值減1。

參考:
https://blog.csdn.net/luoshengyang/article/details/7846923

❺ 如何使用ScrollView

填充圖案的定義也有兩種,一種是按毫米定義的,一種是按英寸定義的,在公制圖紙中通常是按毫米定義的。
一、結構
public class ScrollView extends FrameLayout
java.lang.Object
Android.view.View
android.view.ViewGroup
android.widget.FrameLayout
android.widget.ScrollView
二、概述
一種可供用戶滾動的層次結構布局容器,允許顯示比實際多的內容。ScrollView是一種FrameLayout,意味需要在其上放置有自己滾動內容的子元素。子元素可以是一個復雜的對象的布局管理器。通常用的子元素是垂直方向的LinearLayout,顯示在最上層的垂直方向可以讓用戶滾動的箭頭。
TextView類也有自己的滾動功能,所以不需要使用ScrollView,但是只有兩個結合使用,才能保證顯示較多內容時候的效率。但只有兩者結合使用才可以實現在一個較大的容器中一個文本視圖效果。
ScrollView只支持垂直方向的滾動。

三、構造函數
public ScrollView (Context context)
創建一個默認屬性的ScrollView實例
public ScrollView (Context context, AttributeSet attrs)
創建一個帶有attrs屬性的ScrollView 實例。
public ScrollView (Context context, AttributeSet attrs, int defStyle)
創建一個帶有attrs屬性,並且指定其默認樣式的ScrollView實例。
四、公共方法
public void addView (View child)
添加子視圖。如果事先沒有給子視圖設置layout參數,會採用當前ViewGroup的默認參數來設置子視圖。
參數
child 所添加的子視圖
public void addView (View child, int index)
添加子視圖。如果事先沒有給子視圖設置layout參數,會採用當前ViewGroup的默認參數來設置子視圖。
參數
child 所添加的子視圖
index 添加子視圖的位置
public void addView (View child, int index, ViewGroup.LayoutParams params)
根據指定的layout參數添加子視圖
參數
child 所添加的子視圖
index 添加子視圖的位置
params 為子視圖設置的layout參數

public void addView (View child, ViewGroup.LayoutParams params)
根據指定的layout參數添加子視圖。
參數
child 所添加的子視圖
params 為子視圖設置的layout參數

public boolean arrowScroll (int direction)
響應點擊上下箭頭時對滾動條滾動的處理。
參數
direction 按下的箭頭所對應的方向
返回值
如果我們處理(消耗)了此事件返回true,否則返回false。

public void computeScroll ()
被父視圖調用,用於必要時候對其子視圖的值(mScrollX和mScrollY)進行更新。典型的情況如:父視圖中某個子視圖使用一個Scroller對象來實現滾動操作,會使得此方法被調用。

public boolean dispatchKeyEvent (KeyEvent event)
發送一個key事件給當前焦點路徑的下一個視圖。此焦點路徑從視圖樹的頂層執行直到當前焦點視圖。如果此視圖為焦點視圖,將為自己發送。否則,會為當前焦點路徑的下一個節點發送。此方法也會激起一個key監聽器。
參數
event 發送的key事件
返回值
事件被處理返回true,否則返回false。

public void draw (Canvas canvas)
手動繪制視圖(及其子視圖)到指定的畫布(Canvas)。這個視圖必須在調用這個函數之前做好了整體布局。當實現一個視圖時,不需要繼承這個方法;相反,你應該實現onDraw(Canvas)方法。
參數
canvas 繪制視圖的畫布

public boolean executeKeyEvent (KeyEvent event)
當接收到key事件時,用戶可以調用此函數來使滾動視圖執行滾動,類似於處理由視圖體系發送的事件。
參數
event 需要執行key的事件
返回值
事件被處理返回true,否則返回false。

public void fling (int velocityY)
滾動視圖的滑動(fling)手勢。(譯者註: 如何監聽android的屏幕滑動停止事件)
參數
velocityY Y方向的初始速率。正值表示手指/游標向屏幕下方滑動,而內容將向上滾動。

public boolean fullScroll (int direction)
對響應「home/end」短按時響應滾動處理。此方法將視圖滾動到頂部或者底部,並且將焦點置於新的可視區域的最頂部/最底部組件。若沒有適合的組件做焦點,當前的ScrollView會收回焦點。
參數
direction滾動方向:FOCUS_UP表示視圖向上滾動;FOCUS_DOWN表示視圖向下滾動
返回值
若key事件被消耗(consumed)返回true,其他情況返回false。

public int getMaxScrollAmount ()
返回值
當前滾動視圖響應箭頭事件能夠滾動的最大數。

public boolean isFillViewport ()
指示當前ScrollView的內容是否被拉伸以填充視圖可視范圍(譯者註:viewport可視范圍,參見決定Scrollviewer裡面Control的可視范圍)。
返回值
內容填充視圖返回true,否則返回false。

public boolean isSmoothScrollingEnabled ()
返回值
按箭頭方向滾動時,是否顯示滾動的平滑效果。

public boolean onInterceptTouchEvent (MotionEvent ev)
實現此方法是為了攔截所有觸摸屏幕時的運動事件。可以像處理發送給子視圖的事件一樣去監視這些事件,並且獲取當前手勢在任意點的ownership
使用此方法時候需要注意,因為它與View.onTouchEvent(MotionEvent)有相當復雜的交互,並且前提需要正確執行View.onTouchEvent(MotionEvent)。事件將按照如下順序接收到:
1. 收到down事件
2. Down事件或者由視圖組的一個子視圖處理,或者被用戶自己的onTouchEvent()方法處理;此處理意味你應該執行onTouchEvent()時返回true,這樣才能繼續看到剩下的手勢(取代找一個父視圖處理)。如果onTouchEvent()返回true時,你不會收到onInterceptTouchEvent()的任何事件並且所有對觸摸的處理必須在onTouchEvent()中發生。
3. 如果此方法返回false,接下來的事件(up to and including the final up)將最先被傳遞當此,然後是目標的onTouchEvent()。
4. 如果返回true,將不會收到以下任何事件:目標view將收到同樣的事件但是會伴隨ACTION_CANCEL,並且所有的更進一步的事件將會傳遞到你自己的onTouchEvent()方法中而不會再在這里出現。
參數
ev 體系向下發送的動作事件
返回值
如果將運動事件從子視圖中截獲並且通過onTouchEvent()發送到當前ViewGroup ,返回true。當前目標將會收到ACTION_CANCEL事件,並且不再會有其他消息傳遞到此。
(譯者註:onInterceptTouchEvent和onTouchEvent調用時序)

public boolean onTouchEvent (MotionEvent ev)
執行此方法為了處理觸摸屏幕的運動事件。
參數
ev 運動事件
返回值
事件被處理返回true,其它返回false。

public boolean pageScroll (int direction)
響應短按「page up/ down」時候對滾動的處理。此方法將向上或者向下滾動一屏,並且將焦點置於新可視區域的最上/最下。如果沒有適合的component作為焦點,當前scrollView將收回焦點。
參數
direction 滾動方向:FOCUS_UP表示向上翻一頁,FOCUS_DOWN表示向下翻一頁。
返回值
此key事件被消耗(cosumed)返回true,其他返回false。

public void requestChildFocus (View child, View focused)
當父視圖的一個子視圖的要獲得焦點時,調用此方法。
參數
child 要獲得焦點的父視圖的子視圖。此視圖包含了焦點視圖。如果沒有特殊徐要求,此視圖實際上就是焦點視圖。
focused 子視圖的子孫視圖並且此子孫視圖是真正的焦點視圖

public boolean requestChildRectangleOnScreen (View child, Rect rectangle, boolean immediate)
當組里的某個子視圖需要被定位在屏幕的某個矩形范圍時,調用此方法。重載此方法的ViewGroup可確認以下幾點:
* 子項目將是組里的直系子項
* 矩形將在子項目的坐標體系中
重載此方法的ViewGroup應該支持以下幾點:
* 若矩形已經是可見的,則沒有東西會改變
* 為使矩形區域全部可見,視圖將可以被滾動顯示
參數
child 發出請求的子視圖
rectangle 子項目坐標系內的矩形,即此子項目希望在屏幕上的定位
immediate 設為true,則禁止動畫和平滑移動滾動條
返回值
進行了滾動操作的這個組(group),是否處理此操作。
public void requestLayout ()
當有改變引起當前視圖重新布局時,調用此函數。它將規劃一個視圖樹的layout路徑。

public void scrollTo (int x, int y)
設置當前視圖滾動到的位置。此函數會引起對onScrollChanged(int, int, int, int)函數的調用並且會讓視圖更新。
當前版本取消了在子視圖中的滾動。
參數
x 滾動到的X位置
y 滾動到的Y位置

public void setFillViewport (boolean fillViewport)
設置當前滾動視圖是否將內容高度拉伸以填充視圖可視范圍(譯者註:viewport可視范圍,參見決定Scrollviewer裡面Control的可視范圍)。
參數
fillViewport 設置為true表示拉伸內容高度來適應視口邊界;其他設為false。
public void setOverScrollMode (int mode)
為視圖設置over-scroll模式。有效的over-scroll模式有OVER_SCROLL_ALWAYS(預設值),OVER_SCROLL_IF_CONTENT_SCROLLS(只允許當視圖內容大過容器時,進行over-scrolling)和OVER_SCROLL_NEVER。只有當視圖可以滾動時,此項設置才起作用。
(譯者註:這個函數是2.3 r1 中新增的,API Level 9。關於over-scroll這里譯為彈性滾動,即,參見帖子:類似iPhone的彈性ListView滾動)
參數
mode The new over-scroll mode for this view.
public void setSmoothScrollingEnabled (boolean smoothScrollingEnabled)
用來設置箭頭滾動是否可以引發視圖滾動。
參數
smoothScrollingEnabled 設置箭頭滾動是否可以引起內容的滾動的bool值
public final void smoothScrollBy (int dx, int dy)
類似於scrollBy(int, int),但是滾動時候是平緩的而不是立即滾動到某處。
參數
dx 在X方向滾動的像素數
dy 在Y方向滾動的像素數
public final void smoothScrollTo (int x, int y)
類似於scrollTo(int, int),但是滾動時候是平緩的而不是立即滾動到某處。
參數
x 要滾動到位置的X坐標
y 要滾動到位置的Y坐標
五、受保護方法
protected int (Rect rect)
計算X方向滾動的總合,以便在屏幕上顯示子視圖的完整矩形(或者,若矩形寬度超過屏幕寬度,至少要填滿第一個屏幕大小)。
參數
rect 矩形
返回值
滾動差值
protected int computeVerticalScrollOffset ()
計算垂直方向滾動條的滑塊的偏移。此值用來計算滾動條軌跡的滑塊的位置。
范圍可以以任意單位表示,但是必須與computeVerticalScrollRange()和computeVerticalScrollExtent()的單位一致。
預設的偏移是在當前視圖滾動的偏移。
返回值
滾動條的滑塊垂直方向的偏移。
protected int computeVerticalScrollRange ()
滾動視圖的可滾動范圍是所有子元素的高度。
返回值
由垂直方向滾動條代表的所有垂直范圍,預設的范圍是當前視圖的畫圖高度。
protected float getBottomFadingEdgeStrength ()
返回滾動底部的能見度。能見度的值的范圍是0.0(沒有消失)到1.0(完全消失)之間。預設的執行返回值為0.0或者1.0,而不是他們中間的某個值。滾動時子類需要重載這個方法來提供一個平緩的漸隱的實現。
返回值
滾動底部能見度,值的范圍在浮點數0.0f到1.0f之間。
protected float getTopFadingEdgeStrength ()
返回滾動頂部的能見度。能見度的值的范圍是0.0(沒有消失)到1.0(完全消失)之間。預設的執行返回值為0.0或者1.0,而不是他們中間的某個值。滾動時子類需要重載這個方法來提供一個平緩的漸隱的實現。
返回值
滾動頂部能見度,值的范圍在浮點數0.0f到1.0f之間。
protected void measureChild (View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)
要求當前視圖的一個子視圖測量自己,同時兼顧到當前視圖的MeasureSpec的要求和它的空白。子視圖必須有MarginLayoutParams。比較復雜的工作是在getChildMeasureSpec中完成的。
參數
child 需要自己測量的子視圖
parentWidthMeasureSpec 當前視圖要求的寬度
parentHeightMeasureSpec 當前視圖要求的寬度
protected void measureChildWithMargins (View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
要求當前視圖的一個子視圖測量自己,同時兼顧到當前視圖的MeasureSpec的要求和它的空白和邊界。子視圖必須有MarginLayoutParams。比較復雜的工作是在getChildMeasureSpec中完成的。
參數
child 需要測量的子視圖
parentWidthMeasureSpec 當前視圖要求的寬度
widthUsed 水平方向上由父視圖使用的空白 (也可能是視圖的其他子視圖使用的)
parentHeightMeasureSpec 當前視圖要求的寬度
heightUsed 垂直方向上由父視圖使用的空白 (也可能是視圖的其他子視圖使用的)
protected void onLayout (boolean changed, int l, int t, int r, int b)
當前視圖需要為子視圖分配大小和位置時候調用,子類繼承必須要重載此方法並調用自己子視圖的layout函數。
參數
changed 當前視圖的新的大小或者位置
l 相對父視圖,左邊界位置
t 相對父視圖,上邊界位置
r 相對父視圖,右邊界位置
b 相對父視圖,下邊界位置
protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec)
測量視圖以確定其內容寬度和高度。此方法被measure(int, int)調用。需要被子類重寫以提供對其內容准確高效的測量。
約定:當重寫此方法時,你必須調用setMeasuredDimension(int, int)來保存當前視圖view的寬度和高度。不成功調用此方法將會導致一個IllegalStateException異常,是由measure(int, int)拋出。所以調用父類的onMeasure(int, int)方法是必須的。
父類的實現是以背景大小為默認大小,除非MeasureSpec(測量細則)允許更大的背景。子類可以重寫onMeasure(int,int)以對其內容提供更佳的尺寸。
如果此方法被重寫,那麼子類的責任是確認測量高度和測量寬度要大於視圖view的最小寬度和最小高度(getSuggestedMinimumHeight() 和 getSuggestedMinimumWidth()),使用這兩個方法可以取得最小寬度和最小高度。
參數
widthMeasureSpec 受主窗口支配的水平空間要求。這個需求通過 View.MeasureSpec.進行編碼。
heightMeasureSpec 受主窗口支配的垂直空間要求。這個需求通過 View.MeasureSpec.進行編碼。
protected void onOverScrolled (int scrollX, int scrollY, boolean clampedX, boolean clampedY)
被overScrollBy(int, int, int, int, int, int, int, int, boolean)調用,來對一個over-scroll操作的結果進行響應。(譯者註:這個函數是2.3 r1 中新增的,API Level 9)
參數
scrollX 新的X滾動像素值
scrollY 新的Y滾動像素值
clampedX 當scrollX被over-scroll的邊界限制時,值為true
clampedY 當scrollY被over-scroll的邊界限制時,值為true
protected boolean onRequestFocusInDescendants (int direction, Rect previouslyFocusedRect)
當在滾動視圖的子視圖中查找焦點視圖時,需要注意不要將焦點設置在滾動出屏幕外的控制項上。此方法會比執行預設的ViewGroup代價高,否則此行為也會設置為預設
參數
direction 指定下列常量之一:FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT
previouslyFocusedRect 能夠給出一個較好的提示的矩形(當前視圖的坐標系統)表示焦點從哪裡得來。如果沒有提示為null。
返回值
是否取得了焦點
protected void onSizeChanged (int w, int h, int oldw, int oldh)
布局期間當視圖的大小發生改變時調用。如果只是添加到視圖,調用時顯示的是舊值0。(譯者註:也就是添加到視圖時,oldw和oldh返回的是0)。
參數
w 視圖當前寬度
h 視圖當前高度
oldw 視圖改變前的寬度
oldh 視圖改變前的高度

❻ android屬性動畫詳解

在 Android 動畫中,總共有兩種類型的動畫View Animation(視圖動畫)和Property Animator(屬性動畫);其中
View Animation包括Tween Animation(補間動畫)和Frame Animation(逐幀動畫);
Property Animator包括ValueAnimator和ObjectAnimation;
首先,直觀上,他們有如下三點不同:
1、引入時間不同: View Animation是API Level 1就引入的。Property Animation是API Level 11引入的,即Android 3.0才開始有Property Animation相關的API。
2、所在包名不同: View Animation在包android.view.animation中。而Property Animation API在包 android.animation中。
3、動畫類的命名不同: View Animation中動畫類取名都叫XXXXAnimation,而在Property Animator中動畫類的取名則叫XXXXAnimator大家都知道逐幀動畫主要是用來實現動畫的,而補間動畫才能實現控制項的漸入漸出、移動、旋轉和縮放的;而Property Animator是在Android 3.0版本才引入的,之前是沒有的。
為什麼還要引入Property Animator呢?
1、為什麼引入Property Animator(屬性動畫)
我提出一個假設:請問大家,如何利用補間動畫來將一個控制項的背景色在一分鍾內從綠色變為紅色?這個效果想必沒辦法僅僅通過改變控制項的漸入漸出、移動、旋轉和縮放來實現吧,而這個效果是可以通過Property Animator完美實現的
**這就是第一個原因:Property Animator能實現補間動畫無法實現的功能 **大家都知道,補間動畫和逐幀動畫統稱為View Animation,也就是說這兩個動畫只能對派生自View的控制項實例起作用;而Property Animator則不同,從名字中可以看出屬性動畫,應該是作用於控制項屬性的!正因為屬性動畫能夠只針對控制項的某一個屬性來做動畫,所以也就造就了他能單獨改變控制項的某一個屬性的值!比如顏色!這就是Property Animator能實現補間動畫無法實現的功能的最重要原因。
**我們得到了第二點不同:View Animation僅能對指定的控制項做動畫,而Property Animator是通過改變控制項某一屬性值來做動畫的。
**假設我們將一個按鈕從左上角利用補間動畫將其移動到右下角,在移動過程中和移動後,這個按鈕都是不會響應點擊事件的。這是為什麼呢?因為補間動畫僅僅轉變的是控制項的顯示位置而已,並沒有改變控制項本身的值。View Animation的動畫實現是通過其Parent View實現的,在View被drawn時Parents View改變它的繪制參數,這樣雖然View的大小或旋轉角度等改變了,但View的實際屬性沒變,所以有效區域還是應用動畫之前的區域;我們看到的效果僅僅是系統作用在按鈕上的顯示效果,利用動畫把按鈕從原來的位置移到了右下角,但按鈕內部的任何值是沒有變化的,所以按鈕所捕捉的點擊區域仍是原來的點擊區域。(下面會舉例來說明這個問題)
這就得到了第三點不同:補間動畫雖能對控制項做動畫,但並沒有改變控制項內部的屬性值。而Property Animator則是恰恰相反,Property Animator是通過改變控制項內部的屬性值來達到動畫效果的

我們前面講了Property Animator包括ValueAnimator和ObjectAnimator;這篇文章就主要來看看ValueAnimator的使用方法吧。
我覺得谷歌那幫老頭是最會起名字的人,單從命名上,就能看出來這個東東的含義。ValueAnimator從名字可以看出,這個Animation是針對值的! ValueAnimator不會對控制項做任何操作,我們可以給它設定從哪個值運動到哪個值,通過監聽這些值的漸變過程來自己操作控制項。 它會自己計算動畫的過程,然後我們需要監聽它的動畫過程來自己操作控制項。

這就是ValueAnimator的功能:ValueAnimator對指定值區間做動畫運算,我們通過對運算過程做監聽來自己操作控制項。
總而言之就是兩點:
1. ValueAnimator只負責對指定的數字區間進行動畫運算
2. 我們需要對運算過程進行監聽,然後自己對控制項做動畫操作

插值器的意義其實就相當於物理公式中的加速度參數,所以這也就是它也叫加速器的原因。 如何自定義插值器:

**input參數與任何我們設定的值沒關系,只與時間有關,隨著時間的增長,動畫的進度也自然的增加,input參數就代表了當前動畫的進度。而返回值則表示動畫的當前數值進度 **

在getInterpolation函數中,我們將進度反轉過來,當傳0的時候,我們讓它數值進度在完成的位置,當完成的時候,我們讓它在開始的位置

ObjectAnimator是派生自ValueAnimator的,所以ValueAnimator中所能使用的方法,在ObjectAnimator中都可以正常使用。ObjectAnimator重寫了幾個方法,比如ofInt(),ofFloat()等。利用ObjectAnimator重寫的ofFloat方法如何實現一個動畫:(改變透明度)

前面我們都是定義多個值,即至少兩個值之間的變化,那如果我們只定義一個值呢,如下面的方式:(同樣以MyPointView為例)

僅且僅當我們只給動畫設置一個值時,程序才會調用屬性對應的get函數來得到動畫初始值。如果動畫沒有初始值,那麼就會使用系統默認值。比如ofInt()中使用的參數類型是int類型的,而系統的Int值的默認值是0,所以動畫就會從0運動到100;也就是系統雖然在找到不到屬性對應的get函數時,會給出警告,但同時會用系統默認值做為動畫初始值。
如果通過給自定義控制項MyPointView設置了get函數,那麼將會以get函數的返回值做為初始值。

根據 View setBackGroundColor() 方法可以自定義條用屬性動畫。

❼ android 怎麼使用mvvm框架

MVC、MVP、MVVM
首先,我們先大致了解下Android開發中常見的模式。
MVC
View:XML布局文件。
Model:實體模型(數據的獲取、存儲、數據狀態變化)。
Controllor:對應於Activity,處理數據、業務和UI。
從上面這個結構來看,Android本身的設計還是符合MVC架構的,但是Android中純粹作為View的XML視圖功能太弱,我們大量處理View的邏輯只能寫在Activity中,這樣Activity就充當了View和Controller兩個角色,直接導致Activity中的代碼大爆炸。相信大多數Android開發者都遇到過一個Acitivty數以千行的代碼情況吧!所以,更貼切的說法是,這個MVC結構最終其實只是一個Model-View(Activity:View&Controller)的結構。
MVP
View:對應於Activity和XML,負責View的繪制以及與用戶的交互。
Model:依然是實體模型。
Presenter:負責完成View與Model間的交互和業務邏輯。
前面我們說,Activity充當了View和Controller兩個角色,MVP就能很好地解決這個問題,其核心理念是通過一個抽象的View介面(不是真正的View層)將Presenter與真正的View層進行解耦。Persenter持有該View介面,對該介面進行操作,而不是直接操作View層。這樣就可以把視圖操作和業務邏輯解耦,從而讓Activity成為真正的View層。
但MVP也存在一些弊端:
Presenter(以下簡稱P)層與View(以下簡稱V)層是通過介面進行交互的,介面粒度不好控制。粒度太小,就會存在大量介面的情況,使代碼太過碎版化;粒度太大,解耦效果不好。同時對於UI的輸入和數據的變化,需要手動調用V層或者P層相關的介面,相對來說缺乏自動性、監聽性。如果數據的變化能自動響應到UI、UI的輸入能自動更新到數據,那該多好!
MVP是以UI為驅動的模型,更新UI都需要保證能獲取到控制項的引用,同時更新UI的時候要考慮當前是否是UI線程,也要考慮Activity的生命周期(是否已經銷毀等)。
MVP是以UI和事件為驅動的傳統模型,數據都是被動地通過UI控制項做展示,但是由於數據的時變性,我們更希望數據能轉被動為主動,希望數據能更有活性,由數據來驅動UI。
V層與P層還是有一定的耦合度。一旦V層某個UI元素更改,那麼對應的介面就必須得改,數據如何映射到UI上、事件監聽介面這些都需要轉變,牽一發而動全身。如果這一層也能解耦就更好了。
復雜的業務同時也可能會導致P層太大,代碼臃腫的問題依然不能解決。
MVVM
View:對應於Activity和XML,負責View的繪制以及與用戶交互。
Model:實體模型。
ViewModel:負責完成View與Model間的交互,負責業務邏輯。
MVVM的目標和思想與MVP類似,利用數據綁定(Data Binding)、依賴屬性(Dependency Property)、命令(Command)、路由事件(Routed Event)等新特性,打造了一個更加靈活高效的架構。
數據驅動
在常規的開發模式中,數據變化需要更新UI的時候,需要先獲取UI控制項的引用,然後再更新UI。獲取用戶的輸入和操作也需要通過UI控制項的引用。在MVVM中,這些都是通過數據驅動來自動完成的,數據變化後會自動更新UI,UI的改變也能自動反饋到數據層,數據成為主導因素。這樣MVVM層在業務邏輯處理中只要關心數據,不需要直接和UI打交道,在業務處理過程中簡單方便很多。
低耦合度
MVVM模式中,數據是獨立於UI的。
數據和業務邏輯處於一個獨立的ViewModel中,ViewModel只需要關注數據和業務邏輯,不需要和UI或者控制項打交道。UI想怎麼處理數據都由UI自己決定,ViewModel不涉及任何和UI相關的事,也不持有UI控制項的引用。即便是控制項改變了(比如:TextView換成EditText),ViewModel也幾乎不需要更改任何代碼。它非常完美的解耦了View層和ViewModel,解決了上面我們所說的MVP的痛點。
更新UI
在MVVM中,數據發生變化後,我們在工作線程直接修改(在數據是線程安全的情況下)ViewModel的數據即可,不用再考慮要切到主線程更新UI了,這些事情相關框架都幫我們做了。
團隊協作
MVVM的分工是非常明顯的,由於View和ViewModel之間是鬆散耦合的:一個是處理業務和數據、一個是專門的UI處理。所以,完全由兩個人分工來做,一個做UI(XML和Activity)一個寫ViewModel,效率更高。
可復用性
一個ViewModel可以復用到多個View中。同樣的一份數據,可以提供給不同的UI去做展示。對於版本迭代中頻繁的UI改動,更新或新增一套View即可。如果想在UI上做A/B Testing,那MVVM是你不二選擇。
單元測試
有些同學一看到單元測試,可能腦袋都大。是啊,寫成一團漿糊的代碼怎麼可能做單元測試?如果你們以代碼太爛無法寫單元測試而逃避,那可真是不好的消息了。這時候,你需要MVVM來拯救。
我們前面說過了,ViewModel層做的事是數據處理和業務邏輯,View層中關注的是UI,兩者完全沒有依賴。不管是UI的單元測試還是業務邏輯的單元測試,都是低耦合的。在MVVM中數據是直接綁定到UI控制項上的(部分數據是可以直接反映出UI上的內容),那麼我們就可以直接通過修改綁定的數據源來間接做一些Android UI上的測試。
通過上面的簡述以及模式的對比,我們可以發現MVVM的優勢還是非常明顯的。雖然目前Android開發中可能真正在使用MVVM的很少,但是值得我們去做一些探討和調研。
如何構建MVVM應用框架
如何分工
構建MVVM框架首先要具體了解各個模塊的分工。接下來我們來講解View、ViewModel、Model它們各自的職責所在。
View
View層做的就是和UI相關的工作,我們只在XML、Activity和Fragment寫View層的代碼,View層不做和業務相關的事,也就是我們在Activity不寫業務邏輯和業務數據相關的代碼,更新UI通過數據綁定實現,盡量在ViewModel裡面做(更新綁定的數據源即可),Activity要做的事就是初始化一些控制項(如控制項的顏色,添加RecyclerView的分割線),View層可以提供更新UI的介面(但是我們更傾向所有的UI元素都是通過數據來驅動更改UI),View層可以處理事件(但是我們更希望UI事件通過Command來綁定)。 簡單地說:View層不做任何業務邏輯、不涉及操作數據、不處理數據,UI和數據嚴格的分開。
ViewModel
ViewModel層做的事情剛好和View層相反,ViewModel只做和業務邏輯和業務數據相關的事,不做任何和UI相關的事情,ViewModel 層不會持有任何控制項的引用,更不會在ViewModel中通過UI控制項的引用去做更新UI的事情。ViewModel就是專注於業務的邏輯處理,做的事情也都只是對數據的操作(這些數據綁定在相應的控制項上會自動去更改UI)。同時DataBinding框架已經支持雙向綁定,讓我們可以通過雙向綁定獲取View層反饋給ViewModel層的數據,並對這些數據上進行操作。關於對UI控制項事件的處理,我們也希望能把這些事件處理綁定到控制項上,並把這些事件的處理統一化,為此我們通過BindingAdapter對一些常用的事件做了封裝,把一個個事件封裝成一個個Command,對於每個事件我們用一個ReplyCommand 去處理就行了,ReplyCommand 會把你可能需要的數據帶給你,這使得我們在Vie,具體見 MVVM Light Toolkit 使用指南的 Command 部分 。再強調一遍:ViewModel 不做和UI相關的事。

❽ Android 如何判斷一個View重繪或載入完成

1、view重繪時回調(即監聽函數,當view重繪完成自動動用,需要向view的觀察者添加監聽器)。格式:

view.getViewTreeObserver().addOnDrawListener(new OnDrawListener() {

@Override

public void onDraw() {

// TODO Auto-generated method stub

}

});

2、view載入完成時回調(當view載入完成自動動用,需要向view的觀察者添加監聽器)。格式:

view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

@Override

public void onGlobalLayout() {

// TODO Auto-generated method stub

}

});

(8)androidview繪制完成監聽擴展閱讀:

兩種方式刷新:

1、主線程可以直接調用Invalidate()方法刷新

2、子線程可以直接調用postInvalidate()方法刷新。

API的描述 : Invalidatethe whole view. If the view is visible, onDraw(Canvas) will be called at somepoint in the future. This must be called from a UI thread. To call from anon-UI thread, call postInvalidate().。

API的描述譯文:當Invalidate()被調用的時候,View的OnDraw()就會被調用,Invalidate()必須是在UI線程中被調用,如果在新線程中更新視圖的就調用postInvalidate()。

❾ Android 重學系列 View的繪制流程(六) 硬體渲染(上)

本文開始聊聊Android中的硬體渲染。如果跟著我的文章順序,從SF進程到App進程的繪制流程一直閱讀,我們到這里已經有了一定的基礎,可以試著進行橫向比對如Chrome瀏覽器渲染流程,看看軟體渲染,硬體渲染,SF合成都做了什麼程度的優化。

先讓我們回顧一下負責硬體渲染的主體對象ThreadedRenderer在整個繪制流程中做了哪幾個步驟。

在硬體渲染的過程中,有一個很核心的對象RenderNode,作為每一個View繪制的節點對象。

當每一次進行准備進行繪制的時候,都會雷打不動執行如下三個步驟:

如果遇到什麼問題歡迎來到 https://www.jianshu.com/p/c84bfa909810 下進行討論

實際上整個硬體渲染的設計還是比較龐大。因此本文先聊聊ThreadedRender整個體系中主要對象的構造以及相關的原理。

首先來認識下面幾個重要的對象有一個大體的印象。

在Java層中面向Framework中,只有這么多,下面是一一映射的簡圖。

能看到實際上RenderNode也會跟著View 樹的構建同時一起構建整個顯示層級。也是因此ThreadedRender也能以RenderNode為線索構建出一套和軟體渲染一樣的渲染流程。

僅僅這樣?如果只是這么簡單,知道我習慣的都知道,我喜歡把相關總結寫在最後。如果把總攬寫在正文開頭是因為設計比較繁多。因為我們如果以流水線的形式進行剖析容易造成迷失細節的困境。

讓我繼續介紹一下,在硬體渲染中native層的核心對象。

如下是一個思維導圖:

有這么一個大體印象後,就不容易迷失在源碼中。我們先來把這些對象的實例化以及上面列舉的ThreadedRenderer在ViewRootImpl中執行行為的順序和大家來聊聊其原理,先來看看ThreadedRenderer的實例化。

當發現mSurfaceHolder為空的時候會調用如下函數:

而這個方法則調用如下的方法對ThreadedRenderer進行創建:

文件:/ frameworks / base / core / java / android / view / ThreadedRenderer.java

能不能創建的了ThreadedRenderer則決定於全局配置。如果ro.kernel.qemu的配置為0,說明支持OpenGL 則可以直接返回true。如果qemu.gles為-1說明不支持OpenGL es返回false,只能使用軟體渲染。如果設置了qemu.gles並大於0,才能打開硬體渲染。

我們能看到ThreadedRenderer在初始化,做了三件事情:

關鍵是看1-3點中ThreadRenderer都做了什麼。

文件:/ frameworks / base / core / jni / android_view_ThreadedRenderer.cpp

能看到這里是直接實例化一個RootRenderNode對象,並把指針的地址直接返回。

能看到RootRenderNode繼承了RenderNode對象,並且保存一個JavaVM也就是我們所說的Java虛擬機對象,一個java進程全局只有一個。同時通過getForThread方法,獲取ThreadLocal中的Looper對象。這里實際上拿的就是UI線程的Looper。

在這個構造函數有一個mDisplayList十分重要,記住之後會頻繁出現。接著來看看RenderNode的頭文件:
文件:/ frameworks / base / libs / hwui / RenderNode.h

實際上我把幾個重要的對象留下來:

文件:/ frameworks / base / core / java / android / view / RenderNode.java

能看到很簡單,就是包裹一個native層的RenderNode返回一個Java層對應的對象開放Java層的操作API。

能看到這個過程生成了兩個對象:

這個對象實際上讓RenderProxy持有一個創建動畫上下文的工廠。RenderProxy可以通過ContextFactoryImpl為每一個RenderNode創建一個動畫執行對象的上下文AnimationContextBridge。

文件:/ frameworks / base / libs / hwui / renderthread / RenderProxy.cpp

在這里有幾個十分重要的對象被實例化,當然這幾個對象在聊TextureView有聊過( SurfaceView和TextureView 源碼淺析 ):

我們依次看看他們初始化都做了什麼。

文件:/ frameworks / base / libs / hwui / renderthread / RenderThread.cpp

能看到其實就是簡單的調用RenderThread的構造函數進行實例化,並且返回對象的指針。

RenderThread是一個線程對象。先來看看其頭文件繼承的對象:
文件:/ frameworks / base / libs / hwui / renderthread / RenderThread.h

其中RenderThread的中進行排隊處理的任務隊列實際上是來自ThreadBase的WorkQueue對象。

文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h

ThreadBase則是繼承於Thread對象。當調用start方法時候其實就是調用Thread的run方法啟動線程。

另一個更加關鍵的對象,就是實例化一個Looper對象到WorkQueue中。而直接實例化Looper實際上就是新建一個Looper。但是這個Looper並沒有獲取當先線程的Looper,這個Looper做什麼的呢?下文就會揭曉。

WorkQueue把一個Looper的方法指針設置到其中,其作用可能是完成了某一件任務後喚醒Looper繼續工作。

而start方法會啟動Thread的run方法。而run方法最終會走到threadLoop方法中,至於是怎麼走進來的,之後有機會會解剖虛擬機的源碼線程篇章進行講解。

在threadloop中關鍵的步驟有如下四個:

在這個過程中創建了幾個核心對象:

另一個核心的方法就是,這個方法為WorkQueue的Looper注冊了監聽:

能看到在這個Looper中注冊了對DisplayEventReceiver的監聽,也就是Vsync信號的監聽,回調方法為displayEventReceiverCallback。

我們暫時先對RenderThread的方法探索到這里,我們稍後繼續看看回調後的邏輯。

文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h

能看到這里的邏輯很簡單實際上就是調用Looper的pollOnce方法,阻塞Looper中的循環,直到Vsync的信號到來才會繼續往下執行。詳細的可以閱讀我寫的 Handler與相關系統調用的剖析 系列文章。

文件:/ frameworks / base / libs / hwui / thread / ThreadBase.h

實際上調用的是WorkQueue的process方法。

文件:/ frameworks / base / libs / hwui / thread / WorkQueue.h

能看到這個過程中很簡單,幾乎和Message的loop的邏輯一致。如果Looper的阻塞打開了,則首先找到預計執行時間比當前時刻都大的WorkItem。並且從mWorkQueue移除,最後添加到toProcess中,並且執行每一個WorkItem的work方法。而每一個WorkItem其實就是通過從某一個壓入方法添加到mWorkQueue中。

到這里,我們就明白了RenderThread中是如何消費渲染任務的。那麼這些渲染任務又是哪裡誕生呢?

上文聊到了在RenderThread中的Looper會監聽Vsync信號,當信號回調後將會執行下面的回調。

能看到這個方法的核心實際上就是調用drainDisplayEventQueue方法,對ui渲染任務隊列進行處理。

能到在這里mVsyncRequested設置為false,且mFrameCallbackTaskPending將會設置為true,並且調用queue的postAt的方法執行ui渲染方法。

還記得queue實際是是指WorkQueue,而WorkQueue的postAt方法實際實現如下:
/ frameworks / base / libs / hwui / thread / WorkQueue.h

情景帶入,當一個Vsync信號達到Looper的監聽者,此時就會通過WorkQueue的drainDisplayEventQueue 壓入一個任務到隊列中。

每一個默認的任務都是執行dispatchFrameCallback方法。這里的判斷mWorkQueue中是否存在比當前時間更遲的時刻,並返回這個WorkItem。如果這個對象在頭部needsWakeup為true,說明可以進行喚醒了。而mWakeFunc這個方法指針就是上面傳下來:

把阻塞的Looper喚醒。當喚醒後就繼續執行WorkQueue的process方法。也就是執行dispatchFrameCallbacks方法。

在這里執行了兩個事情:

先添加到集合中,在上面提到過的threadLoop中,會執行如下邏輯:

如果大小不為0,則的把中的IFrameCallback全部遷移到mFrameCallbacks中。

而這個方法什麼時候調用呢?稍後就會介紹。其實這部分的邏輯在TextureView的解析中提到過。

接下來將會初始化一個重要對象:

這個對象名字叫做畫布的上下文,具體是什麼上下文呢?我們現在就來看看其實例化方法。
文件:/ frameworks / base / libs / hwui / renderthread / CanvasContext.cpp

文件:/ device / generic / goldfish / init.ranchu.rc

在init.rc中默認是opengl,那麼我們就來看看下面的邏輯:

首先實例化一個OpenGLPipeline管道,接著OpenGLPipeline作為參數實例化CanvasContext。

文件:/ frameworks / base / libs / hwui / renderthread / OpenGLPipeline.cpp

能看到在OpenGLPipeline中,實際上就是存儲了RenderThread對象,以及RenderThread中的mEglManager。透過OpenGLPipeline來控制mEglManager進而進一步操作OpenGL。

做了如下操作:

文件:/ frameworks / base / libs / hwui / renderstate / RenderState.cpp

文件:/ frameworks / base / libs / hwui / renderthread / DrawFrameTask.cpp

實際上就是保存這三對象RenderThread;CanvasContext;RenderNode。

文件:/ frameworks / base / core / jni / android_view_ThreadedRenderer.cpp

能看到實際上就是調用RenderProxy的setName方法給當前硬體渲染對象設置名字。

文件:/ frameworks / base / libs / hwui / renderthread / RenderProxy.cpp

能看到在setName方法中,實際上就是調用RenderThread的WorkQueue,把一個任務隊列設置進去,並且調用runSync執行。

能看到這個方法實際上也是調用post執行排隊執行任務,不同的是,這里使用了線程的Future方式,阻塞了執行,等待CanvasContext的setName工作完畢。

閱讀全文

與androidview繪制完成監聽相關的資料

熱點內容
遇到不合法app應該怎麼辦 瀏覽:90
匯編程序編譯後的文件 瀏覽:75
大智慧均線源碼 瀏覽:371
單片機排阻的作用 瀏覽:213
滴滴金融app被下架如何還款 瀏覽:210
jpg轉換成pdf免費軟體 瀏覽:741
范里安pdf 瀏覽:443
偽造pdf 瀏覽:75
能刪除android文件夾嗎 瀏覽:446
LINUX使用V2ray 瀏覽:797
找人幫忙注冊app推廣是什麼 瀏覽:820
獨立伺服器如何恢復初始化 瀏覽:11
優秀到不能被忽視pdf 瀏覽:316
導遊程序員家政 瀏覽:586
22乘28的快速演算法 瀏覽:338
軟通動力程序員節2021 瀏覽:845
安卓系統如何卸載安裝包 瀏覽:870
簡訊刪除助手文件夾 瀏覽:688
java辦公自動化 瀏覽:343
php中超鏈接 瀏覽:254