在编写自定义滑动控件时常常会用到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()都会导致这个方法的执行。
我们在开发控件时,常会有这样的需求:当单机某个按钮时,某个图片会在规定的时间内滑出窗口,而不是一下子进入窗口。实现这个功能可以使用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可滑动控件源码
运行效果图如下,滑动屏幕会显示不同的图片。