你真的了解View的坐標嗎?
閑聊
View,對我們來說在熟悉不過了,從接觸Android開始,我們就一直在接觸View,界面當中到處都是 View,比如我們經常用到的TextView,Button,LinearLayout等等,但是我們真的了解View嗎?尤其是View的坐標。mLeft,mRight,mY,mX,mTranslationY,mScoollY,相對于屏幕的坐標等等這些概念你真的清楚了嗎?如果真的清楚了,那你沒有必要度這篇博客,如果你還是有一些模糊,建議花上幾分鐘的時間讀一下,這篇博客較短,花個幾分鐘的時間就可以閱讀完。
為什么要寫這一篇博客呢?
因為掌握View的坐標很重要,尤其是對于自定義View,學習動畫有重大的意義。
這篇博客主要講解一下問題
- View 的 getLeft()和get Right()和 getTop() 和getBottom()
- View 的 getY(), getTranslationY() 和 getTop() 之間的聯系
- View 的 getScroolY 和 View 的 scrollTo() 和 scrollBy()
- event.getY 和 event.getRawY()
- 擴展,怎樣獲取狀態欄(StatusBar)和標題欄(titleBar)的高度
基本概念
簡單說明一下(上圖Activity采用默認Style,狀態欄和標題欄都會顯示):最大的草綠色區域是屏幕界面,紅色次大區域我們稱之為“應用界面區域”,最小紫色的區域我們稱之為“View繪制區域”;屏幕頂端、應用界面區之外的那部分顯示手機電池網絡運營商信息的為“狀態欄”,應用區域頂端、View繪制區外部顯示Activity名稱的部分我們稱為“標題欄”。
從這張圖片我們可以看到
在Android中,當ActionBar存在的情況下,
屏幕的 高度=狀態欄+應用區域的高度=狀態欄的 高度+(標題欄的 高度+View 繪制區域的高度)
當ActionBar不存在的情況下
屏幕的高度=狀態欄+應用區域的高度=狀態欄的 高度+(View 繪制區域的 高度)
View 的 getLeft()和getRight()和 getTop() 和getBottom()
View.getLeft() ;
View.getTop() ;
View.getBottom();
View.getRight() ;
top是左上角縱坐標,left是左上角橫坐標,right是右下角橫坐標,bottom是右下角縱坐標,都是相對于它的 直接父View 而言的,而不是相對于 屏幕 而言的。這一點要區分清楚。那那個坐標是相對于屏幕而言的呢,以及要怎樣獲取相對于屏幕的坐標呢?
目前View里面的變量還沒有一個是相對于屏幕而言的,但是我們可以獲取到相對于屏幕的坐標。一般來說,我們要獲取View的坐標和高度 等,都必須等到View繪制完畢以后才能獲取的到,在Activity 的 onCreate()方法 里面 是獲取不到的,必須 等到View繪制完畢以后才能獲取地到View的響應的坐標,一般來說,主要 有以下兩種方法。
第一種方法,onWindowFocusChanged()方法里面進行調用
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//確保只會調用一次
if(first){
first=false;
final int[] location = new int[2];
mView.getLocationOnScreen(location);
int x1 = location[0] ;
int y1 = location[1] ;
Log.i(TAG, "onCreate: x1=" +x1);
Log.i(TAG, "onCreate: y1=" +y1);
}
}
第二種方法,在視圖樹繪制完成的時候進行測量
mView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver
.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// 移除監聽器,確保只會調用一次,否則在視圖樹發揮改變的時候又會調用
mView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
final int[] location = new int[2];
mView.getLocationOnScreen(location);
int x1 = location[0];
int y1 = location[1];
Log.i(TAG, "onCreate: x1=" + x1);
Log.i(TAG, "onCreate: y1=" + y1);
}
});</code></pre>
View 的 getY(), getTranslationY() 和 getTop() 之間的聯
getY()
Added in API level 14
The visual y position of this view, in pixels.(返回的是View視覺上的圖標,即我們眼睛看到位置的Y坐標,默認值跟getTop()相同,別急,下面會解釋)
getTranslationY()
Added in API level 14
The vertical position of this view relative to its top position, in pixels.(豎直方向上相對于top的偏移量,默認值為0)
那 getY() 和 getTranslationY() 和 getTop () 到底有什么關系呢?
@ViewDebug.ExportedProperty(category = "drawing")
public float getY() {
return mTop + getTranslationY();
}
@ViewDebug.ExportedProperty(category = "drawing")
public float getTranslationY() {
return mRenderNode.getTranslationY();
}
@ViewDebug.CapturedViewProperty
public final int getTop() {
return mTop;
}</code></pre>
從以上的源碼我們可以知道 getY()= getTranslationY()+ getTop (),而 getTranslationY() 的默認值是0,除非我們通過 setTranlationY() 來改變它,這也就是我們上面上到的 getY 默認值跟 getTop()相同
那我們要怎樣改變 top值 和 Y 值呢? 很明顯就是調用相應的set方法 ,即 setY() 和setTop() ,就可以改變他們 的值。
View 的 getScroolY 和 View 的 scrollTo() 和 scrollBy()
getScrollY是一個比較特別的函數,因為它涉及一個值叫mScrollY,簡單說,getScrollY一般得到的都是0,除非你調用過scrollTo或scrollBy這兩個函數來改變它。
scrollTo() 和 scrollBy()
從字面意思我們可以知道 scrollTo() 是滑動到哪里的意思 ,scrollBy()是相對當前的位置滑動了多少。當然這一點在源碼中也是可以體現出來的
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
有幾點需要注意的是
- 不論是scrollTo或scrollBy,其實都是對View的內容進行滾動而不是對View本身,你可以做個小實驗,一個LinearLayouy背景是黃色,里面放置一個子LinearLayout背景是藍色,調用scrollTo或scrollBy,移動的永遠是藍色的子LinearLayout。
- 還有就是scrollTo和scrollBy函數的參數和坐標系是“相反的”,比如scrollTo(-100,0),View的內容是向X軸正方向移動的,這個相反打引號是因為并不是真正的相反,具體可以看源碼。
View 的 width 和 height
@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight() {
return mBottom - mTop;
}
我們可以看到 Android的 height 是由 mBottom 和 mTop 共同得出的,那我們要怎樣設置Android的高度呢?有人會說直接在xml里面設置 android:height="" 不就OK了,那我們如果要動態設置height的高度呢,怎么辦?你可能會想到 setWidth()方法?但是我們找遍了View的所有方法,都沒有發現 setWidth()方法,那要怎樣動態設置height呢?其實有兩種方法
int width=50;
int height=100;
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
if(layoutParams==null){
layoutParams=new ViewGroup.LayoutParams(width,height);
}else{
layoutParams.height=height;
}
view.setLayoutParams(layoutParams);
第二種方法,單獨地改變top或者bottom的值,這種方法不推薦使用
至于width,它跟height基本一樣,只不過它是有mRight 和mLeft 共同決定而已。
需要注意的是,平時我們在執行動畫的過程,不推薦使用LayoutParams來改變View的狀態,因為改變LayoutParams會調用requestLayout()方法,會標記當前View及父容器,同時逐層向上提交,直到ViewRootImpl處理該事件,ViewRootImpl會調用三大流程,從measure開始,對于每一個含有標記位的view及其子View都會進行測量、布局、繪制,性能較差,源碼體現如下:關于requestLayout ()方法的更多分析可以
public void setLayoutParams(ViewGroup.LayoutParams params) {
if (params == null) {
throw new NullPointerException("Layout parameters cannot be null");
}
mLayoutParams = params;
resolveLayoutParams();
if (mParent instanceof ViewGroup) {
((ViewGroup) mParent).onSetLayoutParams(this, params);
}
requestLayout();
}
因此我們如果在api 14 以后 ,在動畫執行過程中,要改變View的狀態,推薦使用setTranslationY()和setTranslationX(0等方法,而 盡量避免改變LayoutParams.因為性能嫌貴來說較差。
event.getY 和 event.getRawY()
要區分于MotionEvent.getRawX() 和MotionEvent.getX();,
在public boolean onTouch(View view, MotionEvent event) 中,當你觸到控件時,x,y是相對于該控件左上點(控件本身)的相對位置。 而rawx,rawy始終是相對于屏幕的位置。getX()是表示Widget相對于自身左上角的x坐標,而getRawX()是表示相對于屏幕左上角的x坐標值 (注意:這個屏幕左上角是手機屏幕左上角,不管activity是否有titleBar或是否全屏幕)。

擴展,怎樣獲取狀態欄(StatusBar)和標題欄(titleBar)的高度
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
//屏幕
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
Log.e(TAG, "屏幕高:" + dm.heightPixels);
//應用區域
Rect outRect1 = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(outRect1);
//這個也就是狀態欄的 高度
Log.e(TAG, "應用區頂部" + outRect1.top);
Log.e(TAG, "應用區高" + outRect1.height());
// 這個方法必須在有actionBar的情況下才能獲取到狀態欄的高度
//View繪制區域
Rect outRect2 = new Rect();
getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(outRect2);
Log.e(TAG, "View繪制區域頂部-錯誤方法:" + outRect2.top); //不能像上邊一樣由outRect2.top獲取,這種方式獲得的top是0,可能是bug吧
Log.e(TAG, "View繪制區域高度:" + outRect2.height());
int viewTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop(); //要用這種方法
Log.e(TAG, "View繪制區域頂部-正確方法:" + viewTop);
int titleBarHeight=viewTop;
Log.d(TAG, "onWindowFocusChanged: 標題欄高度titleBarHeight=" +titleBarHeight);
}
這里我們需要注意的 是在ActionBar存在的情況下,通過這種方法我們才能夠得出titleBar的高度,否則是無法得到的,因為viewTop 為0.
來自:http://www.jianshu.com/p/7709823642ec