ScrollView实现滚动到顶部和底部的判断和监听,消除滚动回弹效果

  最近项目中用到了ScrollView,需要实现判断ScrollView是否滚动到顶部和底部,以进行相应的数据采集,下文将记录本人在实现ScrollView是否滚动到顶部和底部这个需求的一些过程,希望对大家有参考意义,如有不正确之处,望大家多多指教。

一、原理

对于ScrollView的滑动操作,ScrollViewView)提供了两个很重要的方法:

1)getScrollY(): 滑动ScrollViewY轴(即垂直)方向上滑动的距离,个人觉得可以理解为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();
    }
}

猜你喜欢

转载自blog.csdn.net/okg0111/article/details/80229948