Android安卓下拉阻尼效果实现原理及简单实例

Android自定义ScrollView实现阻尼回弹效果 Android开发中,当一个页面存放的控件超出屏幕时,通常需要使用ScrollView来包裹布局。这样用户可以通过手指的滑动来查看超出屏幕的部分。然而当ScrollView滑动到边界时,继续滑动只会显示一个阴影效果。IOS自带的控件却可以实现边界的阻尼回弹效果,这种阻尼回弹效果会让用户有更好的使用体验。这里给出一个Android上的实现方案 解决思路: ScrollView使用时要求内部有且仅一个... 阅读详情

转载请注明出处:codog_main的博客

前言

  本文将通过代码讲解下拉阻尼效果的实现原理。
  实现灵感来源于这篇博客,但是这篇博客的代码并不能让我满意,或者说是糟糕的,不过还是非常感谢作者带给我的启发。
  现在大部分资讯类安卓APP都有一个下拉刷新的功能,又如微信联系人列表顶部的小程序入口,也使用了这种下拉阻尼的效果。
  我的代码主要是解释其实现原理,为方便读者理解,所以代码逻辑非常简单,但如果想要实现例如下拉刷新转动的进度圈,还需要修改代码中的MoveHeaderTask类中的onProgressUpdate方法;如果要实现滑动列表顶部加入这种下拉阻尼效果,则需要修改代码中的onTouch方法,通过判断是否到达列表顶部来决定是否触发下拉阻尼效果的逻辑代码。
  最新的微信版本还实现了一个具有惯性的滑动列表(不清楚这样表述是否正确),滑动的速度大小和小程序入口的下拉阻尼效果会形成互动,但这已不是本文讨论的重点,这需要感兴趣的读者自行对我的代码进行迭代。
  运行效果如下:

  如图,拉动"可见主体"到达一定高度,"隐藏头部"就会弹出,反之,向上滑动到一定高度,"隐藏头部"则会收回,如果未到达指定高度,则恢复原状。
  实际运行效果其实很流畅,也不会出现上图中,头部无法完全隐藏的情况,只是AS自带的录屏工具比较差劲。我不建议把这个自定义控件用在对话框类型的activity上,因为前一个activity处于可见状态,可能会占用大量算力,导致动画效果不流畅,亲测。

原理

  这种效果是通过自定义控件的方式来实现的,我自定义了一个控件类型,这个自定义控件(PullDownDumperLayout)继承自线性布局(LinearLayout)。
  用户可以下拉弹出的那个视图,例如微信的小程序列表,开发者只是将这个视图移出了父元素之外,所以不可见,我们暂且称之为隐藏头部,只有下拉到一定程度才会弹出,而主体,例如微信的联系人列表,则是可见的,布局见下图。

实现这个效果需要我们做三件工作:

  1. 隐藏作为头部的控件
  2. 监听用户对屏幕的操作事件
  3. 实现下拉回弹的动画效果

  我们这个自定义控件会自动获取内部第一个子元素充当头部,其余的元素则是充当可见的主体(详见代码中的注释)。
  基本的布局原理差不多就这样了,但是我们还需要让自定义控件监听用户的手势操作,例如上下滑动等。这里我和灵感来源的那篇博客一样,让自定义控件实现View.OnTouchListener接口,实现内部的onTouch方法可以监听来自屏幕的所有触摸操作。代码中我让头部和第二个子元素(可见的主体)注册了这个监听器,这是为了方便读者理解,读者可根据自己的需求进行修改。
  注意,对于不能监听屏幕触摸事件的控件需要添加:
    android:clickable="true"
  至此,我们已经可以进行布局和监听用户手势了,但是还需要实现一个头部展开和隐藏的动画效果。当用户将隐藏头部下拉或上滑到一定高度时,这个效果就会被触发,这需要依赖上面所述的onTouch方法。动画效果的实现需要另开一个线程进行操作,线程的启动方式我们可以采用继承AsyncTask类来实现。
  除此之外,我们可能会多次复用这个控件,所以在自定义控件类的最后还需要一些调整参数的set方法。
  这里提个醒,在接下来的代码中,我们的自定义控件因为继承自LinearLayout,里面需要重写onLayout方法,而onLayout方法顾名思义就是布局,这个方法在Activity中的onCreate方法执行之后才会被调用,所以我们可以在Activity的onCreate方法中利用findViewById获取实例,调用上面提到的set方法进行参数的初始化。
  LinearLayout中不止onLayout一个方法,详细解析请读者移步其他关于XML标签加载过程的文章,这里不做赘述。

代码

PullDownDumperLayout .java:

public class PullDownDumperLayout extends LinearLayout implements View.OnTouchListener {
   
   

    /**
     * 取布局中的第一个子元素为下拉隐藏头部
     */
    private View mHeadLayout;

    /**
     * 隐藏头部布局的高的负值
     */
    private int mHeadLayoutHeight;

    /**
     * 隐藏头部的布局参数
     */
    private MarginLayoutParams mHeadLayoutParams;

    /**
     * 判断是否为第一次初始化,第一次初始化需要把headView移出界面外
     */
    private boolean mOnLayoutIsInit=false;

    /**
     * 移动时,前一个坐标
     */
    private float mMoveY;

    /**
     * 如果为false,会退出头部展开或隐藏动画
     */
    private boolean mChangeHeadLayoutTopMargin;

    /**
     * 触发动画的分界线,由mRatio计算得到
     */
    private int mBoundary;

    /**
     * 头部布局的隐藏和展开速度,以及单次执行时间
     */
    private int mHeadLayoutHideSpeed;
    private int mHeadLayoutUnfoldSpeed;
    private long mSleepTime;

    /**
     * 触发动画的分界线,头部布局上半部分和整体高度的比例
     */
    private double mRatio;

    public PullDownDumperLayout(Context context, AttributeSet attrs) {
   
   
        super(context, attrs);
        //初始化参数,根据自己的需求调整
        mHeadLayoutHideSpeed=-20;
        mHeadLayoutUnfoldSpeed=20;
        mSleepTime=10;
        mRatio=0.5;
    }

    /**
     * 布局开始设置每一个控件
     * 在activity的onCreate执行之后才会执行
     * 因此可以在onCreate中调用set方法设置参数
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
   
   
        super.onLayout(changed, l, t, r, b);
        if(!mOnLayoutIsInit && changed) {
   
   
            //将第一个子元素作为头部移出界面外
            mHeadLayout = this.getChildAt(0);
            mHeadLayoutHeight=-mHeadLayout.getHeight();
            mBoundary=(int)(mRatio*mHeadLayoutHeight);//计算触发动画分界线
            mHeadLayoutParams=(MarginLayoutParams) mHeadLayout.getLayoutParams();
            mHeadLayoutParams.topMargin=mHeadLayoutHeight;
            mHeadLayout.setLayoutParams(mHeadLayoutParams);
            //TODO 设置手势监听器,不能触碰的控件需要添加android:clickable="true"
            getChildAt(1).setOnTouchListener(this);
            mHeadLayout.setOnTouchListener(this);
            //标记已被初始化
            mOnLayoutIsInit=true;
        }
    }

    /**
     * 屏幕触摸操作监听器
     * @return 返回false表示在执行onTouch后会继续执行onTouchEvent
     */
    @Override
    public boolean onTouch(View v, MotionEvent event) {
   
   
        switch (event.getAction()) {
   
   
            case MotionEvent.ACTION_DOWN:
                mMoveY=event.getRawY();//捕获按下时的坐标,初始化mMoveY
                mChangeHeadLayoutTopMargin=false;
                break;
            case MotionEvent.ACTION_MOVE:
                float currY=event.getRawY();
                int vector=(int)(currY-mMoveY);//向量,用于判断手势的上滑和下滑
                mMoveY=currY;
                //判断是否为滑动
                if(Math.abs(vector)==0){
   
   
                    return false;
                }
                //头部完全隐藏时不再向上滑动
                
带阻尼回弹效果的RecyclerView 一、前提 接到新需求,要求列表滑动过程增加阻尼回弹效果,且即使列表不能填充一整个屏幕的情况下也支持滑动。 有人说,给RecyclerView加上 android:overScrollMode="always" 就行了,事实证明,NO!这个东西只是在滑动到边缘是多了个水波阴影而已,没有阻尼回弹。 又有人说,给ListView加上 android:overScrollMode="always" ... 阅读详情

相关推荐

安卓实现垂直滚动阻尼效果,超级超级简单

【代码】安卓实现垂直滚动阻尼效果,超级超级简单。

那人独钓寒江雪 519

实现Android ListView带阻尼效果的下拉上拉刷新功能

ListView作为Android开发中极为重要的组件,其刷新机制对提升用户体验至关重要。本章将对ListView的刷新机制进行深入介绍,从基础的刷新流程开始,逐步分析其背后的原理和常见的优化手段。自定义Header/Footer是提升用户界面个性化和用户体验的重要手段。在ListView或RecyclerView中,Header和Footer区域提供了展示额外信息的空间,比如广告、重要通知、引导页等。通过自定义实现,开发者可以将更多的业务逻辑与界面风格融入应用,以满足产品的特定需求。

weixin_42575109的博客 960

android 有阻尼下拉刷新列表的实现方法

本文将会介绍有阻尼下拉刷新列表的实现,先来看看效果预览: 这是下拉状态: 这是下拉松开手指后listView回滚到刷新状态时的样子: 1. 如何调用 虽然效果图看起来样子不太好看,主要是因为那个蓝色的背景对不对,没关系,这只是一个背景而已,在了解了我们这个下拉刷新列表的实现之后,你就可以很轻松地修改这个背景,从而实现你想要的UI效果!话不多说,下面我们先来讲讲这个下拉刷新列表是如何使用的,这也是我们编写代码所要实现的目标。 final PullToRefreshListView eListView = (PullToRefreshListView) rootView.findVi

服务器测试之intel E8102CQDA2

网卡性能 iperf E810

qq_41897488的博客 2506

小米路由器R1D进阶玩法:MIXBOX工具箱安装与插件管理指南

本文详细介绍了为小米路由器R1D安装MIXBOX工具箱的完整指南。通过开启SSH、执行一键安装脚本,用户可以轻松解锁路由器的进阶功能,如安装离线下载、内网穿透等插件,从而将旧设备转变为强大的家庭网络服务中心。

weixin_29292431的博客 1110

在Wincc8.0的Wincc 3D Control控件中使用3D模型

如果之前没有在Blender中自定义光照,EnableBackground属性选择“是”,Wincc 3D Control会按一个均匀的光照强度渲染。Wincc8.0更新后提供了一个Wincc 3D Control控件来显示3D图像,但是该控件实际体验下来现阶段还存在功能不足、运行不稳定的问题。再把导出的dae文件复制进Wincc项目的GraCS文件夹中,文件名称复制进控件的SceneFile属性里。选择编辑模式,全选模型,点击网格→分离→按材质,分离出不同的部件(不同的部件最好使用不同的材质)。

ghoring的博客 7129

赴美参加学术会议办理流程

有幸能在研究生阶段获得学校资助出国参加学术会议,首先要感谢学校的支持,以及学院和实验室的各位老师~ 接下来说下参会整个准备过程: 本人在接到论文录用通知后,就开始办理相关手续。因为会议地点是美国夏威夷,而且录用通知时间与会议召开时间只隔两个月左右,所以接下来两个月时间要办好学校出国申请手续,赴美签证,因为专业原因,据说签证不好办,所以时间比较紧。学校出国申请和签证相关材料是并行进行的,赴美花费...

xingxin_xu的博客 3195

手把手教你搞定JBI投稿:从Statement of Significance到Declaration Statement的保姆级避坑指南

本文提供了一份详细的JBI投稿指南,重点解析Statement of Significance和Declaration of Interests的撰写技巧,帮助研究者避开常见格式陷阱。通过实战案例和检查清单,确保投稿符合JBI的学术严谨性和临床转化价值要求,提升稿件通过率。

weixin_29278805的博客 620

把 Claude Code 接进 ABAP 世界,vibing-steampunk 到底能做什么

《Vibing Steampunk:AI Agent 与 ABAP 开发的工程化桥梁》摘要 Vibing Steampunk(vsp)是一个连接 AI Agent 与 SAP ABAP 开发环境的开源工具,通过 Model Context Protocol(MCP)将 ABAP 开发能力标准化。它解决了传统 ABAP 开发中 AI 辅助工具的局限性,使 Claude Code 等 AI 编程助手能够直接访问 ADT API,实现代码读写、调试、测试等完整开发流程。该工具采用 Go 语言开发,提供 147 个

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 1933

Android15车载系统开机冷启动流程源码解析

本文主要介绍Android15车机系统开机冷启动的流程,暂不包含bootloader,仅从kernel开始直至Launcher首页展示。

jidapenglin28的专栏 253

【实战】小红书笔记爬虫终极指南:模拟滑动加载,抓取图文(2026最新版)

小红书作为国内最大的生活方式分享社区,每天产生海量的图文笔记。对于数据分析、市场调研、竞品分析等需求,爬取小红书公开笔记成为一项常见任务。然而,小红书的反爬机制日益升级——接口加密、动态Token、滑动验证、设备指纹……本文将深入剖析小红书Web端和App端的爬取技术,手把手教你用Python实现模拟滑动加载、自动抓取图文笔记的完整爬虫系统。全文包含大量代码、原理分析和排坑经验,适合有一定爬虫基础的读者进阶学习。目录前言一、小红书反爬机制概览二、环境准备2.1 Python环境2.2 浏览器驱动。

2201_76125261的博客 764

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

上一篇: 关于“代码远程线程注入”的核心代码分享
下一篇: Android安卓实现原生文件选择器(浏览器)多选文件功能
codig
博客等级 码龄9年 10粉丝 3原创
评论 3
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值