1. android圖形渲染原理上
對於Android開發者來說,我們或多或少有了解過Android圖像顯示的知識點,剛剛學習Android開發的人會知道,在Actvity的onCreate方法中設置我們的View後,再經過onMeasure,onLayout,onDraw的流程,界面就顯示出來了;對Android比較熟悉的開發者會知道,onDraw流程分為軟體繪制和硬體繪制兩種模式,軟繪是通過調用Skia來操作,硬繪是通過調用Opengl ES來操作;對Android非常熟悉的開發者會知道繪制出來的圖形數據最終都通過GraphiBuffer內共享內存傳遞給SurfaceFlinger去做圖層混合,圖層混合完成後將圖形數據送到幀緩沖區,於是,圖形就在我們的屏幕顯示出來了。
但我們所知道的Activity或者是應用App界面的顯示,只屬於Android圖形顯示的一部分。同樣可以在Android系統上展示圖像的WebView,Flutter,或者是通過Unity開發的3D游戲,他們的界面又是如何被繪制和顯現出來的呢?他們和我們所熟悉的Acitvity的界面顯示又有什麼異同點呢?我們可以不藉助Activity的setView或者InflateView機制來實現在屏幕上顯示出我們想要的界面嗎?Android系統顯示界面的方式又和IOS,或者Windows等系統有什麼區別呢?……
去探究這些問題,比僅僅知道Acitvity的界面是如何顯示出來更加的有價值,因為想要回答這些問題,就需要我們真正的掌握Android圖像顯示的底層原理,當我們掌握了底層的顯示原理後,我們會發現WebView,Flutter或者未來會出現的各種新的圖形顯示技術,原來都是大同小異。
我會花三篇文章的篇幅,去深入的講解Android圖形顯示的原理,OpenGL ES和Skia的繪制圖像的方式,他們如何使用,以及他們在Android中的使用場景,如開機動畫,Activity界面的軟體繪制和硬體繪制,以及Flutter的界面繪制。那麼,我們開始對Android圖像顯示原理的探索吧。
在講解Android圖像的顯示之前,我會先講一下屏幕圖像的顯示原理,畢竟我們圖像,最終都是在手機屏幕上顯示出來的,了解這一塊的知識會讓我們更容易的理解Android在圖像顯示上的機制。
圖像顯示的完整過程,分為下面幾個階段:
圖像數據→CPU→顯卡驅動→顯卡(GPU)→顯存(幀緩沖)→顯示器
我詳細介紹一下這幾個階段:
實際上顯卡驅動,顯卡和顯存,包括數模轉換模塊都是屬於顯卡的模塊。但為了能能詳細的講解經歷的步驟,這里做了拆分。
當顯存中有數據後,顯示器又是怎麼根據顯存裡面的數據來進行界面的顯示的呢?這里以LCD液晶屏為例,顯卡會將顯存里的數據,按照從左至右,從上到下的順序同步到屏幕上的每一個像素晶體管,一個像素晶體管就代表了一個像素。
如果我們的屏幕解析度是1080x1920像素,就表示有1080x1920個像素像素晶體管,每個橡素點的顏色越豐富,描述這個像素的數據就越大,比如單色,每個像素只需要1bit,16色時,只需要4bit,256色時,就需要一個位元組。那麼1080x1920的解析度的屏幕下,如果要以256色顯示,顯卡至少需要1080x1920個位元組,也就是2M的大小。
剛剛說了,屏幕上的像素數據是從左到右,從上到下進行同步的,當這個過程完成了,就表示一幀繪制完成了,於是會開始下一幀的繪制,大部分的顯示屏都是以60HZ的頻率在屏幕上繪制完一幀,也就是16ms,並且每次繪制新的一幀時,都會發出一個垂直同步信號(VSync)。我們已經知道,圖像數據都是放在幀緩沖中的,如果幀緩沖的緩沖區只有一個,那麼屏幕在繪制這一幀的時候,圖像數據便沒法放入幀緩沖中了,只能等待這一幀繪制完成,在這種情況下,會有很大了效率問題。所以為了解決這一問題,幀緩沖引入兩個緩沖區,即 雙緩沖機制 。雙緩沖雖然能解決效率問題,但會引入一個新的問題。當屏幕這一幀還沒繪制完成時,即屏幕內容剛顯示一半時,GPU 將新的一幀內容提交到幀緩沖區並把兩個緩沖區進行交換後,顯卡的像素同步模塊就會把新的一幀數據的下半段顯示到屏幕上,造成畫面撕裂現象。
為了解決撕裂問題,就需要在收到垂直同步的時候才將幀緩沖中的兩個緩沖區進行交換。Android4.1黃油計劃中有一個優化點,就是CPU和GPU都只有收到垂直同步的信號時,才會開始進行圖像的繪制操作,以及緩沖區的交換工作。
我們已經了解了屏幕圖像顯示的原理了,那麼接著開始對Android圖像顯示的學習。
從上一章已經知道,計算機渲染界面必須要有GPU和幀緩沖。對於Linux系統來說,用戶進程是沒法直接操作幀緩沖的,但我們想要顯示圖像就必須要操作幀緩沖,所以Linux系統設計了一個虛擬設備文件,來作為對幀緩沖的映射,通過對該文件的I/O讀寫,我們就可以實現讀寫屏操作。幀緩沖對應的設備文件於/dev/fb* ,*表示對多個顯示設備的支持, 設備號從0到31,如/dev/fb0就表示第一塊顯示屏,/dev/fb1就表示第二塊顯示屏。對於Android系統來說,默認使用/dev/fb0這一個設幀緩沖作為主屏幕,也就是我們的手機屏幕。我們Android手機屏幕上顯示的圖像數據,都是存儲在/dev/fb0里,早期AndroidStuio中的DDMS工具實現截屏的原理就是直接讀取/dev/fb0設備文件。
我們知道了手機屏幕上的圖形數據都存儲在幀緩沖中,所以Android手機圖像界面的原理就是將我們的圖像數據寫入到幀緩沖內。那麼,寫入到幀緩沖的圖像數據是怎麼生成的,又是怎樣加工的呢?圖形數據是怎樣送到幀緩沖去的,中間經歷了哪些步驟和過程呢?了解了這幾個問題,我們就了解了Android圖形渲染的原理,那麼帶著這幾個疑問,接著往下看。
想要知道圖像數據是怎麼產生的,我們需要知道 圖像生產者 有哪些,他們分別是如何生成圖像的,想要知道圖像數據是怎麼被消費的,我們需要知道 圖像消費者 有哪些,他們又分別是如何消費圖像的,想要知道中間經歷的步驟和過程,我們需要知道 圖像緩沖區 有哪些,他們是如何被創建,如何分配存儲空間,又是如何將數據從生產者傳遞到消費者的,圖像顯示是一個很經典的消費者生產者的模型,只有對這個模型各個模塊的擊破,了解他們之間的流動關系,我們才能找到一條更容易的路徑去掌握Android圖形顯示原理。我們看看谷歌提供的官方的架構圖是怎樣描述這一模型的模塊及關系的。
如圖, 圖像的生產者 主要有MediaPlayer,CameraPrevier,NDK,OpenGl ES。MediaPlayer和Camera Previer是通過直接讀取圖像源來生成圖像數據,NDK(Skia),OpenGL ES是通過自身的繪制能力生產的圖像數據; 圖像的消費者 有SurfaceFlinger,OpenGL ES Apps,以及HAL中的Hardware Composer。OpenGl ES既可以是圖像的生產者,也可以是圖像的消費者,所以它也放在了圖像消費模塊中; 圖像緩沖區 主要有Surface以及前面提到幀緩沖。
Android圖像顯示的原理,會僅僅圍繞 圖像的生產者 , 圖像的消費者 , 圖像緩沖區 來展開,在這一篇文章中,我們先看看Android系統中的圖像消費者。
SurfaceFlinger是Android系統中最重要的一個圖像消費者,Activity繪制的界面圖像,都會傳遞到SurfaceFlinger來,SurfaceFlinger的作用主要是接收圖像緩沖區數據,然後交給HWComposer或者OpenGL做合成,合成完成後,SurfaceFlinger會把最終的數據提交給幀緩沖。
那麼SurfaceFlinger是如何接收圖像緩沖區的數據的呢?我們需要先了解一下Layer(層)的概念,一個Layer包含了一個Surface,一個Surface對應了一塊圖形緩沖區,而一個界面是由多個Surface組成的,所以他們會一一對應到SurfaceFlinger的Layer中。SurfaceFlinger通過讀取Layer中的緩沖數據,就相當於讀取界面上Surface的圖像數據。Layer本質上是 Surface和SurfaceControl的組合 ,Surface是圖形生產者和圖像消費之間傳遞數據的緩沖區,SurfaceControl是Surface的控制類。
前面在屏幕圖像顯示原理中講到,為了防止圖像的撕裂,Android系統會在收到VSync垂直同步時才會開始處理圖像的繪制和合成工作,而Surfaceflinger作為一個圖像的消費者,同樣也是遵守這一規則,所以我們通過源碼來看看SurfaceFlinger是如何在這一規則下,消費圖像數據的。
SurfaceFlinger專門創建了一個EventThread線程用來接收VSync。EventThread通過Socket將VSync信號同步到EventQueue中,而EventQueue又通過回調的方式,將VSync信號同步到SurfaceFlinger內。我們看一下源碼實現。
上面主要是SurfaceFlinger初始化接收VSYNC垂直同步信號的操作,主要有這幾個過程:
經過上面幾個步驟,我們接收VSync的初始化工作都准備好了,EventThread也開始運轉了,接著看一下EventThread的運轉函數threadLoop做的事情。
threadLoop主要是兩件事情
mConditon又是怎麼接收VSync的呢?我們來看一下
可以看到,mCondition的VSync信號實際是DispSyncSource通過onVSyncEvent回調傳入的,但是DispSyncSource的VSync又是怎麼接收的呢?在上面講到的SurfaceFlinger的init函數,在創建EventThread的實現中,我們可以發現答案—— mPrimaryDispSync 。
DispSyncSource的構造方法傳入了mPrimaryDispSync,mPrimaryDispSync實際是一個DispSyncThread線程,我們看看這個線程的threadLoop方法
DispSyncThread的threadLoop會通過mPeriod來判斷是否進行阻塞或者進行VSync回調,那麼mPeriod又是哪兒被設置的呢?這里又回到SurfaceFlinger了,我們可以發現在SurfaceFlinger的 resyncToHardwareVsync 函數中有對mPeriod的賦值。
可以看到,這里最終通過HWComposer,也就是硬體層拿到了period。終於追蹤到了VSync的最終來源了, 它從HWCompser產生,回調至DispSync線程,然後DispSync線程回調到DispSyncSource,DispSyncSource又回調到EventThread,EventThread再通過Socket分發到MessageQueue中 。
我們已經知道了VSync信號來自於HWCompser,但SurfaceFlinger並不會一直監聽VSync信號,監聽VSync的線程大部分時間都是休眠狀態,只有需要做合成工作時,才會監聽VSync,這樣即保證圖像合成的操作能和VSync保持一致,也節省了性能。SurfaceFlinger提供了一些主動注冊監聽VSync的操作函數。
可以看到,只有當SurfaceFlinger調用 signalTransaction 或者 signalLayerUpdate 函數時,才會注冊監聽VSync信號。那麼signalTransaction或者signalLayerUpdate什麼時候被調用呢?它可以由圖像的生產者通知調用,也可以由SurfaceFlinger根據自己的邏輯來判斷是否調用。
現在假設App層已經生成了我們界面的圖像數據,並調用了 signalTransaction 通知SurfaceFlinger注冊監聽VSync,於是VSync信號便會傳遞到了MessageQueue中了,我們接著看看MessageQueue又是怎麼處理VSync的吧。
MessageQueue收到VSync信號後,最終回調到了SurfaceFlinger的 onMessageReceived 中,當SurfaceFlinger接收到VSync後,便開始以一個圖像消費者的角色來處理圖像數據了。我們接著看SurfaceFlinger是以什麼樣的方式消費圖像數據的。
VSync信號最終被SurfaceFlinger的onMessageReceived函數中的INVALIDATE模塊處理。
INVALIDATE的流程如下:
handleMessageTransaction的處理比較長,處理的事情也比較多,它主要做的事情有這些
handleMessageRefresh函數,便是SurfaceFlinger真正處理圖層合成的地方,它主要下面五個步驟。
我會詳細介紹每一個步驟的具體操作
合成前預處理會判斷Layer是否發生變化,當Layer中有新的待處理的Buffer幀(mQueuedFrames>0),或者mSidebandStreamChanged發生了變化, 都表示Layer發生了變化,如果變化了,就調用signalLayerUpdate,注冊下一次的VSync信號。如果Layer沒有發生變化,便只會做這一次的合成工作,不會注冊下一次VSync了。
重建Layer棧會遍歷Layer,計算和存儲每個Layer的臟區, 然後和當前的顯示設備進行比較,看Layer的臟區域是否在顯示設備的顯示區域內,如果在顯示區域內的話說明該layer是需要繪制的,則更新到顯示設備的VisibleLayersSortedByZ列表中,等待被合成
rebuildLayerStacks中最重要的一步是 computeVisibleRegions ,也就是對Layer的變化區域和非透明區域的計算,為什麼要對變化區域做計算呢?我們先看看SurfaceFlinger對界面顯示區域的分類:
還是以這張圖做例子,可以看到我們的狀態欄是半透明的,所以它是一個opaqueRegion區域,微信界面和虛擬按鍵是完全不透明的,他是一個visibleRegion,除了這三個Layer外,還有一個我們看不到的Layer——壁紙,它被上方visibleRegion遮擋了,所以是coveredRegion
對這幾個區域的概念清楚了,我們就可以去了解computeVisibleRegions中做的事情了,它主要是這幾步操作:
2. android繪圖,怎麼才能做出拖動畫布效果
使用卡馬克地圖緩沖演算法。
基本原理是使用drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint)方法。
將點陣圖上指定的部分(src矩形部分)繪制到指定的屏幕位置(dst矩形部分),通過改變src矩形的位置改變實現地圖的移動。
給你個連接,自己先看看。
http://xys289187120.blog.51cto.com/3361352/656998
3. 求助View.ondraw中繪制view-Android開發問答
我覺得你還是要看看Android中View和ViewGroup的繪制機制
android自定義view–繪制順序及相關原理
如果你的自定義View需要繪制其他View,建議繼承ViewGroup。這樣你只要布局好子View就可以了。要是沒特殊需要,基本上不需要自己繪制子View。
4. 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工作完畢。
5. 如何用android顯示實時曲線求方法,代碼
1. 網格背景,心電圖的原理比較簡單,首先繪制一個背景,就是網格就以Windows下的任務管理器來說吧,下面綠色的網格是固定的,如果你比較懶或者考慮繪制效率你甚至可以直接使用一個背景圖片代替,當然代碼繪制效率沒有什麼問題,直接使用Canvas的drawLine方法即可。由兩個for循環控制著橫縱坐標,當然Android123推薦大家使用drawLines參數直接是一個數組。
2. K線圖,對於真正的曲線或者說K線圖,其實就是描點畫圖了,在Android中我們自繪控制項中重寫onDraw方法,onDraw的形參Canvas提供了drawPoint(float x,float y,Point point) 。這三個參數前兩個正好對應橫豎坐標,第三個參數為Point對象,可以控制畫筆的顏色、粗細和類型。如果是動態的,你需要使用一個計時器,最簡單的使用Handler的postDelay方法,使用一個數組動態保存著每個點即可。
6. 怎麼用Android畫一個正方形
先來介紹一下畫幾何圖形要用到的,畫布(Canvas)、畫筆(Paint)。
1. 畫一個圓使用的是drawCircle:canvas.drawCircle(cx, cy, radius, paint);x、y代表坐標、radius是半徑、paint是畫筆,就是畫圖的顏色;
2. 在畫圖的時候還要有注意,你所畫的矩形是實心(paint.setStyle(Paint.Style.FILL))還是空心(paint.setStyle(Paint.Style.STROKE);
畫圖的時候還有一點,那就是消除鋸齒:paint.setAntiAlias(true);
3. 還有就是設置一種漸變顏色的矩形:
Shader mShader = new LinearGradient(0,0,100,100, new int[]{Color.RED,Color.GREEn,Color.BLUE,Color.YELLO},null,Shader.TileMode.REPEAT);
ShapeDrawable sd;
//畫一個實心正方形
sd = new ShapeDrawable(new RectShape());
sd.setBounds(20,20,100,100);
sd.draw(canvas);
//一個漸變色的正方形就完成了
4. 正方形:drawRect:canvas.drawRect(left, top, right, bottom, paint)
這里的left、top、right、bottom的值是:
left:是矩形距離左邊的X軸
top:是矩形距離上邊的Y軸
right:是矩形距離右邊的X軸
bottom:是矩形距離下邊的Y軸
5. 長方形:他和正方形是一個原理,這個就不用說了
6. 橢圓形:記住,這里的Rectf是float類型的
RectF re = new Rect(left, top, right, bottom);
canvas.drawOval(re,paint);
好了,說了這么多的的東西,那就讓我們來看一下真正的實例吧!!!
package com.hades.game;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Bundle;
import android.view.View;
public class CanvasActivity extends Activity {
/**
* 畫一個幾何圖形
* hades
* 藍色著衣
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyView myView = new MyView(this);
setContentView(myView);
}
public class MyView extends View {
public MyView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 設置畫布的背景顏色
canvas.drawColor(Color.WHITE);
/**
* 定義矩形為空心
*/
// 定義畫筆1
Paint paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
// 消除鋸齒
paint.setAntiAlias(true);
// 設置畫筆的顏色
paint.setColor(Color.RED);
// 設置paint的外框寬度
paint.setStrokeWidth(2);
// 畫一個圓
canvas.drawCircle(40, 30, 20, paint);
// 畫一個正放形
canvas.drawRect(20, 70, 70, 120, paint);
// 畫一個長方形
canvas.drawRect(20, 170, 90, 130, paint);
// 畫一個橢圓
RectF re = new RectF(20, 230, 100, 190);
canvas.drawOval(re, paint);
/**
* 定義矩形為實心
*/
paint.setStyle(Paint.Style.FILL);
// 定義畫筆2
Paint paint2 = new Paint();
// 消除鋸齒
paint2.setAntiAlias(true);
// 設置畫筆的顏色
paint2.setColor(Color.BLUE);
// 畫一個空心圓
canvas.drawCircle(150, 30, 20, paint2);
// 畫一個正方形
canvas.drawRect(185, 70, 130, 120, paint2);
// 畫一個長方形
canvas.drawRect(200, 130, 130, 180, paint2);
// 畫一個橢圓形
RectF re2 = new RectF(200, 230, 130, 190);
canvas.drawOval(re2, paint2);
}
}
}
7. Android UI繪制之View繪制的工作原理
這是AndroidUI繪制流程分析的第二篇文章,主要分析界面中View是如何繪制到界面上的具體過程。
ViewRoot 對應於 ViewRootImpl 類,它是連接 WindowManager 和 DecorView 的紐帶,View的三大流程均是通過 ViewRoot 來完成的。在 ActivityThread 中,當 Activity 對象被創建完畢後,會將 DecorView 添加到 Window 中,同時會創建 ViewRootImpl 對象,並將 ViewRootImpl 對象和 DecorView 建立關聯。
measure 過程決定了 View 的寬/高, Measure 完成以後,可以通過 getMeasuredWidth 和 getMeasuredHeight 方法來獲取 View 測量後的寬/高,在幾乎所有的情況下,它等同於View的最終的寬/高,但是特殊情況除外。 Layout 過程決定了 View 的四個頂點的坐標和實際的寬/高,完成以後,可以通過 getTop、getBottom、getLeft 和 getRight 來拿到View的四個頂點的位置,可以通過 getWidth 和 getHeight 方法拿到View的最終寬/高。 Draw 過程決定了 View 的顯示,只有 draw 方法完成後 View 的內容才能呈現在屏幕上。
DecorView 作為頂級 View ,一般情況下,它內部會包含一個豎直方向的 LinearLayout ,在這個 LinearLayout 裡面有上下兩個部分,上面是標題欄,下面是內容欄。在Activity中,我們通過 setContentView 所設置的布局文件其實就是被加到內容欄中的,而內容欄id為 content 。可以通過下面方法得到 content:ViewGroup content = findViewById(R.android.id.content) 。通過 content.getChildAt(0) 可以得到設置的 view 。 DecorView 其實是一個 FrameLayout , View 層的事件都先經過 DecorView ,然後才傳遞給我們的 View 。
MeasureSpec 代表一個32位的int值,高2位代表 SpecMode ,低30位代表 SpecSize , SpecMode 是指測量模式,而 SpecSize 是指在某種測量模式下的規格大小。
SpecMode 有三類,如下所示:
UNSPECIFIED
EXACTLY
AT_MOST
LayoutParams需要和父容器一起才能決定View的MeasureSpec,從而進一步決定View的寬/高。
對於頂級View,即DecorView和普通View來說,MeasureSpec的轉換過程略有不同。對於DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同確定;
對於普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同決定;
MeasureSpec一旦確定,onMeasure就可以確定View的測量寬/高。
小結一下
當子 View 的寬高採用 wrap_content 時,不管父容器的模式是精確模式還是最大模式,子 View 的模式總是最大模式+父容器的剩餘空間。
View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即測量、布局、繪制。其中 measure 確定 View 的測量寬/高, layout 確定 view 的最終寬/高和四個頂點的位置,而 draw 則將 View 繪制在屏幕上。
measure 過程要分情況,如果只是一個原始的 view ,則通過 measure 方法就完成了其測量過程,如果是一個 ViewGroup ,除了完成自己的測量過程外,還會遍歷調用所有子元素的 measure 方法,各個子元素再遞歸去執行這個流程。
如果是一個原始的 View,那麼通過 measure 方法就完成了測量過程,在 measure 方法中會去調用 View 的 onMeasure 方法,View 類裡面定義了 onMeasure 方法的默認實現:
先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源碼:
可以看到, getMinimumWidth 方法獲取的是 Drawable 的原始寬度。如果存在原始寬度(即滿足 intrinsicWidth > 0),那麼直接返回原始寬度即可;如果不存在原始寬度(即不滿足 intrinsicWidth > 0),那麼就返回 0。
接著看最重要的 getDefaultSize 方法:
如果 specMode 為 MeasureSpec.UNSPECIFIED 即未指定模式,那麼返回由方法參數傳遞過來的尺寸作為 View 的測量寬度和高度;
如果 specMode 不是 MeasureSpec.UNSPECIFIED 即是最大模式或者精確模式,那麼返回從 measureSpec 中取出的 specSize 作為 View 測量後的寬度和高度。
看一下剛才的表格:
當 specMode 為 EXACTLY 或者 AT_MOST 時,View 的布局參數為 wrap_content 或者 match_parent 時,給 View 的 specSize 都是 parentSize 。這會比建議的最小寬高要大。這是不符合我們的預期的。因為我們給 View 設置 wrap_content 是希望View的大小剛好可以包裹它的內容。
因此:
如果是一個 ViewGroup,除了完成自己的 measure 過程以外,還會遍歷去調用所有子元素的 measure 方法,各個子元素再遞歸去執行 measure 過程。
ViewGroup 並沒有重寫 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 這幾個方法專門用於測量子元素。
如果是 View 的話,那麼在它的 layout 方法中就確定了自身的位置(具體來說是通過 setFrame 方法來設定 View 的四個頂點的位置,即初始化 mLeft , mRight , mTop , mBottom 這四個值), layout 過程就結束了。
如果是 ViewGroup 的話,那麼在它的 layout 方法中只是確定了 ViewGroup 自身的位置,要確定子元素的位置,就需要重寫 onLayout 方法;在 onLayout 方法中,會調用子元素的 layout 方法,子元素在它的 layout 方法中確定自己的位置,這樣一層一層地傳遞下去完成整個 View 樹的 layout 過程。
layout 方法的作用是確定 View 本身的位置,即設定 View 的四個頂點的位置,這樣就確定了 View 在父容器中的位置;
onLayout 方法的作用是父容器確定子元素的位置,這個方法在 View 中是空實現,因為 View 沒有子元素了,在 ViewGroup 中則進行抽象化,它的子類必須實現這個方法。
1.繪制背景( background.draw(canvas); );
2.繪制自己( onDraw );
3.繪制 children( dispatchDraw(canvas) );
4.繪制裝飾( onDrawScrollBars )。
dispatchDraw 方法的調用是在 onDraw 方法之後,也就是說,總是先繪制自己再繪制子 View 。
對於 View 類來說, dispatchDraw 方法是空實現的,對於 ViewGroup 類來說, dispatchDraw 方法是有具體實現的。
通過 dispatchDraw 來傳遞的。 dispatchDraw 會遍歷調用子元素的 draw 方法,如此 draw 事件就一層一層傳遞了下去。dispatchDraw 在 View 類中是空實現的,在 ViewGroup 類中是真正實現的。
如果一個 View 不需要繪制任何內容,那麼就設置這個標記為 true,系統會進行進一步的優化。
當創建的自定義控制項繼承於 ViewGroup 並且不具備繪制功能時,就可以開啟這個標記,便於系統進行後續的優化;當明確知道一個 ViewGroup 需要通過 onDraw 繪制內容時,需要關閉這個標記。
參考:《Android開發藝術探索》
8. Android 之 Project Butter 詳細介紹
UI 優化系列專題,來聊一聊 Android 渲染相關知識,主要涉及 UI 渲染背景知識 、 如何優化 UI 渲染 兩部分內容。
《 View 繪制流程之 setContentView() 到底做了什麼? 》
《 View 繪制流程之 DecorView 添加至窗口的過程 》
《 深入 Activity 三部曲(3)View 繪制流程 》
《 Android 之 LayoutInflater 全面解析 》
《 關於渲染,你需要了解什麼? 》
《 Android 之 Choreographer 詳細分析 》
《 Android 之如何優化 UI 渲染(上) 》
《 Android 之如何優化 UI 渲染(下) 》
現在我們已經很少能夠聽到關於 Android UI 卡頓的話題了,這得益於 Google 長期以來對 Android 渲染性能的重視,基本每次 Google I/O 都會花很多篇幅講這一塊。隨著時間的推移,Android 系統一直在不斷進化、壯大,並且日趨完善。
其中,Google 在 2012 年的 I/O 大會上宣布了 Project Butter 黃油計劃,那個曾經嚴重影響 Android 口碑的 UI 流程性問題,首先在這得到有效的控制,並且在 Android 4.1 中正式開啟了這個機制。
Project Butter 對 Android Display 系統進行了重構,引入了三個核心元素,即 VSYNC 、 Triple Buffer 和 Choreographer 。
其中 VSYNC 是理解 Project Butter 的核心。接下來,我們就圍繞 VSYNC 開始介紹 Project Butter 對 Android Display 系統做了哪些優化。
VSYNC 最初是由 GPU 廠商開發的一種,用於防止屏幕撕裂的技術方案,全稱 Vertical Synchronization,該方案很早就已經被廣泛應用於 PC 上。我們可以把它理解為一種時鍾中斷。
VSYNC 是一種圖形技術,它可以同步 GPU 的 幀速率 和顯示器的 刷新頻率 ,所以在理解 VSYNC 產生的原因及其作用之前,我們有必要先來了解下這兩個概念。
表示屏幕在一秒內刷新畫面的次數, 刷新頻率取決於硬體的固定參數,單位 Hz(赫茲)。例如常見的 60 Hz、144 Hz,即每秒鍾刷新 60 次或 144 次。
逐行掃描
顯示器並不是一次性將畫面顯示到屏幕上,而是從左到右邊,從上到下逐行掃描顯示,不過這一過程快到人眼無法察覺到變化。以 60 Hz 刷新率的屏幕為例,即 1000 / 60 ≈ 16ms。
表示 GPU 在一秒內繪制操作的幀數,單位 fps。例如在電影界採用 24 幀的速度足夠使畫面運行的非常流暢。而 Android 系統則採用更加流程的 60 fps,即每秒鍾繪制 60 幀畫面。更多內容參考《 Why 60 fps 》。
現在,刷新頻率和幀率需要一起合作,才能使圖形內容呈現在屏幕上,GPU 會獲取圖形數據進行繪制, 然後硬體負責把圖像內容呈現到屏幕上,這一過程在應用程序的生命周期內一遍又一遍的發生。
如上圖,CPU / GPU 生成圖像的 Buffer 數據,屏幕從 Buffer 中讀取數據刷新後顯示。理想情況下幀率和刷新頻率保持一致,即每繪制完成一幀,顯示器顯示一幀。不幸的是,刷新頻率和幀率並不總是能夠保持相對同步,如果幀速率實際比刷新率快,例如幀速率是 120 fps,顯示器的刷新頻率為 60 Hz。此時將會發生一些視覺上的問題。
當 GPU 利用一塊內存區域寫入一幀數據時,從頂部開始新一幀覆蓋前一幀,並立刻輸出一行內容。當屏幕刷新時,此時它並不知道圖像緩沖區的狀態,因此從緩沖區抓取的幀並不是完整的一幀畫面(繪制和屏幕讀取使用同一個緩沖區)。此時屏幕顯示的圖像會出現上半部分和下半部分明顯偏差的現象,這種情況被稱之為 「tearing」(屏幕撕裂)。
那如何防止 「tearing」 現象的發生呢?由於圖像繪制和讀取使用的是同一個緩沖區,所以屏幕刷新時可能讀取到的是不完整的一幀畫面。解決方案是採用 Double Buffer。
Double Buffer(雙緩沖)背後的思想是讓繪制和顯示器擁有各自的圖像緩沖區。GPU 始終將完成的一幀圖像數據寫入到 Back Buffer ,而顯示器使用 Frame Buffer ,當屏幕刷新時,Frame Buffer 並不會發生變化,Back Buffer 根據屏幕的刷新將圖形數據 到 Frame Buffer,這便是 VSYNC 的用武之地。
在 Android 4.1 之前,Android 便使用的雙緩沖機制。怎麼理解呢?一般來說,在同一個 View Hierarchy 內的不同 View 共用一個 Window,也就是共用同一個 Surface。
每個 Surface 都會有一個 BufferQueue 緩存隊列,但是這個隊列會由 SurfaceFlinger 管理,通過匿名共享內存機制與 App 應用層交互。
整個流程如下:
但是 UI 繪制任務可能會因為 CPU 在忙別的事情,導致沒來得及處理。所以 從 Android 4.1 開始, VSYNC 則更進一步,現在 VSYNC 脈沖信號開始用於下一幀的所有處理 。
Project Butter 首先對 Android Display 系統的 SurfaceFlinger 進行了改造,目標是提供 VSYNC 中斷。每收到 VSYNC 中斷後,CPU 會立即准備 Buffer 數據,由於大部分顯示設備刷新頻率都是 60 Hz(一秒刷新 60 次),也就是說一幀數據的准備工作都要在 16ms 內完成。
這樣應用總是在 VSYNC 邊界上開始繪制,而 SurfaceFlinger 總是在 VSYNC 邊界上進行合成。這樣可以消除卡頓,並提升圖形的視覺表現。
如果理解了雙緩沖機制的原理,那就非常容易理解什麼是三緩沖區了。如果只有兩個 Graphic Buffer 緩沖區 A 和 B,如果 CPU / GPU 繪制過程較長,超過一個 VSYNC 信號周期。
由上圖可知:
為什麼 CPU 不能在第二個 16ms 處理繪制工作呢?原因是只有兩個 Buffer,緩沖區 B 中的數據還沒有準備完成,所以只能繼續展示 A 緩沖區的內容,這樣緩沖區 A 和 B 都分別被顯示設備和 GPU 佔用,CPU 則無法准備下一幀的數據。如果再提供一個緩沖區,CPU、GPU 和顯示設備都能使用各自的緩沖區工作,互不影響。
簡單來說,三重緩沖機制就是在雙緩沖機制基礎上增加了一個 Graphic Buffer 緩沖區,這樣可以最大限度的利用空閑時間,帶來的壞處是多使用的一個 Graphic Buffer 所佔用的內存。
由上圖可知:
Choreographer 也是 Project Butter 計劃新增的機制,用於配合系統的 VSYNC 中斷信號。它本質是一個 Java 類,如果直譯的話為舞蹈指導,這是一個極富詩意的表達,看到這個詞不得不贊嘆設計者除了 Coding 之外的廣泛視野。舞蹈是有節奏的,節奏使舞蹈的每個動作更加協調和連貫;視圖刷新也是如此。
Choreographer 可以接收系統的 VSYNC 信號,統一管理應用的輸入、動畫和繪制等任務的執行時機。Android 的 UI 繪制任務將在它的統一指揮下,井然有序的完成。業界一般通過它來監控應用的幀率。
Choreographer 的構造方法:
優先順序的高低和處理順序有關。當收到 VSYNC 信號時,Choreographer 將首先處理 INPUT 類型的回調,然後 ANIMATION 類型,最後才是 TRAVERSAL 類型。
另外,Android 在 4.1 還對 Handler 機制進行了略微改造,使之支持 Asynchronous Message(非同步消息) 和 Synchronization Barrier(同步屏障)。一般情況下同步消息和非同步消息的處理方式並沒有什麼區別,只有在設置了 同步屏障 時才會出現差異。 同步屏障為 Handler 消息機制增加了一種簡單的優先順序關系,非同步消息的優先順序要高於同步消息 。簡單點說,設置了同步屏障之後,Handler 只會處理非同步消息。
以 View 的繪制流程為例:
scheleTraversals 首先禁止了後續消息的處理能力,一旦設置了消息隊列的 postSyncBarrier,所有非 Asynchronous 的消息將被停止派發。
UI 繪制任務設置了 CALLBACK 類型為 TRAVERSAL 類型的任務,即 mTraversalRunnable:
Choreographer 的 postCallback 方法將會申請一次 VSYNC 中斷信號,通過 DisplayEventReceiver 的 scheleVsync 方法。當 VSYNC 信號到達時,便會回調 Choreographer 的 doFrame 方法,內部會觸發已經添加的回調任務:
此時 UI 繪制任務 doTraversal 方法被回調,即在 Android 4.1 之後, UI 繪制任務被放置到了 VSYNC 中斷處理中了。Choreographer 確實做到了統一協調管理 UI 的繪制工作。有關 Choreographer 更詳細的分析,可以參考《 Android 之 Choreographer 詳細分析 》。
在從根本解決 Android UI 不流暢的問題上,Project Butter 黃油計劃率先邁出了最重要一步,Android 的渲染性能也確實有了很大改善。
不過優化是無止境的,Google 在後續版本中又引入了一些比較大的改變,例如 Android 5.0 的 RenderThread,Android 將所有的繪制任務都放到了該線程,這樣即便主線程有耗時的操作也可以保證動畫流暢性。
關於 UI 渲染所涉及的內容非常多,而且 Android 渲染框架演進的非常快,文章最後也會附上一些(8)android繪制原理擴展閱讀,便於更好的學習理解。
文中如有不妥或有更好的分析結果,歡迎您的留言或指正。文章如果對你有幫助,請留個贊吧。
其他系列專題
9. android的自定義View的實現原理哪位能給我個思路呢。謝謝。
如果說要按類型來劃分的話,自定義View的實現方式大概可以分為三種,自繪控制項、組合控制項、以及繼承控制項。那麼下面我們就來依次學習一下,每種方式分別是如何自定義View的。
一、自繪控制項
自繪控制項的意思就是,這個View上所展現的內容全部都是我們自己繪制出來的。繪制的代碼是寫在onDraw()方法中的,而這部分內容我們已經在Android視圖繪制流程完全解析,帶你一步步深入了解View(二)中學習過了。
下面我們准備來自定義一個計數器View,這個View可以響應用戶的點擊事件,並自動記錄一共點擊了多少次。新建一個CounterView繼承自View,代碼如下所示:
<?xmlversion="1.0"encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#ffcb05">
<Button
android:id="@+id/button_left"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:layout_marginLeft="5dp"
android:background="@drawable/back_button"
android:text="Back"
android:textColor="#fff"/>
<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="ThisisTitle"
android:textColor="#fff"
android:textSize="20sp"/>
</RelativeLayout>
在這個布局文件中,我們首先定義了一個RelativeLayout作為背景布局,然後在這個布局裡定義了一個Button和一個TextView,Button就是標題欄中的返回按鈕,TextView就是標題欄中的顯示的文字。
接下來創建一個TitleView繼承自FrameLayout,代碼如下所示:
{
privateButtonleftButton;
privateTextViewtitleText;
publicTitleView(Contextcontext,AttributeSetattrs){
super(context,attrs);
LayoutInflater.from(context).inflate(R.layout.title,this);
titleText=(TextView)findViewById(R.id.title_text);
leftButton=(Button)findViewById(R.id.button_left);
leftButton.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){
((Activity)getContext()).finish();
}
});
}
publicvoidsetTitleText(Stringtext){
titleText.setText(text);
}
publicvoidsetLeftButtonText(Stringtext){
leftButton.setText(text);
}
(OnClickListenerl){
leftButton.setOnClickListener(l);
}
}
TitleView中的代碼非常簡單,在TitleView的構建方法中,我們調用了LayoutInflater的inflate()方法來載入剛剛定義的title.xml布局,這部分內容我們已經在Android LayoutInflater原理分析,帶你一步步深入了解View(一)這篇文章中學習過了。
接下來調用findViewById()方法獲取到了返回按鈕的實例,然後在它的onClick事件中調用finish()方法來關閉當前的Activity,也就相當於實現返回功能了。
另外,為了讓TitleView有更強地擴展性,我們還提供了setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法,分別用於設置標題欄上的文字、返回按鈕上的文字、以及返回按鈕的點擊事件。
到了這里,一個自定義的標題欄就完成了,那麼下面又到了如何引用這個自定義View的部分,其實方法基本都是相同的,在布局文件中添加如下代碼:
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.customview.TitleView
android:id="@+id/title_view"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</com.example.customview.TitleView>
</RelativeLayout>
這樣就成功將一個標題欄控制項引入到布局文件中了,運行一下程序。
現在點擊一下Back按鈕,就可以關閉當前的Activity了。如果你想要修改標題欄上顯示的內容,或者返回按鈕的默認事件,只需要在Activity中通過findViewById()方法得到TitleView的實例,然後調用setTitleText()、setLeftButtonText()、setLeftButtonListener()等方法進行設置就OK了。
10. android地圖縮小放大的時候maker之間聚合是什麼原理
<1>GeoPoint
表示一個地理坐標點,存放經度和緯度,以微度的整數形式存儲。
方法
GeoPoint(int latitudeE6, int longitudeE6)用給定的經緯度構造一個GeoPoint
方法介紹:
public int getLatitudeE6()
返回GeoPoint的緯度,單位微度
public int getLongitudeE6()
返回GeoPoint的經度,單位微度
public void setLatitudeE6(int latitudeE6)
設置GeoPoint的緯度,單位微度
public void setLongitudeE6(int longitudeE6)
設置GeoPoint的經度,單位微度
<2>介面 Projection
該介面用來在屏幕像素x/y坐標系和地球經緯度坐標系之間進行轉換,通過 MapView.getProjection()來取得映射類。
GeoPoint fromPixels(int x, int y)
該方法用給定的像素坐標創建一個新的GeoPoint。
給定的像素點是以MapView的左上角為原點的坐標系統,MapView提供了這個像素轉換器(PixelConverter)。 參數:result -
搜索結果iError - 錯誤號,0表示正確返回
Point toPixels(GeoPoint in, Point out)
把給定的GeoPoint變換到相對於MapView左上角的屏幕像素坐標。MapView提供了這種投影變換。 參數:in - 待變換的一對經緯度out
- 一個用於輸出預先存在的對象;如果為空,將返回一個新分配的像素點。
<3>MapController
處理地圖移動和縮放的工具類。
返回類型方法
voidanimateTo(GeoPoint point)對以給定的點GeoPoint,開始動畫顯示地圖。
voidanimateTo(GeoPoint point, Message message)對以給定的GeoPoint,開始動畫顯示地圖。
booleanonKey(View v, int keyCode, KeyEvent event)處理按鍵事件,把事件變換為適度的地圖平移。
voidscrollBy(int x, int y)按照給定的像素數據量滾動。
voidsetCenter(GeoPoint point)在給定的中心點GeoPoint上設置地圖視圖。
intsetZoom(int zoomLevel)設置地圖的縮放級別。
voidstopAnimation(boolean
jumpToFinish)終止所有未完成的動畫,有條件的把地圖中心修正到已完成的特殊動畫的偏移量上去。
voidstopPanning()重新設置平移狀態,使地圖靜止。
booleanzoomIn()放大一個級別。
booleanzoomInFixing(int xPixel, int yPixel)放大一個級別。
booleanzoomOut()縮小一個級別。
booleanzoomOutFixing(int xPixel, int yPixel)縮小一個級別。
voidzoomToSpan(int latSpanE6, int lonSpanE6)嘗試調整地圖的縮放,以便顯示給定的經緯度范圍。
<4>MapView
一個顯示地圖的視圖,當被焦點選中時,它能捕獲按鍵事件和觸摸手勢去平移和縮放地圖。
返回類型方法
booleancanCoverCenter()檢查當前是否有地圖貼片覆蓋地圖中心點。
protected booleancheckLayoutParams (android.view.ViewGroup.LayoutParams
p)僅檢查p是否是的一個MapView.LayoutParams實例。
voidcomputeScroll()捕獲滾動事件,用它們去平移地圖。
voiddisplayZoomControls(boolean takeFocus)顯示縮放控制項,可以選擇是否請求焦點選中以便通過按鍵訪問。
protected
android.view.ViewGroup.()返回一個Layout參數的集合,其中參數帶有ViewGroup.LayoutParams.WRAP_CONTENT的寬度,ViewGroup.LayoutParams.WRAP_CONTENT高度和坐標(0,0)。
protected
android.view.ViewGroup.(android.view.ViewGroup.LayoutParams
p)
android.view.ViewGroup.(AttributeSet
attrs)
MapControllergetController()返回地圖的MapController,這個對象可用於控制和驅動平移和縮放。
intgetLatitudeSpan()當前緯線的跨度(從地圖的上邊緣到下邊緣),十進制度×1,000,000。
intgetLongitudeSpan()當前經度的跨度(從地圖的左邊緣到地圖的右邊緣),單位:十進制的度×1,000,000。
GeoPointgetMapCenter()返回當前地圖中心點位置,做為一個GeoPoint(經度、緯度)的對象。
intgetMaxZoomLevel()返回當前視圖中心點的最大縮放級別。
java.util.ListgetOverlays()獲取Overlay列表。
ProjectiongetProjection()獲取屏幕像素坐標和經緯度對之間的轉換。
ViewgetZoomControls()已過時。
intgetZoomLevel()返回當前地圖的縮放級別。
booleanisSatellite()
booleanisStreetView()
booleanisTraffic()是否顯示交通流量。
protected voidonDetachedFromWindow()當被分離調用,清除縮放控制項。
protected voidonDraw(Canvas canvas)
voidonFocusChanged(boolean hasFocus, int direction, Rect
previouslyFocusedRect)當這個視圖的焦點狀態變化時被視圖系統調用。
booleanonKeyDown(int keyCode, KeyEvent event)把按鍵傳送到overlay。
booleanonKeyUp(int keyCode, KeyEvent event)把按鍵傳送到overlay。
protected voidonLayout(boolean flag, int l, int t, int r, int b)
protected voidonMeasure(int widthMeasureSpec, int heightMeasureSpec)
voidonRestoreInstanceState(Bundle state)把MapView的狀態恢復到一個Bundle。
voidonSaveInstanceState(Bundle state)把MapView的狀態存儲到一個Bundle中。
protected voidonSizeChanged(int w, int h, int oldw, int
oldh)重新調整地圖對象的尺寸。
booleanonTouchEvent(MotionEvent
event)首先把touch事件傳送到overlay,如果它不處理它們,就把事件再傳送到手勢探測器,然後分發探測到的手勢。
booleanonTrackballEvent(MotionEvent
event)把trackball事件首先傳送到overlay,如果它們不處理消息,嘗試取平移和點擊。
voidonWindowFocusChanged(boolean hasFocus)當包含這個視圖的窗口得到或是去焦點時被調用。
voidpreLoad()
voidsetDrawOverlayWhenZooming(boolean bDraw)設置在縮放動畫過程中是否繪制overlay,默認為不繪制。
如果繪制,在覆蓋物很多的情況下效率會有損失。 自1.1版本之後支持。
voidsetBuiltInZoomControls(boolean on)設置是否啟用內置的縮放控制項。
voidsetReticleDrawMode(com..mapapi.MapView.ReticleDrawMode
mode)暫不支持。
voidsetSatellite(boolean on)設置是否打開衛星圖。
voidsetStreetView(boolean on)暫不支持。
voidsetTraffic(boolean on)設置是否打開交通流量圖層。
voidregMapViewListener(BMapManager bmapMan, MKMapViewListener
listener)注冊地圖顯示事件監聽器。
<5>MyLocationOverlay
一個負責顯示用戶當前位置的Overlay。
Overlay是一個覆蓋,它繪制用戶當前在地圖上的位置(精準度),和/或一個嵌入的指南針。子類能覆蓋方法dispatchTap()去處理對當前位置的點擊。
為了開啟這個overlay的功能,需要去調用enableMyLocation()和/或enableCompass(),
或調用Activity中的Activity.onResume()方法。記住,當在後台是,要在Activity中的Activity.onPause()方法中調用相應的disableMyLocation()和/或disableCompass()關閉這個功能。
返回類型方法
voiddisableCompass()關閉指南針的更新。
voiddisableMyLocation()停止位置更新。
protected booleandispatchTap()在「我的位置」坐標上處理點擊事件。
booleandraw(Canvas canvas, MapView mapView, boolean shadow, long
when)繪制方法。
protected voiddrawCompass(android.graphics.Canvas canvas, float
bearing)繪制指南針。
protected voiddrawMyLocation(Canvas canvas, MapView mapView,
android.location.Location lastFix, GeoPoint myLocation, long when)繪制「我的位置」點。
booleanenableCompass()開啟指南針更新功能。
booleanenableMyLocation()嘗試開啟MyLocation功能,並向MKLocationManager.GPS_PROVIDER和MKLocationManager.NETWORK_PROVIDER注冊更新。
LocationgetLastFix()返回一個位置,對應於最近設定的用戶位置。
GeoPointgetMyLocation()返回一個GeoPoint,對應於一個最近設定的用戶位置。
floatgetOrientation()返回最近設定的的指南針朝向。
booleanisCompassEnabled()檢查指南針小部件是否被顯示。
booleanisMyLocationEnabled()
voidonAccuracyChanged(Sensor sensor, int accuracy)
voidonLocationChanged(Location location)監聽並獲取位置更新。
voidonProviderDisabled(java.lang.String provider)
voidonProviderEnabled(java.lang.String provider)
voidonSensorChanged(SensorEvent event)當指南針的值變換時,由SensorManager調用。
booleanonSnapToItem(int x, int y, android.graphics.Point snapPoint, MapView
mapView)檢查給定的(x,y)是否和引起當前行為(如縮放)的item足夠靠近。
booleanonTap(GeoPoint p, MapView mapView)檢查點擊的位置是否非常接近於當前的位置(如果已知)。
booleanrunOnFirstFix(java.lang.Runnable
runnable)把一個runnable加入隊列,一旦收到一個位置信息,這個runnable就被執行。
<6>Overlay
Overlay是一個基類,它表示可以顯示在地圖上方的覆蓋overlay。
添加一個overlay時,從這個基類派生出一個子類,創建一個實例,然後把它加入到一個列表中。這個列表通過調用MapView.getOverlays()得到。為了允許用戶觸摸去對齊一個點,子類應當實現Overlay.Snappable介面。
返回類型方法
booleandraw(Canvas canvas, MapView mapView, boolean
shadow)在地圖上繪制overlay。
booleandraw(Canvas canvas, MapView mapView, boolean shadow, long
when)專門繪制動畫overlay的調用。
protected static voiddrawAt(Canvas canvas, Drawable drawable, int x, int y,
boolean bShadow)在某個偏移位置畫一個Drawable的便捷方法。
booleanonKeyDown(int keyCode, KeyEvent event, MapView
mapView)處理一個按鍵被按下的事件。
booleanonKeyUp(int keyCode, KeyEvent event, MapView mapView)處理一個按鍵放開事件。
booleanonTap(GeoPoint p, MapView mapView)處理一個「點擊」事件。
booleanonTouchEvent(MotionEvent e, MapView mapView)處理一個觸摸事件。
booleanonTrackballEvent(MotionEvent e, MapView mapView)處理一個軌跡球事件。