一直对Scroller这个类不太熟悉,之前老是在网上找着看,但是过不了多长时间后就忘记了,今天来整理一下
先看一下Scroller里面的方法:
http://api.apkbus.com/reference/android/widget/Scroller.html
为了理解方便,拿SlideView来做说明,关于SlideView的demo网上有很多,这里为了讲解主要贴出SlideView:
<span style="font-size:14px;">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(); //相对于开始偏移的位置 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的增量(相对偏移量?) 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); } }</span>
先说一下实现的具体思路,SlideView是一个横向LinearLayout,作为父布局,它里面有两个child(当然可以更多),第一个child设置宽度充满整个父布局,这样的第二个child就会被挤出屏幕外面,要明确一点SlideView是无穷大的,你看到的只是屏幕显示的那一块,然后通过SlideView的scrollTo,让SlideView整个平移,这样第二个child就会显示出来
具体讲解一下:
1.ACTION_DOWN:mScroller停止动画,这个没什么说的
2.ACTION_MOVE:举个例子:SlideView已经偏移了scrollX,你下次move触发的时候只需要偏移scrollX - deltaX就可以了,scrollTo是相对于上次偏移,getScrollX却是向对于开始时的位置
3.ACTION_UP:在UP之前可以看到mScroller没有做任何关于滚动的的动作,up的时候调用了
<span style="font-size:14px;"> 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(); }</span>
触发了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做了什么呢?
<span style="font-size:14px;"> 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; }</span>
<span style="font-size:14px;"> 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; }</span>
可以看出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,直到滚动结束