最近项目中用到了ScrollView,需要实现判断ScrollView是否滚动到顶部和底部,以进行相应的数据采集,下文将记录本人在实现ScrollView是否滚动到顶部和底部这个需求的一些过程,希望对大家有参考意义,如有不正确之处,望大家多多指教。
一、原理
对于ScrollView的滑动操作,ScrollView(View)提供了两个很重要的方法:
1)getScrollY(): 滑动ScrollView在Y轴(即垂直)方向上滑动的距离,个人觉得可以理解为ScrollView顶部已经滑出屏幕的距离;
2)onScrollChanged(int l, int t, int oldl, int oldt):当ScrollView滑动时,会触发该方法的执行,可以用来监听ScrollView的滑动变化;
同时,如果需要实现ScrollView滑动到顶部或底部的监听,需要找出ScrollView刚好滑动到顶部或底部时的临界值(或条件):
1、监听滑动到顶部:
getScrollY() == 0;
(注:此处不能设置为getScrollY() <= 0,下面会给出原因说明)
这个很好理解,当ScrollView滑动到顶部时,在Y轴(垂直)方向上的滑动距离getScrollY()值为0,即ScrollView顶部刚刚好没有滑出屏幕的距离;
2、监听滑动到底部:
View contentView = getChildAt(0);
contentView.getMeasuredHeight()== getScrollY() + getHeight();
(注:此处不能设置为<=,下面会给出原因说明)
由于ScrollView只能有一个(直接)子View,通过getChildAt(0)方法可以获取ScrollView的唯一子View对象,通过getMeasuredHeight()可以获取子View的测量高度,正如上面所说的,getScrollY()可以获取ScrollView顶部滑出屏幕的距离,getHeight()可以获取ScrollView的可见高度,如果contentView.getMeasuredHeight()== getScrollY() + getHeight()这个条件成立,即可以判断ScrollView滑动到底部了
二、遇到的问题及解决
1)底部回弹效果:
上图是在一次快速拖动页面滑动到底部时的ScrollView控件getScrollY()值的输出,其中红色的输出值是ScrollView控件滑动刚到底部时的输出值,在ScrollView快速拖拽滑动到底部时,会存在回弹效果,会产生如下两种情况:
a.ScrollView的滑出屏幕距离scrollY值会先大于真正到底部时的值(如上图显示的是1944),然后在短时间内回弹到真正到底部时的距离值(如上图显示的是1944),像上文所说的,所以如果在监听滑动到底部时,设置的成立条件为contentView.getMeasuredHeight() <= getScrollY() + getHeight(),在短时间内会存在多次回调
b.ScrollView真正回弹到底部时会触发两次操作(如上面的两个红色箭头所示),可以通过Handler+标志位的方案过滤第一次回调操作,只监听第一次回调操作,下面会给出代码实现。
2)顶部回弹效果:
同时,如上图所示,在ScrollView快速拖拽滑动到顶部时,也会存在回弹效果,产生如下两种情况:
a.ScrollView的滑出屏幕距离scrollY值会先小于真正到顶部时的值(先出现负值),然后在短时间内回弹到真正到顶部时的距离值(如上图显示的是0),像上文所说的,所以如果在监听滑动到顶部时,设置的成立条件为getScrollY() <= 0,在短时间内会存在多次回调
b.ScrollView真正回弹到顶部时会触发两次操作(如上面的两个红色箭头所示),同样,也可以通过Handler+标志位的方案过滤第一次回调操作,只监听第一次回调操作,下面会给出代码实现
三、实现代码
1)自定义ScrollView,并在内部定义滑动到顶部和底部时的回调接口和方法
public class CustomScrollView extends ScrollView {
//回调监听接口
private OnScrollChangeListener mOnScrollChangeListener;
//标识是否滑动到顶部
private boolean isScrollToStart = false;
//标识是否滑动到底部
private boolean isScrollToEnd = false;
private static final int CODE_TO_START = 0x001;
private static final int CODE_TO_END = 0x002;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case CODE_TO_START:
//重置标志“滑动到顶部”时的标志位
isScrollToStart = false;
break;
case CODE_TO_END:
//重置标志“滑动到底部”时的标志位
isScrollToEnd = false;
break;
default:
break;
}
}
};
public CustomScrollView(Context context) {
super(context);
}
public CustomScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
if (mOnScrollChangeListener != null) {
Log.i("CustomScrollView", "scrollY:" + getScrollY());
//滚动到顶部,ScrollView存在回弹效果效应(这里只会调用两次,如果用<=0,会多次触发)
if (getScrollY() == 0) {
//过滤操作,优化为一次调用
if (!isScrollToStart) {
isScrollToStart = true;
mHandler.sendEmptyMessageDelayed(CODE_TO_START, 200);
Log.e("CustomScrollView", "toStart");
mOnScrollChangeListener.onScrollToStart();
}
} else {
View contentView = getChildAt(0);
if (contentView != null && contentView.getMeasuredHeight() == (getScrollY() + getHeight())) {
//滚动到底部,ScrollView存在回弹效果效应
//优化,只过滤第一次
if (!isScrollToEnd) {
isScrollToEnd = true;
mHandler.sendEmptyMessageDelayed(CODE_TO_END, 200);
Log.e("CustomScrollView", "toEnd,scrollY:" + getScrollY());
mOnScrollChangeListener.onScrollToEnd();
}
}
}
}
}
//滑动监听接口
public interface OnScrollChangeListener {
//滑动到顶部时的回调
void onScrollToStart();
//滑动到底部时的回调
void onScrollToEnd();
}
public void setOnScrollChangeListener(OnScrollChangeListener onScrollChangeListener) {
mOnScrollChangeListener = onScrollChangeListener;
}
}
2)在主界面实现自定义滑动监听接口,实现监听方法,获取自定义ScrollView实例,并设置相关接口即可:
public class MainActivity extends AppCompatActivity implements CustomScrollView.OnScrollChangeListener {
private CustomScrollView scrollView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
scrollView = findViewById(R.id.scrollView);
scrollView.setOnScrollChangeListener(this);
}
@Override
public void onScrollToStart() {
Toast.makeText(this, "滑动到顶部了", Toast.LENGTH_SHORT).show();
}
@Override
public void onScrollToEnd() {
Toast.makeText(this, "滑动到底部了", Toast.LENGTH_SHORT).show();
}
}