【本文出自大圣代的技術專欄 http://blog.csdn.net/qq_23191031】
【轉載煩請注明出處,尊重他人勞動成果就是對您自己的尊重】
前言
View在Android的世界中扮演著重要的角色,正是這些控件組成了一個又一個精美的App。View體系是Android界面編程的核心,雖然它不屬于四大組件但是它的重要行卻毫不遜色,這個系列我會陸續從View的滑動事件、View 的事件反饋、自定義View等多個方面逐步介紹Android View體系。如果能幫助到你,那是我莫大的榮幸。
Android控件框架
在Android的世界中View是所有控件的基類(祖宗),其中也包括ViewGroup在內。View是一個抽象的概念,特指界面中的某一個控件。而ViewGroup是代表著控件的集合,其中可以包含多個View控件,并管理他們。從某種角度上來講Android中的控件可以分為兩大類:View與ViewGroup。通過ViewGroup,整個界面的控件形成了一個樹形結構,這也就是我們常說的控件樹,上層的控件要負責測量與繪制下層的控件,并傳遞交互事件。我們在開發中常常使用到的findViewById()方法,就是在控件樹中進行深度遍歷來查找對應元素的。在每棵控件樹的頂部都存在著一個ViewParent對象,它是整棵控件樹的核心所在,所有的交互管理事件都由它來統一調度和分配,從而對整個視圖進行整體控制。
在每一個Activity中都包含了一個Window,而這個Window通常上是由PhoneWindow實現的,而PhoneWindow又將DecorView設置為整個界面的根布局,DecorView作為根布局將要顯示的具體內容呈現在PhoneWindow上,并提供了一些通用方法來操作界面。這里所有View的交互事件都由WindowManagerService(WMS)進行接收,并通過Activity回調相應的onClickListener。
在上面的視圖上我們可以看到此時屏幕被分成了兩部分:TitleView與ContentView。如圖紅色的區域就是ContentView,contentView是一個ID為content的Framelayou這也是我們通過布局文件可以控制的區域,實際上我們所有的布局都設置在這樣的Fragmelayout中。
這也就是為什么Activity、Fragment中設置根布局的方法叫做setContentView了。
插播: requestWindowFeature(Window.FEATURE_NO_TITLE) 與 setContentView() 調用順序的關系
在設置setContentView()方法之前我們可以通過
requestWindowFeature(Window.FEATURE_NO_TITLE)
方法設置標簽來顯示全屏。如果你看了Activity源碼中的setContentVeiw()方法你會發現,當setContentView()一旦調用,ContentView布局與TitleView會同時被加載,加載之后在調用requestWindowFeature(Window.FEATURE_NO_TITLE)
方法設置標簽已經沒有作用了。所以只有在setContentView()
方法之前設置標簽才能剔除TitleView
達到ContentView
占據全屏的效果。
當Acitivity的生命周期中,當onCreate()方法中調用setContentView方法后,ActivityManagerService(AMS)會調用onResume()方法,此時系統才會把整個DecorView添加到PhoneWindow中顯示出來,至此界面回執完成。
貼一張圖匯總一下吧
更詳細的說明請參見【Android View源碼分析(一)】setContentView加載視圖機制深度分析
Android的常用坐標系
在Android的世界中我們最常用到的就是Android坐標系(我認為稱為世界坐標系更準確)和視圖坐標系了。對于一個控件而言,它在Android世界坐標系中的位置我們可以稱之為:絕對坐標系;而在視圖坐標系中,指示的就是它的相對位置了。下面我們就來分析一下他們吧
1,世界坐標系
在Android的世界中,屏幕的左上角定點作為世界坐標系的原點,從這個原點水平向右為X軸正方向,原點垂直向下為Y軸正反向。
Android系統中為我們提供了getLocationOnScreen(int[] location)
方法來獲取控件在整個屏幕的絕對坐標,此時要注意的是:該坐標是從屏幕的左上角(原點)開始獲取的,所以也包括了狀態欄的高度,如下圖。
1.1,世界坐標系中屏幕區域的劃分
通過上圖我們可以很直觀的看到Android的屏幕區域是如何劃分的。接下來我們就看看如何或者這些區域中的坐標和度量方法吧。
//獲取屏幕區域的寬高等尺寸獲取
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int widthPixels = metrics.widthPixels;
int heightPixels = metrics.heightPixels;
//應用程序App區域寬高等尺寸獲取
Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
//獲取狀態欄高度
Rect rect= new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
int statusBarHeight = rectangle.top;
//View布局區域寬高等尺寸獲取
Rect rect = new Rect();
getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(rect);
注意:
這些方法最好都在Activity的
onWindowFocusChanged()
方法之后調用,因為在Activity的聲明周期中 onCreate、onStar、 onResume這些方法都不是界面visible的真正時刻,真正的visible是在onWindowFocusChanged()
方法執行時才被執行的。(onWindowFocusChanged()
是在onResume()
之后調用的,所以有的文章也會說是onResume()
之后調用,其實更加準確的是在onWindowFocusChanged()
之后,此處我會在以后的文章中做詳細介紹)
2,視圖坐標系
在日常開發中我們接觸最對的就是視圖坐標系了,視圖坐標系描述的是子控件在父控件中相對位置。貼一張圖來說明一下
所謂視圖坐標系是以控件(例如圖中的TextView)父視圖(圖中的ViewGroup))的左上角為坐標原點的(綠色部分),從原出發水平向右為x軸正方向,垂直向下為y軸正方向來表示控件的相對位置的。
那么這個相對位置到底如何表示呢,同樣看圖說話。
簡單的總結一下:
View提供的獲取坐標方法
通過如下方法可以獲得View到其父控件(ViewGroup)的距離:
方法 | 解釋 |
---|---|
getTop() | 獲取View自身頂邊到其父布局頂邊的距離 |
getLeft() | 獲取View自身左邊到其父布局左邊的距離 |
getRight() | 獲取View自身右邊到其父布局左邊的距離 |
getBottom() | 獲取View自身底邊到其父布局頂邊的距離 |
getX() | 返回值為getLeft()+getTranslationX(),當setTranslationX()時getLeft()不變,getX()變。 |
getY() | 返回值為getTop()+getTranslationY(),當setTranslationY()時getTop()不變,getY()變。 |
MotionEvent提供的獲取坐標方法
我們看上圖那個觸摸點,我們知道無論是View還是ViewGroup,最終的點擊事件都會由onTouchEvent(MotionEvent event)方法來處理,MotionEvent也提供了各種獲取焦點坐標的方法:
方法 | 解釋 |
---|---|
getX() | 獲取點擊事件距離控件左邊的距離,即視圖坐標 |
getY() | 獲取點擊事件距離控件頂邊的距離,即視圖坐標 |
getRawX() | 獲取點擊事件距離整個屏幕左邊距離,即絕對坐標 |
getRawY() | 獲取點擊事件距離整個屏幕頂邊的的距離,即絕對坐標 |
注意:
View中的
getX()
、getY()
方法只是與MotionEvent中的getX(
)、getY()
方法只是重名而已,并不是一個。
上面就解釋了你在很多代碼中看見各種getXXX方法進行數學邏輯運算判斷的含義。不過上面只是說了一些相對靜止的世界坐標點關系,下面我們來看看幾個和上面方法緊密相關的View方法,此處在本篇文章中不是重點,我會在以后的文章中做詳細講解。:
View寬高方法 | 解釋 |
---|---|
getWidth() | layout后有效,返回值是mRight-mLeft,一般會參考measure的寬度(measure可能沒用),但不是必須的。 |
getHeight() | layout后有效,返回值是mBottom-mTop,一般會參考measure的高度(measure可能沒用),但不是必須的。 |
getMeasuredWidth() | 返回measure過程得到的mMeasuredWidth值,供layout參考,或許沒用。 |
getMeasuredHeight() | 返回measure過程得到的mMeasuredHeight值,供layout參考,或許沒用。 |
參考:
如果說我比別人看得更遠些,那是因為我站在了巨人的肩上
《Android群英傳第三章》
Android中的坐標系以及獲取坐標的方法
Android應用坐標系統全面詳解