Android ScrollView监听滑动到顶部和底部的两种方式(你可能不知道的细节)

Android ScrollView监听滑动到顶部和底部,虽然网上很多资料都有说,但是不全,而且有些细节没说清楚

使用场景:

1. 做一些复杂动画的时候,需要动态判断当前的ScrollView是否滚动到底部或者顶部

2. ScrollView滚动到顶部或者底部时主动触发一些操作(典型的就是滚动到底部触发自动加载操作)

两种方式:

1. onScrollChanged方式,自己计算

2. onOverScrolled使用系统计算的结果,api >= 9才支持

可能忽视的细节1:

如果是手势滑动,上面两种方式都对,但是如果是调用ScrollViewsmoothScrollToscrollTo方法来滚动的话,

只有onScrollChanged监听对,onOverScrolled监听不对,因为通过代码来滚动话是精确滚动,onOverScrolled方法没处理这种情况

两种方式如何选择?

一般来说如果系统有实现的东西,就用系统的,我们毕竟是基于Android系统来做开发,别人做的考虑很多适配兼容问题

所以原则就是系统有就用它的,没有才用我们自己的,具体实现仔细看代码,记得看注释

如果不考虑smoothScrollTo和scrollTo滚动,上面这个原则就是对的,如果要考虑的话,这里只能使用onScrollChanged

滚动到顶部和底部时对应的计算关系:

备注:无padding的情况可以转换为有padding的情况,即tp,bp=0

mScrollY + H – tp – bp = h ===> mScrollY + H = h 

代码实现,自定义View,可以直接拷贝就可以使用

下面代码不考虑smoothScrollTo和scrollTo方法的影响,要考虑的话,去掉onOverScrolled方法,去掉onScrollChanged的api版本条件限制即可

import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;

/**
 * 监听ScrollView滚动到顶部或者底部做相关事件拦截
 */
public class SmartScrollView extends ScrollView {

    private boolean isScrolledToTop = true;  // 初始化的时候设置一下值
    private boolean isScrolledToBottom = false;
    public ScanScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    private ISmartScrollChangedListener mSmartScrollChangedListener;

    /** 定义监听接口 */
    public interface ISmartScrollChangedListener {
        void onScrolledToBottom();
        void onScrolledToTop();
    }

    public void setScanScrollChangedListener(ISmartScrollChangedListener smartScrollChangedListener) {
        mSmartScrollChangedListener = smartScrollChangedListener;
    }

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
        if (scrollY == 0) {
            isScrolledToTop = clampedY;
            isScrolledToBottom = false;
        } else {
            isScrolledToTop = false;
            isScrolledToBottom = clampedY;
        }
        notifyScrollChangedListeners();
    }

    @Override
    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        if (android.os.Build.VERSION.SDK_INT < 9) {  // API 9及之后走onOverScrolled方法监听
            if (getScrollY() == 0) {    // 小心踩坑1: 这里不能是getScrollY() <= 0
                isScrolledToTop = true;
                isScrolledToBottom = false;
            } else if (getScrollY() + getHeight() - getPaddingTop()-getPaddingBottom() == getChildAt(0).getHeight()) {
                // 小心踩坑2: 这里不能是 >=          // 小心踩坑3(可能忽视的细节2):这里最容易忽视的就是ScrollView上下的padding 
                isScrolledToBottom = true;
                isScrolledToTop = false;
            } else {
                isScrolledToTop = false;
                isScrolledToBottom = false;
            }
            notifyScrollChangedListeners();
        }
        // 有时候写代码习惯了,为了兼容一些边界奇葩情况,上面的代码就会写成<=,>=的情况,结果就出bug了
        // 我写的时候写成这样:getScrollY() + getHeight() >= getChildAt(0).getHeight()
        // 结果发现快滑动到底部但是还没到时,会发现上面的条件成立了,导致判断错误
        // 原因:getScrollY()值不是绝对靠谱的,它会超过边界值,但是它自己会恢复正确,导致上面的计算条件不成立
        // 仔细想想也感觉想得通,系统的ScrollView在处理滚动的时候动态计算那个scrollY的时候也会出现超过边界再修正的情况
    }

    private void notifyScrollChangedListeners() {
        if (isScrolledToTop) {
            if (mSmartScrollChangedListener != null) {
                mSmartScrollChangedListener.onScrolledToTop();
            }
        } else if (isScrolledToBottom) {
            if (mSmartScrollChangedListener != null) {
                mSmartScrollChangedListener.onScrolledToBottom();
            }
        }
    }

    public boolean isScrolledToTop() {
        return isScrolledToTop;
    }

    public boolean isScrolledToBottom() {
        return isScrolledToBottom;
    }
}

至此已经介绍完了,为了形象的表示smoothScrollTo和scrollTo方法调用时两种监听方式的结果下面加上log,这里不想看的可以不往下看了

@Override
protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
    super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
    if (scrollY == 0) {
        isScrolledToTop = clampedY;
        isScrolledToBottom = false;
        System.out.println("onOverScrolled isScrolledToTop:" + isScrolledToTop);
    } else {
        isScrolledToTop = false;
        isScrolledToBottom = clampedY;
        System.out.println("onOverScrolled isScrolledToBottom:" + isScrolledToBottom);
    }
    notifyScrollChangedListeners();
}

@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    super.onScrollChanged(l, t, oldl, oldt);   // 这个log可以研究ScrollView的上下padding对结果的影响
    System.out.println("onScrollChanged getScrollY():" + getScrollY() + " t: " + t + " paddingTop: " + getPaddingTop());
    if (getScrollY() == 0) {
        isScrolledToTop = true;
        isScrolledToBottom = false;
        System.out.println("onScrollChanged isScrolledToTop:" + isScrolledToTop);
    } else if (getScrollY() + getHeight() - getPaddingTop() - getPaddingBottom() == getChildAt(0).getHeight()) {
        isScrolledToBottom = true;
        System.out.println("onScrollChanged isScrolledToBottom:" + isScrolledToBottom);
        isScrolledToTop = false;
    } else {
        isScrolledToTop = false;
        isScrolledToBottom = false;
    }
    notifyScrollChangedListeners();
}

下面我们看下具体的执行结果:

1. 手动滑动到底部的情况--->两种方式都监听到了

2. 手动滑动到顶部的情况--->两种方式都监听到了

3. 调用smoothScrollTo(0, Integer.MAX_VALUE)或者scrollTo(0, Integer.MAX_VALUE)滑动到底部的情况

    --->只有onScrollChanged方法监听到滑动到底部

4.  调用smoothScrollTo(0, 0)或者scrollTo(0, 0)滑动到顶部的情况

    --->只有onScrollChanged方法监听到滑动到底部

感悟:

  很多细小的知识,我们平时总是因为开发的时候太忙来不及去深究,但是作为开发者我们还是要对技术保持严谨,需要通过自己的实战形成自己的经验,有些很细小的知识可能在关键时候起到意向不到的作用,如果平时注意积累,到时候会事半功倍。

  文章写得不专业,如果读者觉得有用,记得点赞,也欢迎指正。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏上善若水

039android初级篇之获取已安装应用的图标签名等信息并保存

对于已安装的应用我们可以使用PackageManager获取其图标 程序版本 版本名称 应用名 程序的权限 程序的签名等等。

533
来自专栏MelonTeam专栏

Android ScrollView设置初始position的方法

最近接了产品的一个需求,需要在一个拥有标题栏,内容区,图片区的滚动视图中,默认隐藏标题栏,而且支持用户手动下拉出现标题。 刚听到这个需求的时候,觉得这个很简单,...

2357
来自专栏一“技”之长

iOS原生地图开发进阶——使用导航和附近兴趣点检索

iOS中的mapKit框架对国际化的支持非常出色。在前些篇博客中,对这个地图框架的基础用法和标注与覆盖物的添加进行了详细的介绍,这篇博客将介绍两个更加实用的功能...

614
来自专栏非著名程序员

Attr、Style和Theme详解

? 前言 这三个概念贯穿Android框架的方方面面,是Android程序设计中很重要的一环,理解它们,并能学以致用,不但可以让你的代码变得简洁明了,还可以...

1889
来自专栏everhad

札记:翻译-使用Scene和Transition实现【场景切换】动画效果

简述:transitions framework 下面翻译transition为“过渡”,强调动画过程的含义,不过更多时候使用transition单词本身。 ...

1976
来自专栏代码GG之家

android recent key长按事件弹起触发最近列表故障分析

问题描述 [Dialer]it will appear different behaviors after long press the menu to ex...

1855
来自专栏较真的前端

编写模块化CSS——BEM

2307
来自专栏菩提树下的杨过

silverlight中的几个冷门标记 {x:Null},d:DesignWidth,d:DesignHeight

{x:Null}:用于设置某属性值为Null,比如<Rectangle Fill="{x:Null}" />,其实就相当于<Rectangle />,个人感觉这...

1846
来自专栏小灰灰

Java 实现图片合成

图片合成 利用Java的绘图方法,实现图片合成 在开始之前,先定一个小目标,我们希望通过图片合成的方式,创建一个类似下面样式的图片 ? I. 设计思路 首先...

52810
来自专栏非典型技术宅

iOS动画系列之八:使用CAShapeLayer绘画动态流量图1. CAShapeLayer2. 实战:绘制一个镂空图层动画3. 使用CAShapeLayer绘画动态流量图

1323

扫码关注云+社区