欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android開發(fā)多手指觸控事件處理

 更新時間:2023年02月14日 14:13:13   作者:大胃粥  
這篇文章主要為大家介紹了Android開發(fā)多手指觸控事件處理教程示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

多點(diǎn)觸控,一直以來都是事件處理中比較晦澀的一個話題。其一是因?yàn)樗臋C(jī)制與我們常規(guī)思維有點(diǎn)不同,基二是因?yàn)槲覀冇玫谋容^少。那么作為一個有點(diǎn)追求的Android開發(fā)者,我們必須要掌握這些,這樣可以提高代碼的格調(diào)。

寫這篇文章還是有點(diǎn)難度的,我反反復(fù)復(fù)修改了好多次,真的是刪了又改,改了又刪,只為把多點(diǎn)觸控講得明明白白。最后我決定把本文分為三部分進(jìn)行講解

  • 講解多手指觸摸的一些關(guān)鍵性概念。雖然這部分概念非常抽象,并且也無法用源碼去解釋(源碼在底層),但是這部分概念是最關(guān)鍵的。如果你想掌握多點(diǎn)觸控,必須理解并記住這些概念。
  • 講解多手指觸摸事件在ViewGroup是如何分發(fā)處理的。因?yàn)橹挥欣斫饬诉@個,我們才能寫出正確的多手指觸摸事件的代碼。
  • 通過一個例子講解如何在滑動控件中支持多手指滑動。

好了,廢話不多說了,讓我們開始這次愉快的旅程吧。

觸摸事件

首先我們從MotionEvent.getAction()講起吧。很多地方把這個方法的返回值叫做觸摸事件的類型,其實(shí)這個叫法是錯誤的,它的返回值不僅包含事件的類型,還包含手指的索引值。

假如MotionEvent.getAction()返回一個值,用十六進(jìn)制表示為0X0100,這個值的高八位的值是01,用二進(jìn)制表示就是0000 0001,它表示手指的索引,而低八位的值是00,用二進(jìn)制表示就是0000 0000,它才表示事件的類型。

事件類型

那么我們怎么獲取這個事件的類型呢,我想大家應(yīng)該都想到了事件類型的掩碼,MotionEvent.getActionMask()就是通過事件類型掩碼獲取事件類型的。

那么,為什么大家一直說MotionEvent.getAction()返回的就是事件類型呢?因?yàn)檫@是一個巧合,對于單手指操作,MotionEvent.getAction()的返回值中,高八位的索引值是0,因此它正好與事件類型的值一樣。

對于支持多手指操作,MotionEvent.getAction()返回值的事件索引就不再一直是0了,它會隨著手指的增加而改變,因此MotionEvent.getActionMask()才是返回事件類型的正確操作。

那么我們來看下,多手指觸摸情況下所支持的事件類型

事件類型事件說明
ACTION_DOWN第一個手指按下
ACTION_POINTER_DOWN其它手指按下
ACTION_MOVE手指移動
ACTION_POINTER_UP不是最后一個手指抬起
ACTION_UP最后一個手指抬起

我們通過一個例子來解釋下這幾個事件的觸發(fā)時機(jī)。

  • 當(dāng)?shù)谝粋€手指按下的時候,此時觸發(fā)的事件類型是ACTION_DOWN。
  • 當(dāng)有第二個,甚至更多的手指按下的時候就會觸發(fā)ACTION_POINTER_DOWN事件。
  • 當(dāng)任意一個手指滑動的時候,就會觸發(fā)ACTION_MOVE事件。
  • 當(dāng)不是最后一個手指抬起時,會觸發(fā)ACTION_POINTER_UP事件。
  • 當(dāng)最后一個手指擇時,會觸發(fā)ACTION_UP事件。

手指索引

MotionEvent.getAction()返回值中還有個神秘的手指索引,它可以通過MotionEvent.getActionIndex()獲取。那么它有啥用呢?對于單手指,沒有任何叼用,但是對于多手指,那它的作用就大了,這可以獲取手指的觸摸事件的信息,例如MotionEvent.getX(int pointerIndex)獲取X坐標(biāo)值。

手指ID

剛才在事件類型部分,不知大家有沒有注意到,ACTION_MOVE是不區(qū)分手指的,那么我們怎么知道是哪個手指觸發(fā)了ACTION_MOVE的呢?你是不是第一時間想到了手指索引?請你放棄這個想法!

人可以通過眼睛觀察到手指的按下順序,但是硬件和軟件是無法做到的,而手指的索引在事件中可能會改變的。那么一個嚴(yán)峻的問題來了,如何跟蹤一個手指呢?用PointerId!至于原理是什么,我也不太清楚。

那么怎么獲取一個手指的PointerId呢?當(dāng)遇到ACTION_DOWNACTION_POINTER_DOWN的時候,通過如下代碼獲取

// 獲取手指的索引    
int pointerIndex = motionEvent.getActionIndex();
// 通過手指索引獲取手指ID
int pointerId = motionEvent.getPointerId(pointerIndex);

在前面的手指索引部分,我們知道通過索引可能獲取事件的信息,例如坐標(biāo)值,如下代碼

        // 獲取手指索引
        int pointerIndex = event.getActionIndex();
        // 獲取坐標(biāo)值
        float x = event.getX(pointerIndex);
        float y = event.getY(pointerIndex);

然而在ACTION_MOVE事件中,我們要獲取某個手指的坐標(biāo)值,怎么辦呢?首先我們要保存在ACTION_DOWNACTION_POINTER_DOWN中保存手指PointerId值,然后通過這個PointerId調(diào)用MotionEvent.findPointerIndex(int pointerId)獲取手指索引值,最后通過索引值獲取坐標(biāo)值,代碼如下

case MotionEvent.ACTION_MOVE:
    // 根據(jù)PointerId獲取某個手指的索引    
    int pointerIndex = event.findPointerIndex(mPrimaryPointerId);
    // 獲取坐標(biāo)值
    float x = event.getX(pointerIndex);
    float y = event.getY(pointerIndex);
    break;

多手指事件處理

對于多手指觸摸事件呢,其實(shí)比單手指只是多出了ACTION_POINTER_DOWNACTION_POINTER_UP兩個事件,那么這兩個事件在ViewGroup中是如何分發(fā)處理的呢?如果要用源碼來分析呢,這篇文章的篇幅就太長了,但是呢,恰巧這兩個事件與ACTION_MOVE的分發(fā)處理流程是一樣的。如果你還不懂ACTION_MOVE是如何分發(fā)處理的,可以參考我之前寫的ViewGroup事件分發(fā)和處理源碼分析。

支持多手指的滑動控件

掌握了前面的基礎(chǔ)知識后,我們現(xiàn)在就又到了喜聞樂見的實(shí)戰(zhàn)環(huán)節(jié),在這一部分,我們要使一個滑動控件支持多手指滑動。

在實(shí)現(xiàn)這個功能之前,我們要明確實(shí)現(xiàn)思路

  • 只有主手指能控制控件的滑動。
  • 如果有手指按下,就認(rèn)為這個手指是主手指。
  • 當(dāng)有手指抬起時,如果是主手指,那就必須重新找一個手指作為新的主手指。

首先我們需要一個可滑動的控件,這個控件取自手把手教你如何寫事件處理的代碼這篇文章的滑動控件,并且我需要大家對這篇文章的講的事件處理能理解清楚,因?yàn)橄旅鎸懙拇a,我不會去解釋這些基本知識。

我們前面說過,ACTION_POINTER_DOWNACTION_POINTER_UP的處理流程是和ACTION_MOVE一樣的,那么要不要截斷呢?那就要看當(dāng)遇到這兩個事件的時候我們要做什么。

根據(jù)實(shí)現(xiàn)思路中的第二條,如果有手指按下,就認(rèn)為是主手指,因此在處理ACTION_POINTER_DOWN時候只是簡單獲取手指的PointerId,然后保存為主手指即可,所以不需要去截斷。

根據(jù)實(shí)現(xiàn)思路的第三條,如果抬起的是主手指,那么就要重新找一個替代的手指作為主手指,所以也不需要去截斷。

那么,在onInterceptTouchEvent()onTouchEvent()的處理方式是一樣的,首先我們看下保存主手指的代碼如下

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_DOWN:
                onPrimaryPointerDown(ev);
                break;
            case MotionEvent.ACTION_POINTER_DOWN:
                onPrimaryPointerDown(ev);
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_DOWN:
                onPrimaryPointerDown(event);
                break;
        }
        return true;
    }
    /**
     * 當(dāng)有新手指按下的時候,就認(rèn)作是主手指,于是重新記錄按下點(diǎn)的坐標(biāo),以及更新最新的X坐標(biāo)。
     *
     * @param event 觸摸事件。
     */
    private void onPrimaryPointerDown(MotionEvent event) {
        // 獲取手指索引
        int pointerIndex = event.getActionIndex();
        // 通過手指索引獲取手指ID
        mPrimaryPointerId = event.getPointerId(pointerIndex);
        // 通過手指索引保存坐標(biāo)值
        mLastX = mStartX = event.getX(pointerIndex);
        mStartY = event.getY(pointerIndex);
    }    

然后,我們來看下當(dāng)有主手指抬起時,如何尋找替代的主手指

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_UP:
                onPrimaryPointerUp(ev);
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getActionMasked()) {
            case MotionEvent.ACTION_POINTER_UP:
                onPrimaryPointerUp(event);
                break;
        }
        return true;
    }
    /**
     * 當(dāng)主手指抬起時,尋找一個新的主手指,并且更新最新的X坐標(biāo)值為新主手指的X坐標(biāo)值。
     *
     * @param event
     */
    private void onPrimaryPointerUp(MotionEvent event) {
        // 獲取抬起手指的索引值
        int pointerIndex = event.getActionIndex();
        // 通過索引值,獲取抬起手指的ID
        int pointerId = event.getPointerId(pointerIndex);
        // 如果抬起手指的ID等于主手指的ID
        if (pointerId == mPrimaryPointerId) {
            // 尋找一個已經(jīng)存在的手指索引
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            // 通過新的手指索引獲取手指ID
            mPrimaryPointerId = event.getPointerId(newPointerIndex);
            // 通過新的手指索引獲取坐標(biāo)值
            mLastX = event.getX(newPointerIndex);
        }
    }    

把這些問題解決后,那么在處理滑動的代碼的時候,就要通過這個主手指ID來獲取坐標(biāo)值,然后根據(jù)這些坐標(biāo)值來決定滑動,我這里用部分代碼來演示下

    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getActionMasked()) {
            case MotionEvent.ACTION_MOVE:
                // 獲取主手指的坐標(biāo)值
                PointF primaryPointerPoint = getPrimaryPointerPoint(ev);
                // 根據(jù)坐標(biāo)值判斷是否需要滑動
                if (canScroll(primaryPointerPoint.x, primaryPointerPoint.y)) {
                    mBeingDragged = true;
                    getParent().requestDisallowInterceptTouchEvent(true);
                    // 執(zhí)行一次滑動
                    performDrag(primaryPointerPoint.x);
                    mLastX = primaryPointerPoint.x;
                    // 可以滑動就截斷事件
                    return true;
                }
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }
    /**
     * 獲取主手指在某個事件觸發(fā)時的坐標(biāo)。
     *
     * @param event 觸摸事件。
     * @return 如果成功,返回坐標(biāo)點(diǎn),否則返回null。
     */
    private PointF getPrimaryPointerPoint(MotionEvent event) {
        PointF pointF = null;
        if (mPrimaryPointerId != INVALID_POINTER_ID) {
            int pointerIndex = event.findPointerIndex(mPrimaryPointerId);
            if (pointerIndex != -1) {
                pointF = new PointF(event.getX(pointerIndex), event.getY(pointerIndex));
            }
        }
        return pointF;
    }    

總結(jié)

要掌握多手指滑動,必須先得掌握其關(guān)鍵的概念,有了這些概念我們就可以知道事件何時觸發(fā),怎么跟蹤一個手指。然后我們需要掌握多手指事件的處理流程,巧合的是,只要知道ACTION_MOVE的處理流程就明白了多手指事件的流程。最后我們要掌握為一個滑動控件添加多手指支持的實(shí)現(xiàn)思路。

有了這三步,基本上就可以實(shí)現(xiàn)一個支持多手指滑動的控件。不過請注意我的措辭,是基本上,是基本上,是基本上!

最后,我默默地留下一個github地址,供大家參考。

以上就是Android開發(fā)多手指觸控事件處理的詳細(xì)內(nèi)容,更多關(guān)于Android多手指觸控的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論