ScrollView,看到这个字眼相信大家都很熟悉,但是,对于ScrollView的滑动机制,一些特殊的处理,可能不是很了解。
最近把玩一些优秀的app,发现部分在ScrollView上做了一些不一样的处理,优化了体验。
那么,接下来就来分析一下Scrollview的一些特殊处理和处理方法吧。
(以下Scrollview为HorizontalScrollView)
一 . 掌控ScrollView的滑动停止
很多事件,是需要判断ScrollView是否停止滚动来进行的,但是很可惜的是,在ScrollView里面并没有一个固定的方法去监听ScrollView的滚动停止。
那么,在部分应用中,是有进行处理的。那么,这个是怎么做的呢? 其实方法很简单,我们可以在自定义Scrollview,然后进行如下处理:
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
handler.sendMessageDelayed(handler.obtainMessage(-10086), SpaceTime);
break;
case MotionEvent.ACTION_DOWN:
lastX = 0;
break;
case MotionEvent.ACTION_MOVE:
break;
}
return false;
}
我们可以用一个Handler监听ACTION_UP动作,发出一条message
然后,在hangler中通过对scrollview的滑动距离来判断是否停止。代码如下:
int SpaceTime=10;//这里的速度不能太大
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case -10086:
if (lastX == getScrollX()) {
//表示已经停止
} else {//不一样表示未停止
handler.sendMessageDelayed(handler.obtainMessage(-10086),SpaceTime);
lastX = getScrollX();
}
break;
}
}
};
如此,便可以判断scrollview是否停止滚动了。
二.掌控scrollview的速度
相信用过scrollview的都知道,scrollview的滑动速度是有一套自己的算法的,他可以自动的根据我们滑动的速度来进行计算。并且,这个速度是没有提供api来控制的。
当然,我们可以去控制滑动的总体速率变化,这一点是可以通过重写系统的一个方法来实现的。如下:
@Override
public void fling(int velocityX) {
super.fling(velocityX / 2);
}
通过对参数velocityX的修改,可以控制scrollview的整体速度
但是,仅仅这一点可能无法满足我们的需求,比如说,我想让ScrollView滚动到某个速度的时候,便不再减速了,而是匀速滑动,直到停止。又或者,我希望scrollview的加速减速是受到我们的控制的。
这些情况下,那么,仅仅修改整体速率可能是不够的了。那么,这该怎么做呢。
那么,我们有如下几点需要解决
第一点 自动滚动
在scrollview停止之后,我们需要通过代码控制再让他滚动起来。
这里有两个方法,一个是通过系统自带的smoothScrollTo()来实现,但是,这种情况下,滚动的速度是不受我们控制的。
第二种方法,就是通过scrollTo()来控制。
通过对系统源码的判断我们可以发现,其实smoothScrollTo()最终也是调用scrollTo()来实现滑动的。所不同的是,scrollTo()滑动是瞬间的,并且没有滑动轨迹的。
那么,为了达到和smoothScrollTo一样的滑动效果,我们可以通过handle不停的发送消息来通知scrollview进行scrollTo(),一段段微小的变动,就变成了smoothscrollto了。
那么这里代码如下;
//自动滚动
private void autoScrollView() {
handler.sendEmptyMessageDelayed(autoScroll, SpaceTime);
}
int distance =10086;//滚动距离
int speed=20;//滚动速度
int autoScroll=-12345;
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case -12345://自动滚动
int add = distance > 0 ? 1 : -1;//左边右边滚动的判断
//每次滚动speed的距离
if (Math.abs(distance) > speed) {
scrollTo(speed * add + getScrollX(), 0);
sendEmptyMessageDelayed(autoScroll, SpaceTime);
distance -= speed * add;
} else {
scrollTo(distance * add + getScrollX(), 0);
distance = 0;
}
break;
}
}
};
第二点 掌控速度
解决了自动滚动之后,便是重点之处了,掌控scrollview的速度。
首先我们思考一下,scrollview的速度是怎么获取的?
这一点比较容易得知,我们开始有对scrollview是否停止滚动进行判断,这个判断是根据,两次handler直接,滚动的距离来判断的。而这个滚动距离,不正是我们需要的速度么!
ok,到这里,那么我们便获取到了scrollview的速度。
接下来,我们拿这个速度干嘛呢?我们需要scrollview的滚动速度达到某个值的时候,用我们自己的滑动去替代系统的滑动。
比如说,当scrollview的滚动达到速度为50的时候,Ok,我不想让他的速度再降低了,我要让他一直保持在50.
那么,首先应该干嘛呢?
首先,我们应该停止系统的滑动,因为,scrollview的滑动是不受控制的,而如果系统滑动没有停止的话,我们的滑动时无法生效的。
ok,想到第一点,那么我们可想解决办法了。但是问题来了,系统并没有提供一个主动停止滑动的方法给我们。那么应该怎么做呢?
这时候,就要去看看系统的源码了。因为我们知道,滑动中的scrollview虽然没有提供api停止滚动,但是.我们再使用scrollview的过程中可以发现,在滑动中,如果,点击一下scrollview的话,那滑动是会停止的。既然scrollview自带这个事件,那么,我们是否可以找到这个方法呢?
这里,通过阅读源码,找到了一段代码:
case MotionEvent.ACTION_DOWN: {
if (getChildCount() == 0) {
return false;
}
if ((mIsBeingDragged = !mScroller.isFinished())) {
final ViewParent parent = getParent();
if (parent != null) {
parent.requestDisallowInterceptTouchEvent(true);
}
}
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
// Remember where the motion event started
mLastMotionX = (int) ev.getX();
mActivePointerId = ev.getPointerId(0);
break;
}
在scrollview的ontouch事件的ACTION_DOWN里面有这么一段代码。我们可以看到这段
/*
* If being flinged and user touches, stop the fling. isFinished
* will be false if being flinged.
*/
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
从代码上我们就可以看到,scrollview是通过内部的Scroller来终止滑动的。
Ok,到这,我们找到了让scrollview停止的方法。那么我们是否可以通过调用mScroller.abortAnimation();来停止scrollview滚动了。
但是,我们发现,这个mScroller是私有的,也就是说,在自定义的scrollview中是无法正常获得mScroller的对象的。
对了,是无法正常获得,可不代表无法获得。
为什么?
因为安卓源码是java代码,也就是说,我们是可以通过反射来获取到父类的private对象的。
这里直接贴上方法。
public Object get(Object instance, String variableName) {
Class targeClass = instance.getClass().getSuperclass();
HorizontalScrollView superInst = (HorizontalScrollView) targetClass.cast(instance);
Field field;
try {
field = targetClass.getDeclaredField(variableName);
//修改访问限制
field.setAccessible(true);
// superInst 为 null 可以获取静态成员
// 非 null 访问实例成员
return field.get(superInst);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
OverScroller mscroller;
private void init() {
mscroller = (OverScroller) get(this, "mScroller");
}
如此,便可获取到mScrollview对象了。
这两点解决,那么我们便可以顺利的接管scrollview的滑动了。
整体代码如下:
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case -10086:
if (lastX == getScrollX()) {//
//停止滚动了
} else {
if (Math.abs(getScrollX() - lastX) <=MINSPEED) {
mscroller.abortAnimation();
sendEmptyMessageDelayed(autoScroll, SpaceTime);
//当达到我们想要控制的速度,同时发送一条自动滚动的消息。
} else {
handler.sendMessageDelayed(handler.obtainMessage(ViewScrollTag), SpaceTime);
}
lastX = getScrollX();
}
break;
case -12345://自动滚动
int add = distance > 0 ? 1 : -1;
if (Math.abs(distance) > speed) {
scrollTo(speed * add + getScrollX(), 0);
sendEmptyMessageDelayed(autoScroll, SpaceTime);
distance -= speed * add;
} else {
scrollTo(distance * add + getScrollX(), 0);
distance = 0;
}
break;
}
}
};
三 总结
经过以上的操作,我们便可以顺利的掌控scrollview的滑动了,一些优化的处理也能更好的进行了。为了方便大家更好的理解,特地写了一个小demo.有兴趣的可以下载看看。