編輯:Android資訊
Android中控件大致被分為兩類ViewGroup,View。ViewGroup作為容器管理View。Android視圖,是類似於Dom樹的架構。父視圖負責測量定位繪制等操作。我們經常在用的findViewById方法代價昂貴的原因,就是因為他負責至上而下遍歷整棵控件樹,來尋找View實例,在重復操作中盡量少用。現在在用的很多控件都是直接或者間接繼承自View的,如下圖。
每個Activity包含一個PhoneWindow對象,PhoneWindow設置DecorView為應用窗口的根視圖。在裡面就是熟悉的TitleView和ContentView,沒錯,平時使用的setContentView()就是設置的ContentView。
當一個Activity啟動時,會被要求繪制出它的布局。Android框架會處理這個請求,當然前提是Activity提供了合理的布局。繪制從根視圖開始,從上至下遍歷整棵視圖樹,每一個ViewGroup負責讓自己的子View被繪制,每一個View負責繪制自己,通過draw()方法.繪制過程分三步走。
整個繪制流程是在ViewRoot中的performTraversals()方法展開的。部分源代碼如下。
private void performTraversals() { ...... //最外層的根視圖的widthMeasureSpec和heightMeasureSpec由來 //lp.width和lp.height在創建ViewGroup實例時等於MATCH_PARENT int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ...... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); ...... mView.draw(canvas); ...... }
在繪制之前當然要知道view的尺寸和繪制。所以先進行measu和layout(測量和定位),如下圖。
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { //.... //回調onMeasure()方法 onMeasure(widthMeasureSpec, heightMeasureSpec); //more }
計算view的實際大小,獲得高寬存入mMeasuredHeight和mMeasureWidth,measure(int, int)傳入的兩個參數。MeasureSpec是一個32位int值,高2位為測量的模式,低30位為測量的大小。測量的模式可以分為以下三種。
EXACTLY
精確值模式,當layout_width或layout_height指定為具體數值,或者為match_parent時,系統使用EXACTLY。
AT_MOST
最大值模式,指定為wrap_content時,控件的尺寸不能超過父控件允許的最大尺寸。
根據上面的源碼可知,measure方法不可被重寫,自定義時需要重寫的是onMeasure方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
查看源碼可知最終的高寬是調用setMeasuredDimension()設定的,如果不重寫,默認是直接調用getDefaultSize獲取尺寸的。
使用View的getMeasuredWidth()和getMeasuredHeight()方法來獲取View測量的寬高,必須保證這兩個方法在onMeasure流程之後被調用才能返回有效值。
Layout方法就是用來確定view布局的位置,就好像你知道了一件東西的大小以後,總要知道位置才能畫上去。
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
layout獲取四個參數,左,上,右,下坐標,相對於父視圖而言。這裡可以看到,使用了剛剛測量的寬和高。
public void layout(int l, int t, int r, int b) { int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { ..... onLayout(changed, l, t, r, b); ..... }
通過setFrame設置坐標。如果坐標改變過了,則重新進行定位。如果是View對象,那麼onLayout是個空方法。因為定位是由ViewGroup確定的。
當layout結束以後getWidth()與getHeight()才會返回正確的值。
這裡出現一個問題,getWidth/Height()andgetMeasuredWidth/Height()有什麼區別?
public void draw(Canvas canvas) { ...... /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed ...... if (!dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) ...... // Step 2, save the canvas' layers ...... if (drawTop) { canvas.saveLayer(left, top, right, top + length, null, flags); } ...... // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ...... if (drawTop) { matrix.setScale(1, fadeHeight * topFadeStrength); matrix.postTranslate(left, top); fade.setLocalMatrix(matrix); p.setShader(fade); canvas.drawRect(left, top, right, top + length, p); } ...... // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); ...... }
重點是第三步調用onDraw方法。其它幾步都是繪制一些邊邊角角的東西比如背景、scrollBar之類的。其中dispatchDraw,是用來遞歸調用子View,如果沒有則不需要。
onDraw方法是需要自己實現的,因為每個控件繪制的內容不同。主要用canvas對象進行繪制,這裡就不說了。
本文由碼農網 – 小峰原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃! 隨著Android平台持續驚人的增長,越來越多的開發人員開始工作於
我們先假設一個場景需求:剛有孩子的爸爸媽媽對用照片、視頻記錄寶寶成長有強烈的意願,但苦於目前沒有一款專門的手機APP做這件事。A公司洞察到市場需求,要求開發團隊盡
隨著現在社交網絡的日益繁多,眾多的社交客戶端已占據了人們的大量時間,所以在我們的應用中具有一鍵分享的功能對提高我們產品的知名度有很大的幫助。新浪微博、騰訊微博、騰
本文由碼農網 – 小峰原創翻譯,轉載請看清文末的轉載要求,歡迎參與我們的付費投稿計劃! 先來看看我家裡的一面搭載了Android應用的鏡子,上圖: 擁有