代码解说Android Scroller、VelocityTracker

在编写自己定义滑动控件时经常会用到Android触摸机制和Scroller及VelocityTracker。Android
Touch系统简单介绍(二):实例具体解释onInterceptTouchEvent与onTouchEvent的调用过程
对Android触摸机制须要用到的函数进行了具体的解释。本文主要介绍两个重要的类:Scroller及VelocityTracker。利用上述知识,最后给出了一个自己定义滑动控件的demo,该demo类似于ImageGallery

ImageGallery通常是用GridView来实现的,能够左右滑动。本样例实现的控件直接继承一个ViewGroup,对其回调函数如
onTouchEvent、onInterceptTouchEvent、computeScroll等进行重载。弄懂该代码。对Android touch的认识将会更深一层。

VelocityTracker:用于对触摸点的速度跟踪,方便获取触摸点的速度。

使用方法:一般在onTouchEvent事件中被调用。先在down事件中获取一个VecolityTracker对象,然后在move或up事件中获取速度,调用流程可例如以下列所看到的:

VelocityTracker vTracker = null;
@Override
public boolean onTouchEvent(MotionEvent event){
	int action = event.getAction();
	switch(action){
	case MotionEvent.ACTION_DOWN:
		if(vTracker == null){
			vTracker = VelocityTracker.obtain();
		}else{
			vTracker.clear();
		}
		vTracker.addMovement(event);
		break;
	case MotionEvent.ACTION_MOVE:
		vTracker.addMovement(event);
		//设置单位,1000 表示每秒多少像素(pix/second),1代表每微秒多少像素(pix/millisecond)。
		vTracker.computeCurrentVelocity(1000);
		//从左向右划返回正数,从右向左划返回负数
		System.out.println("the x velocity is "+vTracker.getXVelocity());
		//从上往下划返回正数,从下往上划返回负数
		System.out.println("the y velocity is "+vTracker.getYVelocity());
		break;
	case MotionEvent.ACTION_UP:
	case MotionEvent.ACTION_CANCEL:
		vTracker.recycle();
		break;
	}
	return true;
}  

Scroller:用于跟踪控件滑动的轨迹。此类不会移动控件,须要你在View的一个回调函数computerScroll()中使用Scroller对象还获取滑动的数据来控制某个View。

  /**
 * Called by a parent to request that a child update its values for mScrollX
 * and mScrollY if necessary. This will typically be done if the child is
 * animating a scroll using a {@link android.widget.Scroller Scroller}
 * object.
 */
public void computeScroll()
{
}

parentView在绘制式。会调用dispatchDraw(Canvas canvas),该函数会调用ViewGroup中的每一个子view的boolean draw(Canvas canvas, ViewGroup parent, long drawingTime),用户绘制View,此函数在绘制View的过程中会调用computeScroll()

以下给出一段代码:

@Override
public void computeScroll() {
	// TODO Auto-generated method stub
	Log.e(TAG, "computeScroll");
	if (mScroller.computeScrollOffset()) { //or !mScroller.isFinished()
		Log.e(TAG, mScroller.getCurrX() + "======" + mScroller.getCurrY());
		scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
		Log.e(TAG, "### getleft is " + getLeft() + " ### getRight is " + getRight());
		postInvalidate();
	}
	else
		Log.i(TAG, "have done the scoller -----");
}

这段代码在滑动view之前先调用mScroller.computeScrollOffset()来推断滑动动画是否已结束。computerScrollerOffset()的源码例如以下:

/**
 * Call this when you want to know the new location.  If it returns true,
 * the animation is not yet finished.
 */
public boolean computeScrollOffset() {
	if (mFinished) {
		return false;
	}

	//滑动已经持续的时间
	int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
	//若在规定时间还未用完,则继续设置新的滑动位置mCurrX和mCurry
	if (timePassed < mDuration) {
		switch (mMode) {
		case SCROLL_MODE:
			float x = timePassed * mDurationReciprocal;

			if (mInterpolator == null)
				x = viscousFluid(x);
			else
				x = mInterpolator.getInterpolation(x);

			mCurrX = mStartX + Math.round(x * mDeltaX);
			mCurrY = mStartY + Math.round(x * mDeltaY);
			break;
		case FLING_MODE:
			final float t = (float) timePassed / mDuration;
			final int index = (int) (NB_SAMPLES * t);
			float distanceCoef = 1.f;
			float velocityCoef = 0.f;
			if (index < NB_SAMPLES) {
				final float t_inf = (float) index / NB_SAMPLES;
				final float t_sup = (float) (index + 1) / NB_SAMPLES;
				final float d_inf = SPLINE_POSITION[index];
				final float d_sup = SPLINE_POSITION[index + 1];
				velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
				distanceCoef = d_inf + (t - t_inf) * velocityCoef;
			}

			mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;

			mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
			// Pin to mMinX <= mCurrX <= mMaxX
			mCurrX = Math.min(mCurrX, mMaxX);
			mCurrX = Math.max(mCurrX, mMinX);

			mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
			// Pin to mMinY <= mCurrY <= mMaxY
			mCurrY = Math.min(mCurrY, mMaxY);
			mCurrY = Math.max(mCurrY, mMinY);

			if (mCurrX == mFinalX && mCurrY == mFinalY) {
				mFinished = true;
			}

			break;
		}
	}
	else {
		mCurrX = mFinalX;
		mCurrY = mFinalY;
		mFinished = true;
	}
	return true;
}

ViewGroup.computeScroll()被调用时机:

当我们运行ontouch或invalidate()或postInvalidate()都会导致这种方法的运行。

我们在开发控件时。常会有这种需求:当单机某个button时。某个图片会在规定的时间内滑出窗体。而不是一下子进入窗体。实现这个功能能够使用Scroller来实现。

以下给出一段代码,该代码控制下一个界面在3秒时间内缓慢进入的效果。

public void moveToRightSide(){
	if (curScreen <= 0) {
		return;
	}
	curScreen-- ;
	Log.i(TAG, "----moveToRightSide---- curScreen " + curScreen);
	mScroller.startScroll((curScreen + 1) * getWidth(), 0, -getWidth(), 0, 3000);
	scrollTo(curScreen * getWidth(), 0);
	invalidate();
}

上述代码用到了一个函数:void android.widget.Scroller.startScroll(int startX, int startY, int dx, int dy, int duration)

当startScroll运行过程中即在duration时间内,computeScrollOffset  方法会一直返回true,但当动画运行完毕后会返回返加false.

这个函数的源代码例如以下所看到的,主要用于设置滑动參数

/**
 * Start scrolling by providing a starting point, the distance to travel,
 * and the duration of the scroll.
 *
 * @param startX Starting horizontal scroll offset in pixels. Positive
 *        numbers will scroll the content to the left.
 * @param startY Starting vertical scroll offset in pixels. Positive numbers
 *        will scroll the content up.
 * @param dx Horizontal distance to travel. Positive numbers will scroll the
 *        content to the left.
 * @param dy Vertical distance to travel. Positive numbers will scroll the
 *        content up.
 * @param duration Duration of the scroll in milliseconds.
 */
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
	mMode = SCROLL_MODE;
	mFinished = false;
	mDuration = duration;
	mStartTime = AnimationUtils.currentAnimationTimeMillis();
	mStartX = startX;
	mStartY = startY;
	mFinalX = startX + dx;
	mFinalY = startY + dy;
	mDeltaX = dx;
	mDeltaY = dy;
	mDurationReciprocal = 1.0f / (float) mDuration;
}

invalidate()会使得视图重绘,导致parent调用了dispatchDraw(Canvas canvas),然后递归调用child View的draw()函数。该函数又会调用我们定义的computeScroll(), 而这个函数又会调用mScroller.computeScrollOffset()推断动画是否结束。若没结束则继续重绘直到直到startScroll中设置的时间耗尽mScroller.computeScrollOffset()返回false才停下来。

附上完整的实例代码:

自己定义Android可滑动控件源代码

执行效果图例如以下,滑动屏幕会显示不同的图片。

时间: 2024-11-06 05:21:29

代码解说Android Scroller、VelocityTracker的相关文章

代码讲解Android Scroller、VelocityTracker

在编写自定义滑动控件时常常会用到Android触摸机制和Scroller及VelocityTracker.Android Touch系统简介(二):实例详解onInterceptTouchEvent与onTouchEvent的调用过程对Android触摸机制需要用到的函数进行了详细的解释,本文主要介绍两个重要的类:Scroller及VelocityTracker.利用上述知识,最后给出了一个自定义滑动控件的demo,该demo类似于ImageGallery.ImageGallery一般是用Gri

Android Scroller简单用法

Android里Scroller类是为了实现View平滑滚动的一个Helper类.通常在自定义的View时使用,在View中定义一个私有成员mScroller = new Scroller(context).设置mScroller滚动的位置时,并不会导致View的滚动,通常是用mScroller记录/计算View滚动的位置,再重写View的computeScroll(),完成实际的滚动.       相关API介绍如下 Java代码   mScroller.getCurrX() //获取mScr

PM撸代码之Android【武侠篇:封装、继承、多态】

80 PM撸代码之Android[武侠篇:封装.继承.多态] 这是Android系列的第六篇文章,在之前的一篇文章中,已经了解了面向对象的基础概念,这一篇将会通过武侠江湖的类比,讲解面向对象的更多内容,感谢小伙伴们一直以来的支持. 武林门派的三个特征 1 独门秘籍(封装) 2 传承的门派(继承) 3 看情况使功夫和换个姿势说明问题(多态) [封装] 1 门派独门秘籍(封装) 前一篇已经说到,当达摩上了嵩山以后,江湖就正式进入门派时代.每个门派区别于其他门派,肯定是因为这个门派拥有独门武功秘籍.举

PM撸代码之Android【绝顶高手排行榜】

82 PM撸代码之Android[绝顶高手排行榜] 这是Android系列的第七篇文章了,也是关于Java语言的最后一篇文章了.这一篇后,我们将正式开始Android的学习.不过这一篇大家还是要好好地学一下,把Java的基础打好,那么学Android将会事半功倍的. 不久前,公众号的文章已经突破20万字了,我还是挺高兴的,一共发了80多篇文章了.规划中的文章还有很多,希望我能一直写下去吧,没有你们的支持,我是不可能有动力码那么多字的.接下来也不会一直发布Android的教程,而是会穿插地发表文章

《第一行代码:Android》学习笔记:Activity生命周期

<第一行代码:Android> 郭霖(著) Activity所在的栈为后进先出(Last In First Out)结构. Activity状态 运行状态(S1): 该Activity处于与User交互的状态,即是位于栈顶的Activity. 系统一般不考虑回收该处内存. 暂停状态(S2): Activity不再处于栈顶(Another activity comes in front of the activity),但仍然是可见的. 系统只有在内存极低时才考虑回收内存. 停止状态(S3):

《第一行代码:Android》学习笔记:Activity &amp; Intent

<第一行代码:Android> 郭霖(著) 2.2 Activity的基本用法 隐藏标题栏 在AndroidManifest.xml中配置,作为全局配置,在所有Activity范围内生效 android:theme="@android:style/Theme.NoTitleBar" 在代码中配置,必须在setContentView()前调用该方法,只在当前Activity生效 requestWindowFeature(Window.FEATURE_NO_TITLE); 在s

一行代码解决Android M新的运行时权限问题

Android M运行时权限是个啥东西 啥是运行时权限呢?Android M对权限管理系统进行了改版,之前我们的App需要权限,只需在manifest中申明即可,用户安装后,一切申明的权限都可来去自如的使用.但是Android M把权限管理做了加强处理,在manifest申明了,在使用到相关功能时,还需重新授权方可使用.当然,不是所有权限都需重新授权,所以就把这些需要重新授权方可使用的权限称之为运行时权限. 运行时权限的影响 运行时权限的好处可以让用户使用时更有主动权,不会让app随便乱来.但是

如何使用Java代码获取Android移动终端Mac地址

快下班了,现在总结一下如何使用Java代码获取Android移动终端Mac地址: 通过设备开通WiFi连接获取Mac地址是最可取的,代码如下: /** * 设备开通WiFi连接,通过wifiManager获取Mac地址 * * @author 高焕杰 */ public static String getMacFromWifi(Context context){ ConnectivityManager connectivityManager = (ConnectivityManager) con

第四章:重构代码[学习Android Studio汉化教程]

第四章 Refactoring Code The solutions you develop in Android Studio will not always follow a straight path from design to finish. To be an effective Android programmer, you need to be flexible and refactor your code as you develop, debug, and test. In t