效果图
这次,我们来实现第二个模块,即view 的滚动和使用 Scroller 平滑滚动,在这篇文章中,您将看到:
- View 的事件传递简析
- ScrollerBy 和 ScrollerTo 的区别,以及使用 Scroller 实现平滑过渡
前面中,我们已经通过 FlowLayout 实现测量和布局,这次新建一个类 ScrollFlowLayout 是专门实现滚动逻辑
一、View 的事件传递
当点击一个控件的时候,它的向下传递过程大致如下: activity --> window – > viewGroud --> view 。当然第一次走的是 disPatchTouchEvent 方法;通过源码知道,如果我们对 onInterceptTouchEvent 返回true,则父控件接管当前触摸事件,不再往下传递,而是回调自己的 onTouchEvent 方法。
View 的事件传递是基操,大家自行查阅啦
那么,我们就可以在 onInterceptTouchEvent 去这样写:
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastX = ev.getX();
//拿到上次的down坐标
mMoveX = ev.getX();
break;
case MotionEvent.ACTION_MOVE:
float dx = ev.getX() - mLastX;
if (Math.abs(dx) >= mTouchSlop) {
//由父控件接管触摸事件
return true;
}
mLastX = ev.getX();
break;
default:
break;
}
return super.onInterceptTouchEvent(ev);
}
二、View 的滚动和 Scroller
在 onTouchEvent 中,拿到了移动的偏移量,那怎么实现 View 自身的移动呢?
没错,就是使用 ScrollerBy 和 ScrollerTo,它们只改变 View 的内容而不会改变 View 的坐标 ,这正是我们需要的,需要注意的是,向左滑为正,向右为负。
- ScrollerTo(int x,int y) 绝对坐标移动,以原点为参考点
- ScrollerBy(int x,int y) 相对坐标移动,以上一次坐标为参考点
那么,onTouchEvent 就可以这样写了:
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
//scroller 向右为负,向左为正
int dx = (int) (mMoveX - event.getX());
scrollBy(dx, 0);
mMoveX = event.getX();
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
return super.onTouchEvent(event);
}
效果如下:
嗯,还差边界判断,首先,拿到右边边界:
mRightBound = child.getRight() + getPaddingRight();
接着,在move中去判断边界:
case MotionEvent.ACTION_MOVE:
//scroller 向右为负,向左为正
int dx = (int) (mMoveX - event.getX());
/**
* 判断左右边界
*/
int scrollX = getScrollX();
if (scrollX + dx <= 0) {
scrollTo(0, 0);
return true;
}
if (scrollX + dx >= mRightBound - mScreenWidth) {
scrollTo(mRightBound - mScreenWidth, 0);
return true;
}
scrollBy(dx, 0);
mMoveX = event.getX();
break;
接着,再运行一下:
边界加上了,但是总感觉有点卡顿,不够流畅,我们接着优化一下:
三、使用 Scroller 优化滑动卡顿
上面看到,当通过手指按住滑动之后,应该要有个滚动速度;从 Scroller 的 API 中,我们发现可以使用 Scroller 中的 Fling 方法;
先初始化 Scroller
mScroller = new Scroller(context);
而这个横向的滚动速度怎么来呢?
可以有两种,第一个是通过手势 Gestrue 的 Fling 拿到,还有一种则是 VelocityTracker ;这里,我们直接用 VelocityTracker 来拿到横向速度;
首先 VelocityTracker 通过 obtain 来拿到实例,并通过 addMoveMent 拿到 MotionEvent,这样才能正确的速度:
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(event);
在 up 的时候,拿到横向速度:
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
mVelocityTracker.computeCurrentVelocity(1000,mMaximumVelocity);
int velocityX = (int) mVelocityTracker.getXVelocity();
if (Math.abs(velocityX) >= mMinimumVelocity) {
mCurScrollX = getScrollX();
mScroller.fling(mCurScrollX, 0, velocityX, 0, 0, getWidth(), 0, 0);
if (mVelocityTracker != null) {
mVelocityTracker.recycle();
mVelocityTracker = null;
}
}
break;
其中 mVelocityTracker.getXVelocity() 表示的是 1s 内偏移的像素点;接着再把它赋值给 mScroller.fling();
当调用了 fling 之后,Scroller 就会调用 computeScroll 方法了。在 computeScroll 方法,拿到当前偏移的像素,与上次的对比,即可让它平滑滚动,如下:
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()){
int dx = mCurScrollX - mScroller.getCurrX();
// 超出右边界,进行修正
if (getScrollX() + dx >= mRightBound - mScreenWidth) {
dx = mRightBound - mScreenWidth - getScrollX();
}
// 超出左边界,进行修正
if (getScrollX() + dx <= 0) {
dx = -getScrollX();
}
scrollBy(dx,0);
postInvalidate();
}
}
最终效果如下 (gif 看起来一般般,建议运行看效果):
来源:CSDN
作者:夏至的稻穗
链接:https://blog.csdn.net/u011418943/article/details/103807920