原文:http://blog.csdn.net/CodeTraveller/article/details/50818754
ScrollView的状态变化
前言:
所用代码和及描述仅代表个人观点,欢迎交流。 转载请说明出处:http://blog.csdn.net/CodeTraveller/article/details/50818754
用户场景:
在公司做一个 Android 项目,相信大家都有一个直观的体会,就是一旦某个功能在 iOS 上看上去非常牛X, 负责 Android 开发的程序猿就必须想尽办法把这个相似的功能克隆到 Android 项目中,所以就是如下设计:
在Android中实现上图功能,我用一个 ScrollView 包裹了首页的 Layout,然后通过监听 ScrollView 的各种状态来控制下面的那个“返回顶部”的View 的显示或隐藏。
首先我们得知道,ScrollView是没有一个方法能监听它的滑动状态的(ListView有这个方法setOnScrollListener()),和滑动相关的只有一个记录屏幕 Touch 事件的setOnTouchListener()方法。
所以最先想到的是通过手指动作来控制 Layout 的显示,但是这样就遇到下面这些问题:
1, 我们的手指在接触屏幕后,也许会有抖动现象,这样在判断手指上下移动时就会出现错误判断,这会使 Layout 的滑入划出非常的突兀;
2, 我们在 Activity 或 Fragment 中本来就需要处理太多的事,如果在其中还监听屏幕动作的话,会让我们的代(老)码(板)非常混(生)乱(气)。
3, 我仅仅需要一个能够得到其状态的 ScrollView ,不希望让控件的使用者看到太多的判断用户动作的逻辑,并且这样一个可得到其状态的 ScrollView 是在很多界面都需要使用的。
代码分析:
废话说了一大堆,下面直接上代码,先来看看新的 ScrollView 的完整代码:
/**
* @Created by mingweiliao on 16-3-4.
*/
public class ActionScrollView extends ScrollView {
public final static int ACTION_SCROLL_TOP = -1;
public final static int ACTION_SCROLL_BOTTOM = 1;
public final static int ACTION_SCROLL_UP = 10;
public final static int ACTION_SCROLL_STOP = 11;
public final static int ACTION_SCROLL_DOWN = 12;
private final static String TAG_ACTION = "ACTION";
private final static String TAG_SCROLL_Y = "SCROLL_Y";
private final static int HANDLE_WHAT_ACTION = 1;
private final static int HANDLE_WHAT_TIME = 2;
private final static int msg = 0;
private long delayedTime = 0;
private boolean msgLock;
private int lastScrollY = 0;
private ScrollHandler handler = new ScrollHandler();
private class ScrollHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == HANDLE_WHAT_ACTION) {
int action = msg.getData().getInt(TAG_ACTION);
int scrollY = msg.getData().getInt(TAG_SCROLL_Y);
onScrollActionListener.onScroll(action, scrollY);
msgLock = true;
handler.sendEmptyMessageDelayed(HANDLE_WHAT_TIME, delayedTime);
} else if (msg.what == HANDLE_WHAT_TIME) {
msgLock = false;
lastScrollY = getScrollY();
}
}
}
public ActionScrollView(Context context) {
super(context);
}
public ActionScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
private OnScrollActionListener onScrollActionListener;
public interface OnScrollActionListener {
void onScroll(int action, int scrollY);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
checkScrollAction();
}
private void checkScrollAction() {
if (onScrollActionListener == null || msgLock)
return;
if (handler == null)
handler = new ScrollHandler();
int action = -0x1024;
int scrollY = getScrollY();
View childView = getChildAt(0);
if (getScrollY() <= 0) {
action = ACTION_SCROLL_TOP;
} else if (childView != null && childView.getHeight() <= scrollY + getHeight()) {
action = ACTION_SCROLL_BOTTOM;
} else if (scrollY - lastScrollY < 0) {
action = ACTION_SCROLL_UP;
} else if (scrollY - lastScrollY > 0) {
action = ACTION_SCROLL_DOWN;
} else if (scrollY - lastScrollY == 0) {
action = ACTION_SCROLL_STOP;
}
if (action != -0x1024) {
Message msg = new Message();
msg.what = HANDLE_WHAT_ACTION;
Bundle bundle = new Bundle();
bundle.putInt(TAG_ACTION, action);
bundle.putInt(TAG_SCROLL_Y, scrollY);
msg.setData(bundle);
handler.sendMessage(msg);
}
Log.e("ActionScrollView", "ScrollY : " + getScrollY() + "lastScrollY : " + lastScrollY);
lastScrollY = getScrollY();
}
public void setActionDelayedTime(long delayedTime) {
this.delayedTime = delayedTime;
}
@Override
protected void onDetachedFromWindow() {
try {
Log.e("ScrollView", " onDetachedFromWindow() ");
handler.removeMessages(0);
handler = null;
} catch (Exception e) {
}
super.onDetachedFromWindow();
}
public void setOnScrollActionListener(OnScrollActionListener listener) {
this.onScrollActionListener = listener;
}
}
代码段解析:
//滑动条滑动到顶部
public final static int ACTION_SCROLL_TOP = -1;
//滑动条滑动到底部
public final static int ACTION_SCROLL_BOTTOM = 1;
//滑动条在向上划
public final static int ACTION_SCROLL_UP = 10;
//滑动条没有滑动(这个白送的)
public final static int ACTION_SCROLL_STOP = 11;
//滑动条在向下划
public final static int ACTION_SCROLL_DOWN = 12;
上面五个常量记录了ScrollView滑动时的五种状态
public interface OnScrollActionListener {
void onScroll(int action, int scrollY);
}
public void setOnScrollActionListener(OnScrollActionListener listener) {
this.onScrollActionListener = listener;
}
这个借口返回一个是状态值,一个是滑动条的ScrollY的值,通过这个两个值,用户可以知道滑动条具体滑动到哪里以及用户的滑动动作是什么。
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
checkScrollAction();
}
private void checkScrollAction() {
//判断用户是否设置监听以及是否对该方法上锁
if (onScrollActionListener == null || msgLock)
return;
//创建handler来对该方法上锁,防止多次执行
if (handler == null)
handler = new ScrollHandler();
//初始化ScrollView动作及当前的Y轴位置
int action = -0x1024;
int scrollY = getScrollY();
//获得子布局高度
View childView = getChildAt(0);
if (getScrollY() <= 0) {
//如果当前位置在顶部
action = ACTION_SCROLL_TOP;
} else if (childView != null && childView.getHeight() <= scrollY + getHeight()) {
//如果位置在底部
action = ACTION_SCROLL_BOTTOM;
} else if (scrollY - lastScrollY < 0) {
//如果ScrollBar在上滑
action = ACTION_SCROLL_UP;
} else if (scrollY - lastScrollY > 0) {
//如果ScrollView在下滑
action = ACTION_SCROLL_DOWN;
} else if (scrollY - lastScrollY == 0) {
//如果ScrollView没有动
action = ACTION_SCROLL_STOP;
}
//如果动作不是初始动作(因为我没有做else处理)
if (action != -0x1024) {
//发送Handler消息来通知接口,ScrollBar更新了状态
Message msg = new Message();
msg.what = HANDLE_WHAT_ACTION;
Bundle bundle = new Bundle();
bundle.putInt(TAG_ACTION, action);
bundle.putInt(TAG_SCROLL_Y, scrollY);
msg.setData(bundle);
handler.sendMessage(msg);
}
Log.e("ActionScrollView", "ScrollY : " + getScrollY() + "lastScrollY : " + lastScrollY);
//每次更新了状态,需要将lastScrollY设置为结束时的ScrollY
lastScrollY = getScrollY();
}
这个是最关键的方法,onScrollChanged()方法会在ScrollView滑动时调用,并且是滑动过程中实时更新,所以重写了这个方法来实时的返回ScrollView的滑动状态。
其中,判断到达底部的方法是这样的:
childView != null && childView.getHeight() <= scrollY + getHeight()
当ScrollBar滑动到底部时,如果没有弹性滑动,scrollY的值就不会变了,是子布局的高度和ScrollView的高度的差,但是很多手机的ScrollView是可以弹性滑动的,这就导致scrollY + getHeight()的值可能大于子布局的高度,所以,这里需要做“<=”的判断。
private ScrollHandler handler = new ScrollHandler();
private class ScrollHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == HANDLE_WHAT_ACTION) {
int action = msg.getData().getInt(TAG_ACTION);
int scrollY = msg.getData().getInt(TAG_SCROLL_Y);
onScrollActionListener.onScroll(action, scrollY);
msgLock = true;
handler.sendEmptyMessageDelayed(HANDLE_WHAT_TIME, delayedTime);
} else if (msg.what == HANDLE_WHAT_TIME) {
msgLock = false;
lastScrollY = getScrollY();
}
}
}
这里使用Handler进行刷新操作,是考虑到可能有些用户不希望实时监听,又不想重写OnTouch()方法,所以,提供一个延迟时间,在单位延迟时间内,接口中的onScroll()方法只会执行一次。
总结:
1,重写onTouchEvent()方法和onScrollChanged()都可以监听用户动作,但是onTouchEvent()不是实时监听,它只会监听用户手指按下到弹起这段时间内的动作,onScrollChanged()则是实时监听ScrollBar的状态,只要其状态改变,就会执行。
2,如果不希望onScroll()中的方法执行多次,可以设置延迟时间,并为onScroll()里面的方法添加同步块。或如例子中,设置synchronized 方法。
3,ScrollBar滑动到底部时,scrollY + ScrollView.getHeight()是大于或者等于子控件的高度的(可滑动距离+ScrollView的高度>=ScrollView中布局的高度)。
项目下载地址: Android-Studio环境:http://download.csdn.net/detail/codetraveller/9454491