[Android View 知識體系] 必知必會 View 基礎知識

在這裏插入圖片描述

前言

View 在 Android 世界中扮演着重要的角色,正是這些控件組成了一個又一個精美的 App。View 體系是 Android 界面編程的核心,雖然它不屬於四大組件但是它的重要性卻毫不遜色。

什麼是 View

在 Android 的世界中 View 是所有控件的基類,其中也包括 ViewGroup。View 是一個抽象的概念,特指某一個控件。而 ViewGroup 是代表着控件的集合,他的父類也是 View。 ViewGroup 中可以包含多個 View 並管理他們。通過 ViewGroup,整個界面的控件形成了一個樹形結構,這也就是我們常說的控件樹,上層的控件要負責測量與佈局下層的控件、傳遞交互事件。我們在開發中常常使用到的 findViewById() 方法,就是在控件樹中進行深度遍歷(比較耗時)來查找對應元素的。在每棵控件樹的頂部都存在着一個 ViewParent 對象,它是整棵控件樹的核心,所有的交互都由它統一調度和分配,從而對整個視圖進行控制。

View樹結構圖

ViewParent

ViewParent 定義了一些作爲 View 父類應具有的功能,當一個 View 與其父 View 交互時,就可以用到這些 API.

例如上圖綠色的 ViewGroup 節點都是下面子葉的 ViewParent。View 類中提供了getParent()方法用於獲取當前 View 的直接父節點,可以通過 view.getParent().getParent() 遍歷所有上層節點。

view.getParent() VS. view.getRootView()

結論

  1. 如果該 View 是 View 樹的根節點,getParent() 返回 null,
  2. 如果該 View 是 View 樹的非根節點,getParent() 返回其父 View
  3. getRootView 始終返回 View 樹的根 View

示例1: Activity

針對Activity,無論使用什麼佈局,整體結構都可以使用如下圖所示

在這裏插入圖片描述

  • 非根節點也就是非DecorView,其getParent()一定是它的父View,DecorView是View樹中根節點,其getParent()爲null
  • Activity的View樹中任何節點調用getRootView都是DecorView

示例2:inflate 形成View樹並且 rootView 傳遞以 null

View view = LayoutInflater.from(this).inflate(R.layout.title_bar,null);

使用佈局是title_bar.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rl_title_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <TextView
        android:id="@+id/tv_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="title" />
</RelativeLayout>
  • 非根節點也就是 TextView,其 getParent() 是 RelativeLayout ,該 View 樹中根節點爲 ReletiveLayout,其getParent() 是 null
  • 該 View 樹的任何 View 調用 getRootView() 都返回 RelativeLayout

Android的常用座標系

Android 座標系其實就是一個三維座標系,Z 軸向前,X 軸向右,Y軸向下。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-QUChEgcn-1570678961875)(assets/20150516112156313.jpeg)]

Android 屏幕區域的劃分

通過上圖我們可以很直觀的看到 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 = rect.top;
// View 佈局區域寬高等尺寸獲取
Rect rect = new Rect();  
getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(rect);  

特別注意

這些方法最好都在Activity的 onWindowFocusChanged() 方法之後調用,因爲在 Activity 生命週期中 onCreate、onStart、onResume 這些方法都不是界面 visible 的真正時刻,在onWindowFocusChanged()方法回調後纔是真正 visible 時刻。

Android View 座標系

絕對座標系(也稱:Android 座標系)

絕對座標系以屏幕左上角爲座標原點,從這個原點水平向右爲 X 軸正方向,原點垂直向下爲Y軸正反向。即爲上圖紅色部分。

在這裏插入圖片描述

相對座標系(也稱:視圖座標系)

所謂相對座標系(視圖座標系)是以控件父視圖的左上角爲座標原點的,從原出發水平向右爲 X 軸正方向,垂直向下爲 Y 軸正方向來表示控件的相對位置的。

在這裏插入圖片描述

獲取 View 位置的常見方法:

  1. View.getTop、View.getBotoom、View.getLeft、View.getRight
  2. View.getX、 View.getY
  3. View.getTranslationX、View.getTranslationY
  4. View.getLocationInWindow、 View.getLocationOnScreen

View.getXXX()

這些方法獲取的都是相對父容器的原始位置。當 View 發生移動的時候這些方法的值都是保持不變的。

View 的寬高和座標系的關係:

width = getRight() - getLeft()
height = getBottom() - getTop()

translationX、translationY

translationX 表示的是當前 View 對於父 View 發生的偏移量,一開始的時候 translationX = 0,當 View 發生移動的時候 getTop、getRight 這些值是不會發生改變的,改變只有表示偏移量的 translationX 。

View.getX、 View.getY

表示獲取 View 在 Android 座標系中的絕對位置,它與 getLeft、getTranslateX 存在如下關係

getX() = getTranslationX() + getLeft()
getY() = getTranslationY() + getTop()

getLocatonInWindow()、getLocationOnScreen()

getLocationInWindow() :獲取的是一個控件在其所在 window 的座標位置
getLocationOnScreent(): 獲取的是控件在屏幕上的座標位置

getLocationInWindow()是以B爲原點的C的座標。
getLocationOnScreen 以A爲原點,包括了狀態欄的高度

一般情況下一個正常的 Activity 的 Window 是充滿屏幕的,所以這兩個方法將會返回同樣的 x 和 y 座標,僅僅在一些特殊的場景下,例如 dialog 他有屬於自己的 window 這個 Acitivty 的 Window 和屏幕是存在偏移量的,這兩個方法返回的結果將不同。

**注意:**這兩個方法在 Activity 的 onCreate 中獲取的座標永遠是 0,要等 UI 控件都加載完成之後才能獲取。在onWindowFocusChanged() 中獲取最好。因爲在生命週期:onCreate、onStart、onResume中真正的View都沒有可見。

引自 onWindowFocusChanged() 官方文檔:

Called when the current Window of the activity gains or loses focus. This is the best indicator of whether this activity is visible to the user. The default implementation clears the key tracking state, so should always be called.

參考 staticoverflow

總結

方法 解釋
getTop() 獲取 View 自身頂邊到其父佈局頂邊的距離
getLeft() 獲取 View 自身左邊到其父佈局左邊的距離
getRight() 獲取 View 自身右邊到其父佈局左邊的距離
getBottom() 獲取 View 自身底邊到其父佈局頂邊的距離
getX() 返回值爲 getLeft()+getTranslationX(),當setTranslationX()時getLeft()不變,getX()變
getY() 返回值爲 getTop()+getTranslationY(),當setTranslationY()時getTop()不變,getY()變
View寬高方法 解釋
getWidth() layout 後有效,返回值是 mRight-mLeft,表示最終寬度
getHeight() layout後有效,返回值是mBottom-mTop,表示最終高度
getMeasuredWidth() 返回 measure 過程中得到的 mMeasuredWidth 值,表示中間值,供layout參考,或許沒用。
getMeasuredHeight() 返回 measure 過程中得到的 mMeasuredHeigh t值,表示中間值,供layout參考,或許沒用。

MotionEvent、TouchSlop

在這裏插入圖片描述

MotionEvent

MotionEvent 代表手指接觸屏幕後所產生的一些列事件,典型的事件類型有如下幾種:

  • ACTION_DOWN: 手機剛接觸屏幕;
  • ACTION_MOVE: 手機在屏幕上移動;
  • ACTION_UP: 手機從屏幕上鬆開的一瞬間;

如上圖所示,當我們觸摸屏幕的時候點擊到的無論是 View 還是 ViewGroup,最終的點擊事件都會由onTouchEvent(MotionEvent event) 方法來處理,MotionEvent 也提供了各種獲取焦點座標的方法:

方法 解釋
getX() 獲取點擊事件距離**當前 View **左邊框的距離,即視圖座標
getY() 獲取點擊事件距離**當前 View **頂部邊框的距離,即視圖座標
getRawX() 獲取點擊事件距離整個屏幕的左邊距離,即絕對座標
getRawY() 獲取點擊事件距離整個屏幕頂部的距離,即絕對座標

特別注意:View中的getX()getY()方法只是與MotionEvent中的getX()、getY()方法只是重名而已,並不是一個。

TouchSlop

TouchSlop 是系統所能識別出的、認爲是滑動的最小距離。 它是一個常量,和設備有關,可以通過如下方法獲得:

 ViewConfiguration.get(this).getScaledTouchSlop();

如果兩次滑動事件的距離小於這個值,我們就可以認爲他們並不是滑動,這樣就可以給用戶更好的體驗。

VelocityTracker、GestureDetecotr 和 Scroller

VelocityTracker

用於追蹤手指在滑動過程中的速度,包括水平和豎直方向的速度。使用方法如下:

   @Override
    public boolean onTouchEvent(MotionEvent event) {
        VelocityTracker velocityTracker = VelocityTracker.obtain();
        // 將事件加入到追蹤器
        velocityTracker.addMovement(event);
        // 調用計算代碼,這個是必須的,指定計算時間間隔爲 1s
        velocityTracker.computeCurrentVelocity(1000);
        // 獲取 x、y 軸的速度,這個值可能爲負數
        float xVelocity = velocityTracker.getXVelocity();
        float yVelocity = velocityTracker.getYVelocity();
        // 釋放跟蹤器
        velocityTracker.recycle();
        // 重置並回收內存
        velocityTracker.clear();
        return super.onTouchEvent(event);
    }

速度計算公式:速度 = (終點位置 - 起點位置)/ 時間段, 所以獲得的速度可能爲負值,即當從手指從右向左滑動的時候。

computeCurrentVelocity 方法的參數表示的是一個時間間隔,單位是毫秒。

GestureDetector

使用的機會並不多,如果只是監聽滑動的相關動作,建議自己在 onTouchEvent 中實現,如果要監聽雙擊這種行爲的話,再使用 GestureDetector

具體使用可以參考 看完這篇還不會 GestureDetector 手勢檢測,我跪搓衣板!

Scroller

具體可以參考之前的文章 【Android View事件(三)】Scroll類源碼分析與應用

這裏只強調一點:scrollTo 和 scrollBy 只能改變 View 中內容的位置,並不能改變 View 在佈局中的位置。

參考:

《Android 羣英傳》
《Android 開發藝術探究》
Android 中的座標系以及獲取座標的方法
Android 應用座標系統全面詳解

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章