《Android 開發(fā)藝術(shù)探索》筆記3--View事件體系

View的事件體系.png

View的事件體系

View的基礎(chǔ)知識(shí)

View的位置參數(shù)

一個(gè)View的位置主要由四個(gè)頂點(diǎn)構(gòu)成, 或者可以就是兩個(gè)點(diǎn)就可以確定. 分別為左上點(diǎn),右下角每個(gè)點(diǎn)都對(duì)應(yīng)x,y兩個(gè)屬性. 因?yàn)槟J(rèn)都是矩形, 所以兩個(gè)點(diǎn)就可以確定.

一個(gè)View的大小可以利用四個(gè)屬性可知. 分別對(duì)應(yīng)getLeft(),getRight(),getTop(),getBottom系統(tǒng)提供的函數(shù).

  • 一個(gè)控件的寬: getRight() - getLeft()
  • 一個(gè)控件的高: getTop() - getBottom()

在Android3.0中, View增加了幾個(gè)屬性:x , y, translationX, translationY

  • x , y: 表示View的左上角坐標(biāo)點(diǎn)(最終坐標(biāo)點(diǎn)).
  • translationX, translationY: 表示View的左上點(diǎn)相對(duì)于父容器的偏移量(默認(rèn)是0).

而這些參數(shù)的換算關(guān)系為:

x = left + translationX;

y = top + translationY;

MotionEvent和TouchSlop

MotionEvent是指手指在接觸屏幕之后產(chǎn)生的一系列事件

最常見事件類型是ACTION_DOWN,ACTION_MOVE,ACTION_UP

一次事件可以有不同的持續(xù)時(shí)間, 和不同的事件類型. 例如

  • 按下抬起 : DOWN –> UP
  • 按下移動(dòng)抬起 : DOWN -> MOVE -> MOVE -> … ->UP
  • ….

而在移動(dòng)時(shí)可以根據(jù)MotionEvent提供的參數(shù)獲對(duì)應(yīng)的xy取值.

*getX/getY: 返回相對(duì)于當(dāng)前View左上角的x,y坐標(biāo).
getRawX/getRawY: 返回的是針對(duì)整個(gè)屏幕的左上角的x,y坐標(biāo).

TouchSlop是系統(tǒng)可以識(shí)別的最小滑動(dòng)距離單位

只有手指兩次滑動(dòng)大于這個(gè)TouchSlop,系統(tǒng)才認(rèn)為是滑動(dòng).

ViewConfiguration.get(getContent).getSealedTouchSlop()可以獲得這個(gè)系統(tǒng)值默認(rèn)8dp.

用途: 在自定義的時(shí)候, 可以參考系統(tǒng)的默認(rèn)值, 來作為實(shí)際的滑動(dòng)定義.

VelocityTracker GestureDetector

VelocityTracker 速度追蹤

用于追蹤手指在滑動(dòng)過程中的速度,包括水平和數(shù)值方向的速度

使用方式: 在View的OnTouchEvent方法中:

//獲得速度追蹤對(duì)象

VelocityTracker velocity = VelocityTracker.obtain();

velocity.addMovement(event);

//計(jì)算速度 并獲取計(jì)算值

velocity.computeCurrentVelocity(1000); //設(shè)定一個(gè)時(shí)間間隔值

float xVelocity = velocity.getXVelocity();

float yVelocity = velocity.getYVelocity();

必須要先計(jì)算并設(shè)定計(jì)算速度的時(shí)間單元值,才可以獲得速率.

公式: 速度 = (終點(diǎn)位置 - 起點(diǎn)位置) / 時(shí)間間隔值

可以看到, 計(jì)算的速度是根據(jù)我們自己添加的時(shí)間間隔值計(jì)算的. 并且速度可以為負(fù)值,如果向左滑動(dòng).

當(dāng)不需要的時(shí)候, 調(diào)用clear()重置并回收內(nèi)存.


velocity.clear();

velocity.recycle();

GestureDetector 手勢(shì)檢測(cè)

用于輔助檢測(cè)用戶的單擊, 滑動(dòng), 長(zhǎng)按, 雙擊等行為.

使用如下

創(chuàng)建GestureDetector對(duì)象并實(shí)現(xiàn)OnGestureDetector接口.

GestureDetector mGestureDetector = new GestureDetector(this);

// 解決長(zhǎng)按屏幕后無法拖動(dòng)的現(xiàn)象

mGestureDetector.setIsLongpressEnabled(false);

然后接管目標(biāo)View的onTouchEvent()方法. 在onTouchEvent()方法中


boolean consume = mGestureDetector.onTouchEvent(event);

return consume;

然后根據(jù)需求可以選擇性的實(shí)現(xiàn)OnGestureListenerOnDoubleTapListener接口

接口的方法說明:

方法名 描述 所屬接口
onDown 按下 OnGestureListener
onShowPress 按下 但是未松開或者拖動(dòng).強(qiáng)調(diào)狀態(tài) OnGestureListener
onSingleTapUp 抬起 表示單擊行為, 雙擊中也會(huì)觸發(fā) OnGestureListener
onScroll 按下并拖動(dòng) 拖動(dòng)行為 OnGestureListener
onLongPress 長(zhǎng)按 OnGestureListener
onFling 按下屏幕病快速滑動(dòng)后松開 OnGestureListener
onDoubleTap 雙擊,兩次連續(xù)單擊組成, 與onSingleTapConfirmed無法共存 OnDoubleTapListener
onSingleTapConfirmed 嚴(yán)格意義上的單擊 雙擊中的單擊無法觸發(fā) OnDoubleTapListener
onDoubleTapEvent 表示發(fā)生了行為 OnDoubleTapListener

實(shí)際開發(fā)中:根據(jù)喜好來使用. 即使不使用GestureDetector輔助手勢(shì)檢測(cè)類,一樣可以實(shí)現(xiàn).

建議: 如果要監(jiān)聽雙擊這種行為就是用此類.

Scroller 彈性滑動(dòng)對(duì)象

用于實(shí)現(xiàn)View的彈性滑動(dòng).

在開發(fā)中, 當(dāng)需要把View從一個(gè)點(diǎn)移動(dòng)到另一個(gè)點(diǎn)的時(shí)候. 如果使用scrollTo/scrollBy進(jìn)行滑動(dòng)時(shí), 都是瞬間完成. 沒有過度動(dòng)畫, 給用戶感覺很生硬. 使用Scroller 可以實(shí)現(xiàn)有過渡的滑動(dòng).Scroller本身無法讓View彈性滑動(dòng), 需要和View的computerScroll進(jìn)行配合使用.

下面會(huì)說到

View的滑動(dòng)

實(shí)現(xiàn)滑動(dòng)的方式有三種:

  • 通過View本身的scrollTo/scrollBy方法實(shí)現(xiàn)滑動(dòng)
  • 通過動(dòng)畫給View施加平移效果來實(shí)現(xiàn)動(dòng)畫
  • 通過改變View的LayoutParams使View重新布局實(shí)現(xiàn)滑動(dòng)

scrollTo/scrollBy

首先要明確一點(diǎn): 這兩個(gè)方法只能改變View的內(nèi)容位置,而不能改變View本身在布局中的位置

而且方法中都是以像素值來進(jìn)行移動(dòng)的.

  • scrollTo: 針對(duì)當(dāng)前View的絕對(duì)位置進(jìn)行移動(dòng).
  • scrollBy: 根據(jù)當(dāng)前View的內(nèi)容值進(jìn)行相對(duì)位置移動(dòng).

看一下scrollBy的源碼調(diào)用


public void scrollBy(int x, int y) {

   scrollTo(mScrollX + x, mScrollY + y);

}

其實(shí)本質(zhì)上scrollBy調(diào)用了scrollTo方法

mScrollX/mScrollY是什么? 這個(gè)就是當(dāng)前View的內(nèi)容 與這個(gè)View實(shí)際布局位置(原始位置)的差值.
而當(dāng)前View內(nèi)容這個(gè)東西就是讓用戶看到的效果發(fā)生改變. 但是如果這個(gè)View可以被點(diǎn)擊. 那么能觸發(fā)點(diǎn)擊的位置是View的實(shí)際所在布局位置. 而不是View的內(nèi)容顯示的位置.

使用動(dòng)畫

使用動(dòng)畫來對(duì)View進(jìn)行移動(dòng),主要就是操作View的translationX/translationY屬性

可以使用普通動(dòng)畫和屬性動(dòng)畫.

普通動(dòng)畫是對(duì)View進(jìn)行影像的移動(dòng). 可以通過設(shè)置fillAfter=true,來讓影像在動(dòng)畫結(jié)束時(shí)候保留最終結(jié)果.而不是還原到起始位置.

而屬性動(dòng)畫會(huì)對(duì)真實(shí)位置也進(jìn)行改變.

ObjectAnimator.ofFloat(tagerView,"translationX",0,100).setDuration(100).start()

改變布局參數(shù)"

這個(gè)比較簡(jiǎn)單, 獲得View的LayoutParams參數(shù).進(jìn)行修改,改好之后再賦值回去.

MarginLayoutParams params = (MarginLayoutParams)mTextView.getLayoutParams();

params.width += 100;

params.leftMargin += 100;

mTextView.requestLayout();

//或者mTextview.setLayoutParams(params);

關(guān)于這三種方式的簡(jiǎn)單總結(jié)

  • scrollTo/scrollBy: 操作簡(jiǎn)單, 適合對(duì)View內(nèi)容的滑動(dòng)
  • 動(dòng)畫: 操作簡(jiǎn)單,主要適用于沒有交互的View和實(shí)現(xiàn)復(fù)雜的動(dòng)畫效果.
  • 修改布局參數(shù): 操作稍微復(fù)雜,適用于有交互的View.

彈性滑動(dòng)

使用Scroller

一個(gè)簡(jiǎn)單的使用方法如下:


Scroller mScroller = new Scroller(mContent);

// 封裝一個(gè)方法, 接收要移動(dòng)到的目標(biāo)點(diǎn) x和y

private void smoothScrollTo(int destX, int destY){

    int scrollX = getScrollX();

    int deltaX = destX - scrollX;

    // 1000ms內(nèi)逐漸滑向destX

    mScroller.startScroll(scrollX, 0, deltaX, 0, 1000);

    invalidate();

}

//復(fù)寫View的computeScroll方法

public void computeScroll(){

    if(mScroller.computeScrollOffset()){

        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());

        postInvalidate()

    }

}

源碼中Scroller類中startScroll()方法,其實(shí)沒有實(shí)際操作什么,只是保存了調(diào)用方法時(shí),傳遞的幾個(gè)參數(shù). 如: 開始結(jié)束點(diǎn),時(shí)間等. 那動(dòng)畫究竟是怎么實(shí)現(xiàn)的? 復(fù)寫的computeScroll()又有什么用?

流程順序這樣的: 當(dāng)調(diào)用了startScroll()系統(tǒng)只是保存了一些信息, 但是下面調(diào)用invalidate(). 這個(gè)方法都知道是會(huì)導(dǎo)致View的重繪, 在View的draw()方法中又會(huì)去調(diào)用computeScroll()方法,本身computeScroll()是一個(gè)空實(shí)現(xiàn),但是這里進(jìn)行了復(fù)寫. 而這個(gè)方法我們復(fù)寫的時(shí)候調(diào)用了scrollTo()方法! ok這樣View就會(huì)真正的移動(dòng)了! 但是還有一點(diǎn)這次滾動(dòng)只是整個(gè)滾動(dòng)事件的一個(gè)小部分,后續(xù)的怎么觸發(fā)的? 就是下面又調(diào)用了postInvalidate(), 又會(huì)重新繪制重新調(diào)用computeScroll()這個(gè)復(fù)寫過的空實(shí)現(xiàn)方法.

Scroller類中的computeScrollOffset()可以直接返回這個(gè)滾動(dòng)的動(dòng)作是否全部完成. 源碼實(shí)現(xiàn)思路就是根據(jù)時(shí)間的流逝的百分比來計(jì)算出當(dāng)前ScrollX和ScrollY的值.


// 核心代碼  x就是時(shí)間流逝的百分比

mCurrX = mStartX + Math.round(x * mDeltaX);

mCurrY = mStartY + Math.round(x * mDeltaY);

小結(jié)Scroller的工作原理:

Scroller本身不可以實(shí)現(xiàn)滑動(dòng), 需要和View的ComputeScroll()配合使用來完成彈性滑動(dòng). 通過不斷的在computeScroll()調(diào)用View的重繪方法. 每次繪制時(shí)候的當(dāng)前時(shí)間與開始時(shí)間的時(shí)間差與設(shè)定的執(zhí)行動(dòng)畫時(shí)間的百分比,算出每一次需要scroll到的坐標(biāo)點(diǎn), 然后通過調(diào)用scrollTo()來實(shí)現(xiàn)每一次的小滾動(dòng)效果. 通過一連串的滾動(dòng)達(dá)到了平滑的效果. 這就是Scroller工作機(jī)制. 完全實(shí)現(xiàn)了解耦操作. 這個(gè)過程沒有任何一處對(duì)View進(jìn)行引用,甚至連內(nèi)部計(jì)時(shí)器都沒有.

補(bǔ)充:
1、top、left、right、bottom的值,是在view的onLayout的時(shí)候確定。
2、scrollView只在繪制的時(shí)候onLayout,在滾動(dòng)的時(shí)候不會(huì)再次出發(fā)onLayout,所以對(duì)于子View的top、left、right、bottom是沒有影響的、
3、listView自身有回收機(jī)制,所以在滾動(dòng)的時(shí)候需要時(shí)刻去檢測(cè)item是否已經(jīng)滾動(dòng)出了屏幕,這樣就需要重新測(cè)量子view的位置,所以就直接影響了item的top、left、right、bottom。

通過動(dòng)畫

可以直接使用ObjectAnimator.ofFloat(tagerView,"translationX",0,100).setDuration(100).start()

也可以利用動(dòng)畫的特性, 實(shí)現(xiàn)與Scroller原理近似的方法.

final int startX = 100;

final int endX = 200;

ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1).setDuration(1000);

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

  @Override

  public void onAnimationUpdate(ValueAnimator animation) {

      int offset = (int) animation.getAnimatedFraction();

      mTextview.scrollTo(startX+offset, 0);

  }

});

讓系統(tǒng)算出每個(gè)時(shí)間片我們需要移動(dòng)的距離, 并回調(diào)給我們.讓我們自己實(shí)現(xiàn). 如果是一組動(dòng)畫在相同的時(shí)間執(zhí)行的絕對(duì)值相同我們就可以在onAnimationUpdate()一起進(jìn)行調(diào)用.

使用延時(shí)策略

核心思想就是通過發(fā)送一些列延時(shí)消息從而達(dá)到一種漸進(jìn)的效果.

可以使用: Handler, View的postDelayed()方法, 或者線程的sleep()

參看文章

《Android 開發(fā)藝術(shù)探索》書集
《Android 開發(fā)藝術(shù)探索》 03-View的事件體系

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容