一直对Scroller这个类不太熟悉,之前老是在网上找着看,但是过不了多长时间后就忘记了,今天来整理一下
http://api.apkbus.com/reference/android/widget/Scroller.html
为了理解方便,拿SlideView来做说明,关于SlideView的demo网上有很多,这里为了讲解主要贴出SlideView:
public class SlideView extends LinearLayout { private Context mContext; private LinearLayout mViewContent; private LinearLayout mHolder; private TextView tv_delete; // 弹性滑动对象,实现View平滑滚动的一个帮助类 private Scroller mScroller; // 滑动回调接口,用来向上层通知滑动事件 private OnSlideListener mOnSlideListener; private int mHolderWidth = 100; private int mLastX = 0; private int mLastY = 0; private static final int TAN = 2; public SlideView(Context context) { super(context); initView(); } public SlideView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } private void initView(){ mContext = getContext(); mScroller = new Scroller(mContext); setOrientation(LinearLayout.HORIZONTAL); setGravity(Gravity.CENTER_VERTICAL); //将R.layout.slide_view 添加到this, View view=View.inflate(this,R.layout.*,null);是生成一个新的View View.inflate(mContext, R.layout.slide_view, this); mViewContent = (LinearLayout)findViewById(R.id.view_content); mHolder = (LinearLayout)findViewById(R.id.holder); tv_delete = (TextView)findViewById(R.id.delete); } public void setButtonText(CharSequence text){ tv_delete.setText(text); } public void setContentView(View view){ mViewContent.addView(view); } public void onRequireTouchEvent(MotionEvent event){ // 获取点击的坐标 int x = (int)event.getX(); int y = (int)event.getY(); //当前view的左上角相对于母视图的左上角的X轴偏移量。 int scrollX = getScrollX(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: if(!mScroller.isFinished()){ mScroller.abortAnimation(); } if(mOnSlideListener != null){ mOnSlideListener.onSlide(this, OnSlideListener.SLIDE_STATUS_START_SCROLL); } break; case MotionEvent.ACTION_MOVE: //增量 int deltaX = x - mLastX; int deltaY = y - mLastY; if(Math.abs(deltaX) < Math.abs(deltaY)*TAN){ // 滑动不满足条件 不做横向滑动 break; } //这个SlideView相对于上次偏移的距离减去手指这次move的相对于上次move的增量(相对偏移量?) //举个例子:SlideView从scrollX=0开始滚动,如果现在手指向左滑动,deltaX为负值,假设deltaX=-10,scrollX-deltaX就是10 //scrollTo()中的x>0,view是向左偏移10,手指再次move的时候,getScrollX()就=10,如果deltaX现在=-5,scrollX-deltaX就是15, // 从scrolTo可以看出onScrollChanged(mScrollX, mScrollY, oldX, oldY);view的滚动是基于上一个点进行偏移的,也就是说是从10,偏移了15,所以说getscrollx也是基于开始位置的偏移 //scrollTo是相对于上次偏移的距离 int newScrollX = scrollX - deltaX; if(deltaX != 0){ if(newScrollX < 0){ newScrollX = 0; }else if(newScrollX > mHolderWidth){ newScrollX = mHolderWidth; } //将SlideView移动到(newScrollX, 0)这个坐标点位置,并触发computeScroll() this.scrollTo(newScrollX, 0); } break; case MotionEvent.ACTION_UP: int newScrollx = 0; //如果SlideView的偏移量大于默认要滑动距离的3/4,newScrollx=mHolderWidth,否则 newScrollx = 0; if(scrollX - mHolderWidth*0.75 > 0){ newScrollx = mHolderWidth; } this.smoothScrollTo(newScrollx, 0); // 通知上层滑动事件 if(mOnSlideListener != null){ mOnSlideListener.onSlide(this, newScrollx == 0 ? OnSlideListener.SLIDE_STATUS_OFF : OnSlideListener.SLIDE_STATUS_ON); } break; default: break; } mLastX = x; mLastY = y; } /** * 调用此方法滚动到目标位置 * @param fx 目标x坐标 * @param fy 目标Y坐标 */ private void smoothScrollTo(int fx, int fy){ int scrollX = getScrollX(); int dx = fx - scrollX; int scrollY = getScrollY(); int dy = fy - scrollY; //手指up之后从现在的位置偏移到scrollX+dx位置,dx是增量,触发computeScroll mScroller.startScroll(scrollX, scrollY, dx, dy, Math.abs((dx)*3)); invalidate(); } /** * 由mScroller记录/计算好View滚动的位置后,最后由View的computeScroll(),完成实际的滚动 */ @Override public void computeScroll() { //先判断mScroller滚动是否完成 if(mScroller.computeScrollOffset()){ //这里调用View的scrollTo()完成实际的滚动 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //必须调用该方法,否则不一定能看到滚动效果 postInvalidate(); } super.computeScroll(); } /** * 设置滑动回调 * @param onSlideListener */ public void setOnSlideListener(OnSlideListener onSlideListener){ this.mOnSlideListener = onSlideListener; } public interface OnSlideListener { public static final int SLIDE_STATUS_OFF = 0; public static final int SLIDE_STATUS_START_SCROLL = 1; public static final int SLIDE_STATUS_ON = 2; public void onSlide(View view, int status); } }
具体讲解一下:
1.ACTION_DOWN:mScroller停止动画,这个没什么说的
2.ACTION_MOVE:举个例子:SlideView已经偏移了scrollX,你下次move触发的时候只需要偏移scrollX - deltaX就可以了,scrollTo是相对于上次偏移,getScrollX却是向对于开始时的位置
3.ACTION_UP:在UP之前可以看到mScroller没有做任何关于滚动的的动作,up的时候调用了
private void smoothScrollTo(int fx, int fy){ int scrollX = getScrollX(); int dx = fx - scrollX; int scrollY = getScrollY(); int dy = fy - scrollY; //手指up之后从现在的位置偏移到scrollX+dx位置,dx是增量,触发computeScroll mScroller.startScroll(scrollX, scrollY, dx, dy, Math.abs((dx)*3)); invalidate(); }触发了computeScroll()
<span style="font-size:14px;"> @Override public void computeScroll() { //先判断mScroller滚动是否完成 if(mScroller.computeScrollOffset()){ //这里调用View的scrollTo()完成实际的滚动 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); //必须调用该方法,否则不一定能看到滚动效果 postInvalidate(); } super.computeScroll(); }</span>computeScroll里面做了什么??先mScroller.computeScrollOffset()判断mScroller滚动是否完成,computeScrollOffset做了什么呢?
public boolean computeScrollOffset() { if (mFinished) { return false; } int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime); 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; }
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; }
可以看出int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);mStartTime = AnimationUtils.currentAnimationTimeMillis();获取过了多长时间,
然后if (timePassed < mDuration) ,看一下有没有到达设置的时间,
然后一通计算mCurrX = mStartX + Math.round(x * mDeltaX);(计算是为了在mDuration时间内完成滚动dx)得到mCurrX
,然后回到computeScroll(), scrollTo(mScroller.getCurrX(), mScroller.getCurrY());在次偏移mCurrX ,
然后回调computeScroll,直到滚动结束
注意:scrollTo(),如果第一次你在x轴向左上偏移了10,下次再向左偏移10的话,他是基于你当前的位置再偏移10,所以说getscrollx也是基于开始位置的偏移,现在getscrollx是20