你真的了解View的坐標嗎?

avtp290q 8年前發布 | 8K 次閱讀 安卓開發 Android開發 移動開發

閑聊

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

 

 本文由用戶 avtp290q 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!