触摸[5] Scroller

来源:互联网 发布:手机动漫制作软件 编辑:程序博客网 时间:2024/05/14 04:42

【参考链接】

 

前面只实现了内容随手指滑动而滑动,如何在手指离开屏幕以后还能继续平滑滑动一段距离呢?

要实现平滑滑动,需要借助于Scroller类,它其实是通过起始位置、终止位置或初始速度,加上持续时间,计算出中间时刻的滑动位置,然后不断触发重绘来实现的。即通过不断的移动一个小的距离来实现整体的平滑滑动效果。

提供了两个方法

startScroll(),给它传入滑动的起始位置、终止位置和持续时间。



fling(),给他传入手初始速度和起始位置、终止位置的范围。(不过在我这里的实验结果,在靠近边界的时候,fling()效果不是很好)

 


具体使用方式如下

1.     ACTION_UP中根据速度分别调用startScroll()或者fling(),并调用invalidate()触发重绘。

2.     重写computeScroll()方法(),在其中通过Scroller获取到中间时刻的滑动位置,并调用scrollTo()/scrollBy()方法(会设置新的滑动位置并触发重绘),并再次调用invalidate()

3.     可以在ACTION_DOWN中进行处理,如果正在惯性滑动,则在手指按下时取消当前的惯性滑动。

 

public classMyFrameLayoutextendsFrameLayout {

   
//内容的最大值、最小值
   
private intminX=0;
    private int
maxX=0;

   
//滑动坐标的最大值、最小值
   
private intminScrollX;
    private int
maxScrollX;

    private int
lastX;

   
VelocityTrackermVelocityTracker;
    int
scaledMaximumFlingVelocity;
    int
scaledMinimumFlingVelocity;

    private
MyScrollermScroller;

    public
MyFrameLayout(Context context) {
       
super(context);
       
init();
   
}

   
publicMyFrameLayout(Context context,AttributeSet attrs) {
       
super(context,attrs);
       
init();
   
}

   
publicMyFrameLayout(Context context,AttributeSet attrs, intdefStyleAttr) {
       
super(context,attrs,defStyleAttr);
        
init();
   
}

   
public voidinit(){
       ViewConfiguration configuration = ViewConfiguration.get(getContext())
;
       
scaledMinimumFlingVelocity=configuration.getScaledMinimumFlingVelocity();
       
scaledMaximumFlingVelocity= configuration.getScaledMaximumFlingVelocity();
       
Log.e("shadowfaxghh","scaledMinimumFlingVelocity="+scaledMinimumFlingVelocity+" scaledMaximumFlingVelocity="+scaledMaximumFlingVelocity);

       
mScroller=newMyScroller(getContext());
   
}

   
@Override
   
protected voidonMeasure(intwidthMeasureSpec, intheightMeasureSpec) {
       
super.onMeasure(widthMeasureSpec,heightMeasureSpec);
   
}

   
@Override
   
protected voidonLayout(booleanchanged, intleft, inttop, intright, intbottom) {
       
super.onLayout(changed,left,top,right,bottom);

       
//计算出内容的最大值最小值
       
for(inti=0;i<getChildCount();i++){
           View childView = getChildAt(i)
;

           if
(minX>childView.getLeft())
               
minX=childView.getLeft();

           if
(maxX<childView.getRight())
               
maxX=childView.getRight();
       
}

       
//计算出滑动坐标的最大值、最小值//只有内容大小超出自身大小时,才能进行滑动
       
if(minX<0)
           
minScrollX=minX;
       else
           
minScrollX=0;

       if
(maxX>getWidth())
           
maxScrollX=maxX-getWidth();
       else
           
maxScrollX=0;
   
}

   
@Override
   
public booleanonTouchEvent(MotionEvent event) {
       
super.onTouchEvent(event);

       switch
(event.getAction()){
           
caseMotionEvent.ACTION_DOWN:
//               Log.e("shadowfaxghh", "ACTION_DOWN");
               
lastX=(int) event.getRawX();

               
obtaionVelocityTracker();
               
mVelocityTracker.addMovement(event);

               if
(mScroller!=null& !mScroller.isFinished()){
                   
mScroller.abortAnimation();
               
}
               
break;

           case
MotionEvent.ACTION_MOVE:
//               Log.e("shadowfaxghh", "ACTION_MOVE");
               
intnewX= (int) event.getRawX();
               int
deltaX=newX-lastX;

//               if (Math.abs(deltaX) >ViewConfiguration.get(getContext()).getScaledTouchSlop()) {
               
intnewScrollX = getScrollX() + (-deltaX);
               if
(newScrollX >maxScrollX)
                   newScrollX =
maxScrollX;
               if
(newScrollX <minScrollX)
                   newScrollX =
minScrollX;

               
scrollTo(newScrollX,getScrollY());

               
//更新坐标
               
lastX= newX;
//               }

               
obtaionVelocityTracker();
               
mVelocityTracker.addMovement(event);
               break;

           case
MotionEvent.ACTION_UP:
               Log.e(
"shadowfaxghh","ACTION_UP");
               
lastX=(int) event.getRawX();

               if
(mVelocityTracker!=null) {
                   
//计算速度
                   
mVelocityTracker.computeCurrentVelocity(1000,scaledMaximumFlingVelocity);

                   
//获取x方向的速度
                   
intxVelocity = (int)mVelocityTracker.getXVelocity();
                   
Log.e("shadowfaxghh","xVelocity="+ xVelocity);

                   
recycleVelocityTracker();

                   
//使用startScroll()进行滑动
                   
Log.e("shadowfaxghh","startScroll()");

                   
//假定手指离开以后以当前速度/4继续滑动1000ms
                   
intscrollDuration=1000;
                   int
scrollDistance=Math.abs(scrollDuration/1000* xVelocity/4);//computeCurrentVelocity(1000//速度是以1000ms为单位的

                   
intstartScrollX = getScrollX();
                   int
endScrollX =startScrollX;
                   if
(xVelocity>0){//手指向右滑动
                       
endScrollX=startScrollX-scrollDistance;
                   
}else if(xVelocity<0){//手指向左滑动
                       
endScrollX=startScrollX+scrollDistance;
                   
}

                   
if(endScrollX<minScrollX)
                       endScrollX=
minScrollX;
                   if
(endScrollX>maxScrollX)
                       endScrollX=
maxScrollX;

                   
//实际滑动时间ms//当然这是以匀速滑动来计算的,实际上Scroller内部并不是匀速滑动
                   
scrollDuration= (int) (Math.abs((float) (startScrollX-endScrollX)/(float)scrollDistance)*scrollDuration);
                   
mScroller.startScroll(startScrollX,getScrollY(),endScrollX-startScrollX,0,scrollDuration);
                   
invalidate();

//                   //使用Fling()进行滑动
//                   if(Math.abs(xVelocity)>scaledMinimumFlingVelocity) {//
快速滑动
//                       Log.e("shadowfaxghh", "fling()");
//
//                       //
注意xVelocity要取反,因为velocity正负值跟屏幕坐标系一致,但是scroll参数跟屏幕坐标系相反
//                       mScroller.fling(getScrollX(), getScrollY(), -xVelocity, 0, minScrollX,maxScrollX, 0, 0);
//                       invalidate();
//                   }
               
}
               
break;

           case
MotionEvent.ACTION_CANCEL:
               Log.e(
"shadowfaxghh","ACTION_CANCEL");
               
lastX=(int) event.getRawX();
               
recycleVelocityTracker();
               break;

           default
:
               
break;
       
}
       
return true;
   
}

   
@Override
   
public voidcomputeScroll() {
//       super.computeScroll();
       
if(mScroller!=null&&mScroller.computeScrollOffset()){
           
intcurrX =mScroller.getCurrX();
           int
currY =mScroller.getCurrY();
           
scrollTo(currX,currY);
           
invalidate();//某些情况下scrollTo()无法触发重绘,需要自己invalidate()触发
       
}
    }

   
private voidobtaionVelocityTracker(){
       
if(mVelocityTracker==null)
           
mVelocityTracker=VelocityTracker.obtain();
   
}

   
private voidrecycleVelocityTracker(){
       
if(mVelocityTracker!=null){
           
mVelocityTracker.clear();
           
mVelocityTracker.recycle();
           
mVelocityTracker=null;
       
}
    }

   
@Override
   
protected voidonScrollChanged(intl, intt, intoldl, intoldt) {
       
super.onScrollChanged(l,t,oldl,oldt);
   
}
}

 



可以看一下Scroller中相关方法的源码,如下代码基于android-2.3.3_r1

startScroll()

public voidstartScroll(intstartX, intstartY, intdx, intdy, intduration) {
   
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;

   
Log.e("shadowfaxghh","Scroller startScroll()mStartX="+mStartX+" mFinalX="+mFinalX+" mDuration="+mDuration);
}

 

fling()

public voidfling(intstartX, intstartY, intvelocityX, intvelocityY,
                 int
minX, intmaxX, intminY, intmaxY) {
   
mMode=FLING_MODE;
   
mFinished=false;

    float
velocity = (float)Math.hypot(velocityX,velocityY);

   
mVelocity= velocity;
   
//计算出滑动时间
   
mDuration= (int) (1000* velocity /mDeceleration);// Duration is in
    // milliseconds
   
mStartTime= AnimationUtils.currentAnimationTimeMillis();
   
mStartX= startX;
   
mStartY= startY;

   
mCoeffX= velocity ==0?1.0f: velocityX / velocity;
   
mCoeffY= velocity ==0?1.0f: velocityY / velocity;

   
//计算出滑动距离
   
inttotalDistance = (int) ((velocity * velocity) / (2*mDeceleration));

   
mMinX= minX;
   
mMaxX= maxX;
   
mMinY= minY;
   
mMaxY= maxY;

   //跟传入的参数进行比较
   
mFinalX= startX + Math.round(totalDistance*mCoeffX);
   
// Pin to mMinX <= mFinalX <=mMaxX
   
mFinalX= Math.min(mFinalX,mMaxX);
   
mFinalX= Math.max(mFinalX,mMinX);

   
mFinalY= startY + Math.round(totalDistance*mCoeffY);
   
// Pin to mMinY <= mFinalY <=mMaxY
   
mFinalY= Math.min(mFinalY,mMaxY);
   
mFinalY= Math.max(mFinalY,mMinY);
   
Log.e("shadowfaxghh","Scroller fling() mStartX="+mStartX+" mFinalX="+mFinalX+" mDuration="+mDuration);
}

 

computeScrollOffset()

public booleancomputeScrollOffset() {//返回滑动是否已经结束
   
if(mFinished) {
       
return false;
   
}

   
inttimePassed = (int)(AnimationUtils.currentAnimationTimeMillis()-mStartTime);
   
//从开始滑动到现在还没有结束
   
if(timePassed <mDuration) {
       
switch(mMode) {//分两种模式计算中间时刻的速度
           
caseSCROLL_MODE:
               
floatx = (float)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:
               
floattimePassedSeconds = timePassed /1000.0f;
               float
distance = (mVelocity* timePassedSeconds)
                       - (
mDeceleration* timePassedSeconds * timePassedSeconds/2.0f);

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

               
mCurrY=mStartY+ Math.round(distance *mCoeffY);
               
// 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;
}

 

startScroll() vs fling()

参考ScrollView的源码,startScroll()fling()并不是同时使用的

当手指离开屏幕的时候,如果速度大于minFlingVelocity,则进行fling()。否则停止(或回弹)。

 

原创粉丝点击