Android技能樹 — View事件體系小結

前言

最近年末了,打算把本身的Android知識都整理一下。git

Android技能書系列:github

Android基礎知識面試

Android技能樹 — 動畫小結算法

Android技能樹 — View小結數組

Android技能樹 — Activity小結bash

Android技能樹 — View事件體系小結數據結構

Android技能樹 — Android存儲路徑及IO操做小結app

Android技能樹 — 多進程相關小結ide

Android技能樹 — Drawable小結post

數據結構基礎知識

Android技能樹 — 數組,鏈表,散列表基礎小結

Android技能樹 — 樹基礎知識小結(一)

Android技能樹 — Fragment整體小結

算法基礎知識

Android技能樹 — 排序算法基礎小結

此次是講View的事件體系。特別是不一樣狀況下的事件分發,我會用很簡單的方式教會你們。

仍是老樣子,先上腦圖,而後具體一塊塊詳細說明。

腦圖連接:View事件體系

View事件體系

咱們經過具體案例來學習

View相關的基礎知識

好比咱們如今的需求是這樣的:界面上有一個按鈕,咱們的手指點擊這個按鈕後滑動,這個按鈕能夠跟着咱們的手指一塊兒滑動。(桌面的一些小的清理垃圾的懸浮窗的操做差很少,明白了吧)

具體實現能夠看我之前寫過的文章,十分簡單: 小Demo大知識-控制Button移動來學Android座標

咱們來分析,既然按鈕能夠跟着咱們手指滑動,咱們確定是不停告訴按鈕,當前你的位置是哪裏,既然涉及到一些基本知識點,好比View的位置參數等等。

View的位置參數

這裏我配上一張圖,更清楚的來講明這些獲取各自參數的值的說明:

(!!!!!這裏我多畫了getRawX和getRawY方法,View是沒有這二個方法的,請注意!!!!!)

(!!!!!這裏我多畫了getRawX和getRawY方法,View是沒有這二個方法的,請注意!!!!!)

(!!!!!這裏我多畫了getRawX和getRawY方法,View是沒有這二個方法的,請注意!!!!!)

看了這個圖,是否是立刻很清楚了。

注意點:

這裏要說明一個誤區,我面試一些初級水平安卓,我說ViewGroup裏面有個View,這個View的getLeft(),getTop(),getTop(),getBottom()是什麼,讓他畫給我看下,有些人會給下面這個答案:

錯誤的回答

這是錯誤的答案,並且根據正確的描述圖,咱們能夠經過getLeft(),getTop(),getTop(),getBottom()來獲取相應的View的寬高:

width = getRight() - getLeft();
height = getBottom() - getTop();
複製代碼

View操做相關知識

MotinoEvent

MotionEvent是什麼,單獨問你們可能有點懵逼,咱們來寫下咱們日常常常寫的設置觸摸的監聽方法:

view.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        return false;
    }
});
複製代碼

有沒有發現,裏面傳遞過來的參數就有MotionEvent

咱們能夠看到,MotionEvent是觸屏事件。當用戶觸摸屏幕時將產生觸屏事件,事件相關細節(發生觸摸的位置、時間、歷史記錄、手勢動做等)被封裝成MotionEvent對象。

具體的介紹真的不少,百度一搜一大把。要細講實在太多了。這裏很少介紹了。

特別提示!!!
不少人會把上面咱們提到過的view.getX/Y()和這裏的motionvent.getX/Y()弄混。這裏是有差異的。我再畫個圖來明確下兩者的區別。

因此區別是:

View的getX/Y()是指本身View的左上角相對於父View左上角的距離。MotionEvent的getX/Y()是指點擊處離本身View的左上角的距離。

ps:因此面試官問你getX/Y()的時候,必定問清楚是問的哪一個。!否則很容易回答錯誤。

TouchSlop

TouchSlop是系統所能識別出來的被認爲滑動的最小的距離。若是你手指在屏幕上滑動的時候小於這個值,系統就認爲你不是滑動。

VelocityTracker

滑動時候咱們可能還要監聽速度,好比說咱們的需求就是滑動的快和滑動慢,移動的最終距離不一樣等。這時候咱們必定要知道當前用戶在N時間段內的速度究竟是什麼。這時候咱們就須要速度(Velocity)追蹤者(Tracker)。

GestureDetector

咱們先來看看英文翻譯:

沒錯,既然你在屏幕上操做,你多是劃來劃去,多是單擊,多是雙擊。不少狀況。因此這個類就能夠幫咱們來監聽不一樣的操做。

ScaleGestureDetector

在GestureDetector前面添加了一個Scale。

那就明顯是比例的手勢監測,通俗來講就是放大縮小的手勢監測。

好比咱們的需求是在查看圖片的時候,能夠二個手指放大縮小圖片,那我恩就能夠用這個ScaleGestureDetector來監測。十分方便。

附上我之前寫過的文章:圖片操做系列 —(1)手勢縮放圖片功能

View的事件分發機制

事件傳遞三個階段及事件處理的類

其實這二個算是基礎知識。

接下去我會用一個真實的例子帶大家更好的理解事件分發,若是講的不合理,能夠提出來哦✧(≖ ◡ ≖✿)

舉個例子:

PS:(若是例子不適合,你們能夠評論反饋。由於若是例子不適合反而誤導了讀者,反而是個人問題了。)


比如大家公司是一個軟件外包公司,如今有個客戶手點了一下鼠標發給大家老闆一封郵件,說要開發這麼一個APP。大家老闆是否是會一層層的分發下去,老闆 ——> 主管 ——>開發人員。

額外提到點:

  1. 大家老闆收到了通知就是把這個任務分下去,不可能說第一反應先想一想說我要不要把這個任務攔下來本身作,不要叫手下的人去作了(否則還請大家幹嗎,請了大家還要每次想着要不要本身作)。因此他沒有攔截功能,默認確定不會去攔截,確定第一反應就是直接給手下。

  2. 主管都是有權利把任務攔下來的,不給手下的人去作,能夠本身處理,畢竟主管不僅是就分配下任務就夠了,這麼簡單我也想去作主管,可能由於手下都有任務在作,忙不過來的時候,主管會本身去作一些開發任務。

  3. 最底層的開發人員,沒有攔截功能,由於任務分到你這裏了。你還能再給誰呢,攔了也是你作,不攔你又沒有下級能夠給背鍋,仍是你作。

因此對比下知道是否是發現跟咱們的Activity,ViewGroup,View很像:

PS:當收到觸摸事件傳遞到某個層的時候,這個的dispactchEvent會被調用。(至關於上面接受到通知任務的時候會運行這個方法)

老闆 - Activity: 有收到通知的能力,因此會調用dispatchTouchEvent(),而後由於他能夠去通知主管,因此是

客戶通知老闆你有項目了。老闆的dispatchEvent()會被調用。
老闆.dispatchTouchEvent(){
    //老闆先通知主管去處理,
    若是主管給的回覆是:老闆你不用管接下去的事。咱們會處理的。
    if(主管.dispatchTouchEvent()){
        return true;//就直接結束了。
    }
    
    //手下的人說這個app開發不了,只能老闆出馬作事(跟客戶去溝通去)
    return 老闆.onTouchEvent();
}
複製代碼

因此只有dispatchEvent()onTouchEvent()方法。

主管 - ViewGroup

老闆通知了主管有個app要大家部門去開發。主管的dispatchTouchEvent()會被調用
主管.dispatchTouchEvent(){
    //主管把這個活攔下來準備本身來開發這個app
    if(主管.interceptTouchEvent()){
        return 主管.onTouchEvent();//主管也有作事能力
    }else{
        //主管不攔截,主管也能夠去通知開發人員,
        //若是開發人員回饋說主管你別管了。咱們這個app能作好
        if(開發人員.dispatchTouchEvent()){
            return true; //直接就結束了。
        }else{
            //若是手下的開發人員也反饋給主管說搞不定。
            //就只能主管本身出來作事了。
            return 主管.onTouchEvent();
        }
    }
}
複製代碼

因此有dispatchTouchEvent()、interceptTouchEvent()、onTouchEvent()

開發人員 - View

主管通知了開發人員有個app要開發。開發人員的dispatchTouchEvent()會被調用
開發人員.dispatchTouchEvent(){
    return 開發人員.onTouchEvent();
}
複製代碼

因此有dispatchTouchEvent()、onTouchEvent()

不一樣返回值致使不一樣的流程

我知道你們必定看到過相似下面的這種圖:

不少人都會死記硬背的去記下來,說return true/false/super等不一樣狀況下不一樣的調用流程。可是這樣其實很很差記住的。不少人會問我是怎麼記住的,我就是用僞代碼來幫忙記住,什麼事僞代碼,上面那種表達方式就是僞代碼。咱們如今正是來看具體的僞代碼。

Activity的真實代碼:

public boolean dispatchTouchEvent(MotionEvent ev){
    if(ev.getAction == MotionEvent.ACTION_DOWN){
        onUserInteraction();
    }
    
    /**
    調用window的superDispatchTouchEvent方法,
    而後再調用下面的ViewGroup(DecorView)的dispatchTouchEvent()方法。
    
    咱們就直接這麼想,這裏就Activity通知了ViewGroup的dispatchTouchEvent方法。
    
    1.若是這裏getWindow.superDispatchTouchEvent()返回了true,
    這時候就會執行return true語句。
    2.若是這裏getWindow.superDispatchTouchEvent()返回了false,
    這時候就會執行return onTouchEvent(ev);這句,
    
    因此只有當上面的if語句返回false,
    纔有機會調用Activity本身的onTouchEvent()方法。
    
    */
    if(getWindow.superDispatchTouchEvent()){
        return true;
    }
    
    return onTouchEvent(ev);
}
複製代碼

因此不少人會所你重寫Activity的dispatchTouchEvent()方法,返回true/false,都直接結束了事件。返回super才能正常分發,這個說法是不合理的。實際應該這麼描述:

默認重寫Activity的dispatchTouchEvent方法:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    /**
        實際上是調用了super.dispatchTouchEvent方法,
        纔會調用上面咱們貼出的Activity的dispatchTouchEvent方法,
        才能繼續把事件分發下去。
    */
    return super.dispatchTouchEvent(ev);
}
複製代碼

而你們通俗上說返回true/false就事件結束,是由於沒有調用了super.dispatchTouchEvent(ev);。因此就不會分發下去,也就事件結束了。

那假如我這麼寫呢:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    super.dispatchTouchEvent(ev);
    return true/false;
}
複製代碼

沒錯,事件也是同樣會分發下去。子View的方法也會被調用,而不會說直接結束了。

ViewGroup

由於上面咱們已經說過了getWindow.superDispatchTouchEvent()能夠直接理解爲是去調用了ViewGroup的dispatchTouchEvent();

ViewGroup的僞代碼:

public boolean dispatchTouchEvent(MotionEvent ev){
    /**
    若是ViewGroup作了攔截,
    則直接返回了ViewGroup的onTouchEvent()事件的結果。
    */    
    if(onInterceptTouchEvent(ev)){
        return onTouchEvent(ev);
    }else{
        /**
        若是ViewGroup不作攔截,則先分發給child,
        看他們的反應,他們都不接受,則必定會返回false,
        則只能ViewGroup本身去執行本身的onTouchEvent(ev);
        */
        if(child.dispatchTouchEvent(ev)){
            return true;
        }else{
            return onTouchEvent(ev);
        }
    }
}
複製代碼

View的僞代碼:

public boolean dispatchTouchEvent(MotionEvent ev){
    /**
    View 就返回本身的onTouchEvent()
    */
    return onTouchEvent();
}
複製代碼

可能不少人仍是說我看了這些代碼仍是不懂啊,我連起來給你看,你就理解了。

這樣,在不一樣狀況下,返回不一樣的false/true,執行順序就知道了。

額外補充:

《補充1》:

固然其實還有更復雜的狀況,咱們知道有ACTION_DOWN,ACTION_MOVE,ACTION_UP,ACTION_CANCEL等,好比咱們直接ViewGroup攔截Down事件,或者Down事件傳遞到了View後,咱們在MOVE處再攔截,都會執行不一樣的:

  1. DOWN事件被傳遞給ViewGroup的onInterceptTouchEvent()後,該方法返回true,表示攔截該事件,說明ViewGroup本身要處理該事件(事件再也不往下傳遞);調用自身的onTouchEvent()處理事件(DOWN事件將再也不往上傳遞給Activity的onTouchEvent());該事件列的其餘事件(Move、Up)將直接傳遞給ViewGroup 的onTouchEvent()。
  2. 若 ViewGroup 攔截了一個半路的事件(如MOVE),該事件將會被系統變成一個CANCEL事件 而且 傳遞給以前處理該事件的子View; 該事件不會再傳遞給ViewGroup 的onTouchEvent(); 只有再到來的事件纔會傳遞到ViewGroup的onTouchEvent()。

《補充2》:

咱們剛記不記得咱們的View的僞代碼是這樣的:

public boolean dispatchTouchEvent(MotionEvent ev){
    return onTouchEvent();
}
複製代碼

其實上面是作了簡化,其實除了onTouchEvent,還有onTouch事件和onClick事件,咱們繼續用僞代碼來講明規則:

public boolean dispatchTouchEvent(MotionEvent ev){
    
    if(設置了TouchListener){
        if(onTouch的返回值){
            return true;
        }else{
            return onTouchEvent();
        }
    }
    return onTouchEvent();
}

public boolean onTouchEvent(){
    if(設置了ClickListener){
        執行onClick;
    }
    
    .......
}


複製代碼

View的滑動

既然咱們學會了View的事件體系,不少人說那我學會了能怎麼樣,最明顯的就是咱們能夠用來解決不少滑動衝突事件。由於咱們能夠根據實際需求,選擇性的攔截,而後作本身的事件處理。

因此咱們具體來看View的滑動有關的知識:

View的滑動的基本知識我就不特地提出來了。你們能夠分別去搜索。

主要是第二塊View的滑動衝突。咱們就以最簡單的外部左右滑動,內部上下滑動爲例子。

外部左右滑動,內部上下滑動

好比咱們規定,滑動的角度是N度之內的時候就是說明咱們在內部滑動,角度是N度之外的時候是外部滑動。

  1. 外部攔截法
    默認父元素攔截,而後再適合的條件下,不讓父元素攔截。
public boolean onInterceptTouchEvent(MotionEvent event) {
    boolean intercepted=false;
    int x= (int) event.getX();
    int y= (int) event.getY();
    switch (event.getAction()){
        case MotionEvent.ACTION_DOWN:
            intercepted=false;
            //必須不能攔截,不然後續的ACTION_MOME和ACTION_UP事件都會攔截。
            break;
        case MotionEvent.ACTION_MOVE:
            if (父容器須要當前點擊事件){
                intercepted=true;
            }else {
                intercepted=false;
            }
            break;
        case MotionEvent.ACTION_UP:
            intercepted=false;
            break;
        default:
        break;
    }
    mLastXIntercept=x;
    mLastXIntercept=y;
    return intercepted;
}
複製代碼
  1. 內部攔截法:
    默認剛開始是不容許父元素作攔截,也就是子元素剛開始就調用requestDisallowInterceptTouchEvent(true);方法,禁止父元素作攔截,而後再適合的條件再讓父元素攔截。
子元素的dispatchTouchEvent()重寫:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    int x = (int) ev.getX();
    int y = (int) ev.getY();
    switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN: {
            getParent().requestDisallowInterceptTouchEvent(true);
            break;
        }
        case MotionEvent.ACTION_MOVE: {
            int deltaX = x - mLastXIntercept;
            int deltaY = y - mLastYIntercept;
            
            if (父容器須要當前點擊事件) {
                getParent().requestDisallowInterceptTouchEvent(false);
            }
            break;
        }
        case MotionEvent.ACTION_UP: {
            break;
        }
        default:{
            break;
        }
    }
    mLastXIntercept = x;
    mLastYIntercept = y;
    return super.dispatchTouchEvent(ev);
}
複製代碼

同時還要修改父容器的onInterceptTouchEvent()方法,不能作攔截,由於若是剛開始DOWN就攔截了,後面的MOVE,UP都沒機會到子元素的上面的代碼。

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    int action = ev.getAction();
    if (action == MotionEvent.ACTION_DOWN) {
        return false;
    } else {
        return true;
    }
}
複製代碼

結語

歡迎你們查看糾正,😉。。。。讓吐槽來的更猛烈些吧。

相關文章
相關標籤/搜索