日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達(dá)者為師

網(wǎng)站首頁 編程語言 正文

View觸發(fā)機(jī)制API實現(xiàn)GestureDetector?OverScroller詳解_Android

作者:陳序猿_Android ? 更新時間: 2022-12-21 編程語言

前言

前一篇文章講了View的觸發(fā)反饋機(jī)制的原理,對于一個自定義View而言,手勢的處理都是重寫onTouchEvent函數(shù),或者通過setOnTouchEventListener方法捕捉手勢。但是手勢的處理,如滑動、觸摸、雙擊等檢測對應(yīng)的檢測也并不是那么簡單,自己一個個造輪子也過于麻煩,萬幸的是google早已經(jīng)給開發(fā)者提供了手勢捕捉的類- GestureDetector。通過這個類我們可以識別很多的手勢,主要是通過他的onTouchEvent(event)方法完成了不同手勢的識別。雖然他能識別手勢,但是不同的手勢要怎么處理,應(yīng)該是提供給程序員實現(xiàn)的。

GestureDetector

GestureDetector 中一共有三種主要的回調(diào)接口 ,OnGestureListenerOnDoubleTapListenerOnContextClickListener

這三個接口的方法如下。

public interface OnGestureListener {
        boolean onDown(MotionEvent e);
        void onShowPress(MotionEvent e);
        boolean onSingleTapUp(MotionEvent e);
        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
        void onLongPress(MotionEvent e);
        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
}
public interface OnDoubleTapListener {
    boolean onSingleTapConfirmed(MotionEvent e);
    boolean onDoubleTap(MotionEvent e);
    boolean onDoubleTapEvent(MotionEvent e);
}
public interface OnContextClickListener {
    boolean onContextClick(MotionEvent e);
}

GestureDetector 使用

GestureDector 負(fù)責(zé)監(jiān)聽手勢,而 OnDoubleTapListenerOnGestureListener 用于開發(fā)者自己去處理對應(yīng)手勢的反饋

package com.example.androidtemp.view;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.OverScroller;
import androidx.annotation.Nullable;
import androidx.core.view.ViewCompat;
public class TouchView extends View implements GestureDetector.OnGestureListener,GestureDetector.OnDoubleTapListener{
    private static final String TAG = "TouchView";
    GestureDetector gestureDetector = null;
    public TouchView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        gestureDetector = new GestureDetector(context,this);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
    }
    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        Log.i(TAG, "onSingleTapConfirmed: ");
        return false;
    }
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        Log.i(TAG, "onDoubleTap: ");
        return false;
    }
    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        Log.i(TAG, "onDoubleTapEvent: ");
        return false;
    }
    @Override
    public boolean onDown(MotionEvent e) {
        Log.d(TAG, "onDown: ");
        return true;
    }
    @Override
    public void onShowPress(MotionEvent e) {
        Log.i(TAG, "onShowPress: ");
    }
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        Log.i(TAG, "onSingleTapUp: ");
        return false;
    }
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        Log.i(TAG, "onScroll: ");
        return false;
    }
    @Override
    public void onLongPress(MotionEvent e) {
        Log.i(TAG, "onLongPress: ");
    }
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        Log.i(TAG, "onFling: ");
        return false;
    }
}

onDown方法

onDown 方法是在ACTION_DOWN 事件時被調(diào)用的,其的返回值決定了View是否消費該事件,一般我們肯定是需要消費該事件的,因此其值為true.

public boolean onDown() {
    return true;
}

onShowPress方法

@Override
public void onShowPress(MotionEvent e) {
    //進(jìn)行控件顏色的改變或其他一些動作
}

onShowPress 是用戶按下時的一種回調(diào),主要作用是用于給用戶一種按壓下的狀態(tài),可以在該回調(diào)中讓控件顏色改變或進(jìn)行一些動作。需要注意的是,onShowPress 方法不是立即回調(diào)的,在手指觸碰后,在100ms左右后才會回調(diào)。在這100ms內(nèi)如果手指抬起或滾動,該回調(diào)方法不會被觸發(fā)。在前一篇文章View事件分發(fā)機(jī)制 中提到過自定義View 默認(rèn)的super.onTouchEvent 實現(xiàn)中,按壓狀態(tài)也是有一個預(yù)按壓狀態(tài)的檢測,此處的onShowPress的回調(diào)機(jī)制也是同理。

onLongPress 方法

用于檢測長按事件的,即手指按下后不抬起,在一段時間后會觸發(fā)該事件。

@Override 
public void onLongPress(MotionEvent e) {
}

onLongPress 回調(diào)被觸發(fā)前 onShowPress 一定會被觸發(fā)。

需要注意的是 onLongPress一旦被觸發(fā),其他事件都不會被觸發(fā)了。

不過,onLongPress事件可以被禁止使用,通過如下代碼設(shè)置,即不會觸發(fā)長按事件

gestureDetector.setIsLongpressEnabled(false);

onSingleTapUp 方法

@Override
public boolean onSingleTapUp(MotionEvent e) {
    return false;
}

onSingleTapUP的返回值不是太重要,不過一般消費了就還是返回ture吧。

onSingleTapUp的意思顧名思義,即在 手指抬起時觸發(fā),不過他跟一般的onClick、以及onSingleTapConfirmed有一定區(qū)別

單擊事件觸發(fā):

GCS: onSingleTapUp
GCS: onClick
GCS: onSingleTapConfirmed
類型 觸發(fā)次數(shù) 摘要
onSingleTapUp 1 單擊抬起
onSingleTapConfirmed 1 單擊確認(rèn)
onClick 1 單擊事件

雙擊事件觸發(fā):

onSingleTapUp
onClick
onDoubleTap 
onClick
類型 觸發(fā)次數(shù) 摘要
onSingleTapUp 1 在雙擊的第一次抬起時觸發(fā)
onSingleTapConfirmed 0 雙擊發(fā)生時不會觸發(fā)。
onClick 2 在雙擊事件時觸發(fā)兩次。

可以看出來這三個事件還是有所不同的,根據(jù)自己實際需要進(jìn)行使用即可

onScroll

@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float 
        distanceY) {
    return true;
}

onScroll 方法是用于監(jiān)聽手指的滑動的,e1是第一次ACTION_DOWN的事件,e2是當(dāng)前滾動事件。distanceX、distanceY記錄了手指在x、y軸滑動的距離。

需要注意的時,該滑動距離記錄的是上次滑動回調(diào)與這次回調(diào)之間的距離差值。且還有一個有意思的注意事項,該差值是 lastEvent-curEvent 得到的,這與正常的邏輯行為不太一致,不過google就這樣干了,所以當(dāng)我們在計算滑動偏移量時需要對 distanceX、distancesY進(jìn)行一個 相減的操作而不是相加。

onFling

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                       float velocityY) {
    return true;
}

用戶手指在屏幕快速滑動后,在抬起時(ACTION_UP)觸發(fā)該事件。

Fling 中文直接翻譯過來就是一扔、拋、甩,最常見的場景就是在 ListView 或者 RecyclerView 上快速滑動時手指抬起后它還會滾動一段時間才會停止。onFling 就是檢測這種手勢的。

四個參數(shù)的介紹如下

參數(shù) 簡介
e1 手指按下時的 Event。
e2 手指抬起時的 Event。
velocityX 在 X 軸上的運動速度(像素/秒)。
velocityY 在 Y 軸上的運動速度(像素/秒)。

利用 velocityXvelocityY 參數(shù)可以實現(xiàn)一個具有一定初速度的滑動,之后該速度隨著滑動衰減,直到停止。

一般onFling 可以結(jié)合 OverScroller 實現(xiàn)一個均勻減速的滑動效果。

overScroller的用法在后方介紹。

onSingleTapConfirmed 和onDoubleTap

public boolean onSingleTapConfirmed(MotionEvent e) {
    return false;
}
public boolean onDoubleTap(MotionEvent e) {
    return false;
}
public boolean onDoubleTapEvent(MotionEvent e) {
    return false;
}

onSingleTapConfirmed用于監(jiān)聽單擊事件,而onDoubleTap用于監(jiān)聽雙擊事件。這兩個回調(diào)函數(shù)是互斥的。

onSingleTapConfigrmed的調(diào)用是延遲的,其在 手指按下300ms后觸發(fā)。

onSingleTapConfigrmed 適合于在 既檢測單擊事件也檢測雙擊時間時使用。

但是如果只是檢測單擊事件,onSingleTapUp更合適,onSingleTapConfigrmed會讓用戶明顯感覺到延遲。

需要注意的是 onDoubleTap 事件并不是第二次抬起時觸發(fā)的,而是第二次手觸摸到屏幕時即(第二次ACTION_DOWN)事件時就會觸發(fā)該事件,如果要保證在第二次抬起時才觸發(fā)該事件,就需要使用onDoubleTapEvent方法了

onDoubleTapEvent

@Override
public boolean onDoubleTapEvent(MotionEvent e) {
    Log.i(TAG, "onDoubleTapEvent: event:" + e.getActionMasked());
    switch (e.getActionMasked()) {
        case MotionEvent.ACTION_UP:
            Log.i(TAG, "onDoubleTapEvent: ACTION_UP");
            break;
    }
    return true;
}

雙擊時,onDoubleTapEvent 將會在onDoubleTap 后觸發(fā).

雙擊觸發(fā)日志:

TouchView: onDown: 
TouchView: onSingleTapUp: 
TouchView: onDoubleTap: 
TouchView: onDoubleTapEvent: event:0(ACTION_DOWN)
TouchView: onDown: 
TouchView: onDoubleTapEvent: event:2(ACTION_MOVE)
TouchView: onDoubleTapEvent: event:2(ACTION_MOVE)
TouchView: onDoubleTapEvent: event:1(ACTION_UP)
TouchView: onDoubleTapEvent: ACTION_UP

需要注意的是不論是雙擊還是單擊,只要按下長時間未動且未抬起,都會觸發(fā)onLongPress

第二次按下后常按再抬起日志

TouchView: onDown: 
TouchView: onSingleTapUp: 
TouchView: onDoubleTap: 
TouchView: onDoubleTapEvent: event:0
TouchView: onDown: 
TouchView: onDoubleTapEvent: event:2
TouchView: onDoubleTapEvent: event:2
TouchView: onDoubleTapEvent: event:2
TouchView: onShowPress: 
TouchView: onDoubleTapEvent: event:2
TouchView: onDoubleTapEvent: event:2
TouchView: onDoubleTapEvent: event:2
TouchView: onLongPress: 
ouchView: onDoubleTapEvent: event:1
TouchView: onDoubleTapEvent: ACTION_UP

OverScroller

onFling 方法中,曾說過 使用velocityX ,velocityY 兩個參數(shù)可以實現(xiàn) View的滑動效果.

public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
                       float velocityY) {
    return true;
}

示例

此處用一個可拖拉滑動的小圓球作為示例.

scroll效果圖

Fling效果圖

代碼如下

package com.example.androidtemp.view
import android.view.View
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.widget.OverScroller
import kotlin.math.max
import kotlin.math.min
private const val TAG = "SmallBallView"
class SmallBallView(context: Context?, attrs:AttributeSet?) :View(context,attrs) ,GestureDetector.OnGestureListener{
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
    private val BALL_DIAMETER_SIZE = 100 //球直徑長度
    private var originOffsetX = 0f
    private var originOffsetY = 0f
    private var offsetX = 0f
    private var offsetY = 0f
    private val gestureDetector = GestureDetector(this.context,this)
    private val scroller = OverScroller(this.context)
    override fun onTouchEvent(event: MotionEvent): Boolean {
        return gestureDetector.onTouchEvent(event);
    }
    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        originOffsetX = (w - BALL_DIAMETER_SIZE)/2f
        originOffsetY = (h - BALL_DIAMETER_SIZE)/2f
    }
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        // 偏移
        canvas.translate(offsetX,offsetY)
        //中間位置畫個圓
        canvas.drawArc(originOffsetX,originOffsetY,originOffsetX + BALL_DIAMETER_SIZE.toFloat(),originOffsetY + BALL_DIAMETER_SIZE.toFloat(),0f,360f,false,paint)
    }
    override fun onDown(e: MotionEvent?): Boolean = true
    override fun onShowPress(e: MotionEvent?) {}
    override fun onSingleTapUp(e: MotionEvent?): Boolean {
        return false
    }
    override fun onLongPress(e: MotionEvent?) {}
    override fun onScroll(
        e1: MotionEvent?,
        e2: MotionEvent?,
        distanceX: Float,
        distanceY: Float
    ): Boolean  {
        Log.i(TAG, "onScroll: ")
        offsetX -= distanceX
        offsetY -= distanceY
        //移動不能超過圓的一半
        offsetX = min(offsetX,width.toFloat()/2)
        offsetX = max(offsetX,-width.toFloat()/2)
        //移動不能超過圓的一半
        offsetY = min(offsetY,height.toFloat()/2)
        offsetY = max(offsetY,-height.toFloat()/2)
        invalidate()
        return true;
    }
    override fun onFling(
        e1: MotionEvent?,
        e2: MotionEvent?,
        velocityX: Float,
        velocityY: Float
    ): Boolean {
        //限制滑動不能超過一小圓的一半
        scroller.fling(offsetX.toInt(),offsetY.toInt(),velocityX.toInt(),velocityY.toInt(),-width/2,width/2,-height/2,height/2)
        postOnAnimation(scrollerRunnable)
        return true;
    }
    private val scrollerRunnable = object :Runnable {
        override fun run() {
            if (scroller.computeScrollOffset()) {
                offsetX = scroller.currX.toFloat()
                offsetY = scroller.currY.toFloat()
                invalidate()
                postOnAnimation(this)
            }
        }
    }
}

OverScroller方法介紹

  • fling 方法
public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY) {
    fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY, 0, 0);
}
public void fling(int startX, int startY, int velocityX, int velocityY,
            int minX, int maxX, int minY, int maxY, int overX, int overY) {
    //實現(xiàn)邏輯省略,有興趣的可以自己去看代碼
}
參數(shù) 簡介
startX、startY 開始滑動的X(Y)軸位置
velocityX、velocityY 在 X(Y) 軸上的運動速度(像素/秒)。
minX、maxX 滑動時X軸的兩個邊界值,滑動時一旦到達(dá)邊界值,則立刻停止
minY、maxY 滑動時Y軸的兩個邊界值,滑動時一旦到達(dá)邊界值,則立刻停止
overX、overY 在滑動時,可超出的滑動值,可超過邊界值,不過超過邊界值后,又會重新滑動回來
  • startScroll 方法

startScroll的滾動默認(rèn)以一種粘性液體的效果進(jìn)行滾動。

public void startScroll(int startX, int startY, int dx, int dy) {
    startScroll(startX, startY, dx, dy, DEFAULT_DURATION);//DEFAULT_DURATION 250 ms
}
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
    mMode = SCROLL_MODE;
    mScrollerX.startScroll(startX, dx, duration);
    mScrollerY.startScroll(startY, dy, duration);
}
參數(shù) 簡介
startX、startY 開始滑動的X(Y)軸位置
dx、dy 滾動到達(dá)的目標(biāo)位置
duration 滾動花費時間(單位ms),如果不指定默認(rèn)時250ms

原文鏈接:https://juejin.cn/post/7167720906540711966

欄目分類
最近更新