scrollTo与scrollBy用法以及TouchSlop与VelocityTracker解析
?下一篇:?scroller類的用法完全解析以及帶源碼分析
最近在工作中使用到了scrollTo與scrollBy,因此在這準備對它們的用法以及TouchSlop與VelocityTracker做一下整理與總結,以便加深理解,以下是本篇的主要內容,至于Scroller類的解析以及用法,我會放在下一篇文件記錄。
直接開始吧。
1.view相關位置參數
1.1 Android坐標系
在物理學中,描述一個物體的運動通常都需要選定一個參考系,因此所謂的view的相關位置參數也就是這里要說明的Android設備屏幕的平面直角坐標參考系,在android中,將屏幕最左上角的頂點作為android坐標系的原點,從這個點向右是X軸正方向,從這個點向下是Y軸正方向,如下圖所示:
當然android系統也提供getLocationOnScreen(int location[])函數來獲取view在坐標系中點位置,這里要注意的是獲取到到坐標是當前view的左上角在坐標系中的坐標。代碼如下獲取view在屏幕中的坐標:
int[] location = new int[2];view.getLocationOnScreen(location);int x = location[0];int y = location[1]; 還有就是在觸摸事件中我們也可以通過getRawX()和getRawY()來獲取當前觸摸坐標。以上所介紹的便是android坐標系,在android中還有一種比較特殊的坐標系,這種坐標系叫做視圖坐標系,視圖坐標系描述的是子視圖在父視圖中的位置關系。視圖坐標與android坐標系一樣 將父視圖最左上角的頂點作為視圖坐標系的原點,從這個點向右是X軸正方向,從這個點向下是Y軸正方向,如下圖所示:
由圖我們可以發現原點不再是android坐標系中的屏幕最左上角,而是以父視圖左上角為坐標系原點。其實我們在觸控事件中,通過getX()與getY()所獲取到到坐標就是視圖坐標系中的坐標。
1.2 ?View中各類獲取間距的參數值
View自身提供的獲取坐標的方法:
getTop():獲取view自身的頂邊到其父布局頂邊的距離。
getLeft():獲取view自身的左邊到其父布局左邊的距離。
getRight():獲取view自身的右邊到其父布局左邊的距離。
getBottom():獲取view自身的底邊到其父布局頂邊的距離。
MotionEvent提供的方法
getX() :獲取點擊事件距離控件左邊的距離,即視圖坐標。
getY() :獲取點擊事件距離控件頂邊的距離,即視圖坐標。
getRawX():獲取點擊事件距離整個屏幕左邊的距離,即絕對坐標。
getRawY():獲取點擊事件距離整個屏幕頂邊的距離,即絕對坐標。
不理解?怎么辦?請看下圖:
現在夠清晰了吧
2.touchSlop與VelocityTracker
2.1 ?touchSlop
當時我在另一個類看到這么一個調用函數
ViewConfiguration.get(context).getScaledTouchSlop(); 而這個函數獲取到得值就是 touchSlop,touchSlop到底是啥啊?根據方法注釋理解這個touchSlop是一個滑動距離值的常量,也就是說當我們手觸摸在屏幕上滑動時,如果滑動距離沒有超過touchSlop值的話 ,android系統本身是不會認為我們在屏幕上做了手勢滑動,因此只有當我們在屏幕上的滑動距離超過touchSlop值時,android系統本身才會認為我們做了滑動操作并去響應觸摸事件,不過要注意的是不同的設備,touchSlop的值可能是不同的,一切以上述的函數獲取為準。說到這里,這個touchSlop值到底有什么意義?當我們在處理滑動事件時,其實可以利用這個值來過濾掉一些沒必要的動作,比如當兩次滑動距離小于這個值時,我們就可以認為滑動沒發生,從而更好的優化用戶體驗。 可是我還有疑問:ViewConfiguration這個貨是干啥的?某位大神說過:源碼之前,了無秘密!上源碼!/*** Contains methods to standard constants used in the UI for timeouts, sizes, and distances.*/ public class ViewConfiguration {
根據文檔注釋這個類是用來存放UI相關的標準常量,如超時時間,大小,距離.......由此也可知touchSlop只不過是其中的一個常量罷了。我大概掃了幾眼這個類,定義的常量還不少,其實我是想說,我不打算分析這個類.....有興趣的自己再去掃掃.......
2.2 VelocityTracker
/*** Helper for tracking the velocity of touch events, for implementing* flinging and other such gestures.** Use {@link #obtain} to retrieve a new instance of the class when you are going* to begin tracking. Put the motion events you receive into it with* {@link #addMovement(MotionEvent)}. When you want to determine the velocity call* {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}* and {@link #getYVelocity(int)} to retrieve the velocity for each pointer id.*/ public final class VelocityTracker {這段話的大概意思是:輔助跟蹤觸摸事件的速率,如快速滑動或者其他手勢操作。當我們準備開始跟蹤滑動速率時可以使用obtain()方法來獲取一個VelocityTracker的實例,然后在onTouchEvent回調函數中,使用addMovement(MotionEvent)函數將當前的移動事件傳遞給VelocityTracker對象。當我們決定計算當前觸摸點的速率時可以調用computeCurrentVelocity(int units)函數來計算當前的速度,使用getXVelocity() 、getYVelocity()函數來獲得當前X軸和Y軸的速度。
簡單的說就是VelocityTracker是個速度跟蹤類,用于跟蹤手指滑動的速度,包括x軸方向和y軸方向的速度。如何使用?
如果我們決定跟蹤View中onTouchEvent()方法中的手指滑動速度,可以在手指按下時(ACTION_DOWN)使用以下代碼:
VelocityTracker velocityTracker=VelocityTracker.obtain();velocityTracker.addMovement(event);velocityTracker.addMovement(event)的作用可以理解為收集速率追蹤點數據
velocityTracker.computeCurrentVelocity(1000);float velocityX = velocityTracker.getXVelocity();float velocityY = velocityTracker.getXVelocity(); computeCurrentVelocity (int units),基于當前我們所收集到的點計算當前的速率,當我們確定要獲得速率信息的時候,在調用該方法,因為使用它需要消耗很大的性能。
參數:units ?我們想要指定的得到的速度單位,如果值為1,代表1毫秒運動了多少像素。如果值為1000,代表1秒內運動了多少像素。如果值為100,代表100毫秒內運動了多少像素。(這個參數設置真有點.......什么鬼嘛!)這個方法還有一個重載函數?computeCurrentVelocity (int units, float maxVelocity), 跟上面一樣也就是多了一個參數。
參數:maxVelocity ?該方法所能得到的最大速度,這個速度必須和你指定的units使用同樣的單位,而且必須是整數.也就是,你指定一個速度的最大值,如果計算超過這個最大值,就使用這個最大值,否則,使用計算的的結果,
這個最大速度可以通過ViewConfiguration.get(context).getScaledMaximumFlingVelocity()方式獲取。
getXVelocity()和getYVelocity() ,這兩個很簡單,獲得橫向和豎向的速率。前提是一定要先調用computeCurrentVelocity (int units)函數計算當前速度!
最后,東西我們不要了,當然要回收啦!這時當然要調用clear()來重置并調用recycler()方法來回收內存啦,代碼如下,請收下!
/*** 使用完VelocityTracker,必須釋放資源*/private void releaseVelocityTracker() {if (mVelocityTracker != null) {mVelocityTracker.clear();mVelocityTracker.recycle();mVelocityTracker = null;}} 以上就是 VelocityTracker類的簡單介紹與使用方法。下面給出代碼實例package com.zejian.scrollerapp; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.view.VelocityTracker; import android.view.ViewConfiguration; import android.widget.TextView; import com.zejian.scrollerapp.utils.LogUtils; /*** Created by zejian* Time 16/1/20 上午11:45* Email shinezejian@163.com* Description: VelocityTracker速度測試類*/ public class VelocityTrackerActicity extends Activity {private static final String TAG = "VelocityTrackerActicity";private TextView tv;private VelocityTracker mVelocityTracker;private int mPointerId;private int mMaxVelocity;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_velocity_tracker);tv= (TextView) findViewById(R.id.tv);tv.setText("VelocityTrackerActicity");mMaxVelocity = ViewConfiguration.get(this).getScaledMaximumFlingVelocity();}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {LogUtils.e("onTouchEvent start!!");Log.i(TAG, "ACTION_DOWN");if(null == mVelocityTracker) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);final VelocityTracker verTracker = mVelocityTracker;switch (event.getAction()) {case MotionEvent.ACTION_DOWN://獲取第一個觸點的id, 此時可能有多個觸點,獲取其中一個mPointerId = event.getPointerId(0);break;case MotionEvent.ACTION_MOVE://計算瞬時速度verTracker.computeCurrentVelocity(1000, mMaxVelocity);float velocityX = verTracker.getXVelocity(mPointerId);float velocityY = verTracker.getYVelocity(mPointerId);LogUtils.e("velocityX-->" + velocityX);LogUtils.e("velocityY-->"+velocityY);break;case MotionEvent.ACTION_UP:case MotionEvent.ACTION_CANCEL:releaseVelocityTracker();//釋放資源break;default:break;}return super.onTouchEvent(event);}/*** 使用完VelocityTracker,必須釋放資源*/private void releaseVelocityTracker() {if (mVelocityTracker != null) {mVelocityTracker.clear();mVelocityTracker.recycle();mVelocityTracker = null;}} }
3.scrollTo()與scrollBy()
在android中為了實現view的滑動,android系統為此提供了scrollTo()和scrollBy()兩個方法。老樣子唄,看看源碼再說話。
/*** Set the scrolled position of your view. This will cause a call to* {@link #onScrollChanged(int, int, int, int)} and the view will be* invalidated.* @param x the x position to scroll to* @param y the y position to scroll to*/public void scrollTo(int x, int y) {if (mScrollX != x || mScrollY != y) {int oldX = mScrollX;int oldY = mScrollY;mScrollX = x;mScrollY = y;invalidateParentCaches();onScrollChanged(mScrollX, mScrollY, oldX, oldY);if (!awakenScrollBars()) {postInvalidateOnAnimation();}}}/*** Move the scrolled position of your view. This will cause a call to* {@link #onScrollChanged(int, int, int, int)} and the view will be* invalidated.* @param x the amount of pixels to scroll by horizontally* @param y the amount of pixels to scroll by vertically*/public void scrollBy(int x, int y) {scrollTo(mScrollX + x, mScrollY + y);}根據scrollTo(int x, int y)的文檔說明,當我們調用scrollTo(int x, int y)方法時,該方法內部將會去調用onScrollChanged(int, int, int, int),這也將直接導致view重繪,也就實現了所謂的view滑動效果。而scrollBy(int x, int y),這哥們可真的夠懶了,一點內涵都沒有,居然直接跑去調用scrollTo(int x, int y),也罷。不過這么一看兩者的區別也很明顯,scrollTo(int x, int y)是基于所給參數的絕對滑動,而scrollBy(int x, int y)是基于所給參數的相對滑動,簡單一句,scrollTo()是一步到位,而scrollBy()是逐步累加,這點很容易明白,從源碼就能看出來了。但是scrollTo或者scrollBy到底是改變了啥啊?(柯南:真相只有一個那就是看源碼唄)從scrollTo(int x, int y)源碼中我們可以看到mScrollX 和mScrollY 的值將會被改變,這兩值又是啥?
/*** The offset, in pixels, by which the content of this view is scrolled* horizontally.* {@hide}*/@ViewDebug.ExportedProperty(category = "scrolling")protected int mScrollX;/*** The offset, in pixels, by which the content of this view is scrolled* vertically.* {@hide}*/@ViewDebug.ExportedProperty(category = "scrolling")protected int mScrollY; 等等,我們好像發現了什么?沒錯, mScrollX 和 mScrollY都是偏移量,而且都是指當前view的內容相對view本身左上角起始坐標的偏移量。不理解?又怪我咯,看下圖:由此可知我們調用scrollTo(int x, int y)和scrollBy(int x, int y)時傳遞的參數并非是坐標而是偏移量。比如我們view是TextView,那么我們調用scrollTo或者scrollBy方法時,移動的其實就是TextView的內容,但如果我們的view是LinearLayout(ViewGroup),那么移動其實就是該布局內的子view了。到此也算明朗了。android的view內容也提供了獲取這兩個偏移量大小的方法,如下:
/*** Return the scrolled left position of this view. This is the left edge of* the displayed part of your view. You do not need to draw any pixels* farther left, since those are outside of the frame of your view on* screen.** @return The left edge of the displayed part of your view, in pixels.*/public final int getScrollX() {return mScrollX;}/*** Return the scrolled top position of this view. This is the top edge of* the displayed part of your view. You do not need to draw any pixels above* it, since those are outside of the frame of your view on screen.** @return The top edge of the displayed part of your view, in pixels.*/public final int getScrollY() {return mScrollY;}
來個小結:
1.scrollTo()的移動是一步到位,而scrollBy()逐步累加的
2.scrollTo()和scrollBy()傳遞的參數是偏移量而非坐標
3.scrollTo()和scrollBy()移動的都只是View的內容,View的背景本身是不移動的。
到了這里原本以為差不多了,但在實際操作中發現,傳入的參數完全跟想執行的操作相反!!!
比如我們對于一個TextView調用scrollTo(0,20),那么該TextView中的content(比如顯示的文字:波多),會怎么移動呢?按我們前面掌握的知識,應該是向下移動20個單位。但結果恰恰相反,向上移動了20個單位。如果我們想向下移動20個單位應該這樣調用scrollTo(0,-20),這是為啥呢?
要解決這個問題,那么就得看看mScrollX和mScrollY是在哪里被使用的?
根據前面分析,調用scrollTo()方法將會導致view重繪,也就是會去調用public void invalidate(int l, int t, int r, int b)方法,我們先看看這個方法得源碼:
/*** Mark the area defined by the rect (l,t,r,b) as needing to be drawn. The* coordinates of the dirty rect are relative to the view. If the view is* visible, {@link #onDraw(android.graphics.Canvas)} will be called at some* point in the future.* <p>* This must be called from a UI thread. To call from a non-UI thread, call* {@link #postInvalidate()}.** @param l the left position of the dirty region* @param t the top position of the dirty region* @param r the right position of the dirty region* @param b the bottom position of the dirty region*/public void invalidate(int l, int t, int r, int b) {final int scrollX = mScrollX;final int scrollY = mScrollY;invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);}invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false)通過這個方法,我們大概也能猜到點貓膩了。如果我們傳遞的scrollerX值是正數的話,(l - scrollX) 計算后則左邊距會變小,所以內容會往左移動(也就是x軸的負方向)。如果我們傳遞的scrollerX值是負數的話,(l - scrollX) 計算后則左邊距會變大,因此內容會往右移動(也就是x軸的正方向),同理,y軸也一樣。
所以有如下結論:如果我們想往x軸和y軸正方向移動時,mScrollY和mScrollX必須為負值,相反如果我們想往x軸和y軸負方向移動時,mScrollY和mScrollX就必須為正值啦。
腦海突然冒出lol送塔的畫面,然后內心又閃過cf爆敵方頭的刺激感,其實我想說來個實戰案例吧。
在這里我們自定義一個可以自由滑動的view,通過scrollBy()實現,自定義view代碼如下:
package view; import android.content.Context; import android.graphics.Color; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; public class ScrollByDragView extends View{private int lastX;private int lastY;public ScrollByDragView(Context context) {super(context);ininView();}public ScrollByDragView(Context context, AttributeSet attrs) {super(context, attrs);ininView();}public ScrollByDragView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);ininView();}private void ininView() {setBackgroundColor(Color.BLUE);}@Overridepublic boolean onTouchEvent(MotionEvent event) {int x = (int) event.getX();int y = (int) event.getY();switch (event.getAction()) {case MotionEvent.ACTION_DOWN:lastX = (int) event.getX();lastY = (int) event.getY();break;case MotionEvent.ACTION_MOVE:int offsetX = x - lastX;int offsetY = y - lastY;((View) getParent()).scrollBy(-offsetX, -offsetY);break;}return true;} }代碼相對簡單,但這里有點要注意的是,((View) getParent()).scrollBy(-offsetX, -offsetY),這個必須調用父類的scrollBy(),因為我們要滑動的我們自己的自定義view。
布局文件drag_view_scrollby.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><view.ScrollByDragViewandroid:layout_width="100dp"android:layout_height="100dp" /> </LinearLayout> activity代碼package com.zejian.androidmotionevent; import android.app.Activity; import android.os.Bundle; public class DragViewScrollBy extends Activity{@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.drag_view_scrollby);}} 效果圖:
好了,到此本篇結束,下篇將分析一下Scroller類。
總結
以上是生活随笔為你收集整理的scrollTo与scrollBy用法以及TouchSlop与VelocityTracker解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 个性闹钟屏保带滚动字幕
- 下一篇: 服务器无法重命名 请确定磁盘未满或未被写