OverScroller、VelocityTracker、ViewConfiguration综合使用达到炫酷效果

Android 自定义ScrollView 支持惯性滑动,惯性回弹效果。支持上拉加载更多 阅读详情

问题来源

在android应用程序的开发过程中,相信我们很多人都想把应用的交互做的比较绚丽,比如让界面切换平滑的滚动,还有热度灰常高的伪3D等界面效果,通常情况下,系统提供的应用在特效这方面只能为我们提供简单的动画接口,所以要想实现比较酷炫的效果还是要自己去开发布局控件(即所谓的自定义View、ViewGroup)。

ScrollVIew无法解决问题

下面就我最近工作中遇到的一个自定义控件开发做一些简单的介绍,其实那个地方原本可以用ScrollView解决很大一部分问题的,但有一些效果确实需要对控件进行重新定义,在继承ScrollView开发中仍然会遇到一些ScrollView自身的限制,所以就仿照ScrollView自己做了一个控件。在其中遇到了一些问题自然就是像ScrollView中拖动的效果(比如快速拖动在手指离开屏幕时控件依旧会由于惯性继续滑动一段距离后才会停止运动),所以就对这个东东做了一下仔细的研究,虽然以前也做过类似的开发,这次由于时间比较充裕,所以将开发中遇到的一些问题都一一记录了下来。下面开始正题:

过程描述

自定义布局控件自然是要继承某个View或ViewGroup
由于是根据项目的开发来写的这篇博客,所以我就以自定义布局控件(ViewGroup)来做介绍了。
开发一个自定义的ViewGroup自然是要继承ViewGroup类了,在继承这个类之后必须要重写的方法就是

onLayout(boolean changed, int l, int t, int r, int b)

如果想要对这个布局控件以及其子控件的尺寸进行精确的控制那就要重写下面这个方法了

onMeasure(int widthMeasureSpec, int heightMeasureSpec)

这个方法从字面理解就是估算控件的尺寸大小了,关于这个方法的详细说明引用一下另一位童鞋的文章http://www.eoeandroid.com/thread-102385-1-1.html,这里就不详细介绍了

如何让自定义的控件进行平滑的移动,并能够根据手势的情况产生惯性滑动的效果

先介绍一下开发这种滑动效果需要用到的各种工具类:
- android.view.VelocityTracker
- android.view.Scroller
- android.view.ViewConfiguration

VelocityTracker

VelocityTracker从字面意思理解那就是速度追踪器了,在滑动效果的开发中通常都是要使用该类计算出当前手势的初始速度(不知道我这么理解是否正确,对应的方法是:

velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity))

并通过getXVelocity或getYVelocity方法得到对应的速度值initialVelocity,并将获得的速度值传递给Scroller类的

fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)

ViewConfiguration

方法进行控件滚动时各种位置坐标数值的计算,API中对fling 方法的解释是基于一个fling手势开始滑动动作,滑动的距离将由所获得的初始速度initialVelocity来决定。关于ViewConfiguration 的使用主要使用了该类的下面三个方法:

 configuration.getScaledTouchSlop() 
 //获得能够进行手势滑动的距离

configuration.getScaledMinimumFlingVelocity()
//获得允许执行一个fling手势动作的最小速度值

configuration.getScaledMaximumFlingVelocity()
//获得允许执行一个fling手势动作的最大速度值

View需要重写的方法至少要包含下面几个方法:

onTouchEvent(MotionEvent event)
//有手势操作必然少不了这个方法了

computeScroll()
//必要时由父控件调用请求或通知其一个子节点需要

更新它的mScrollX和mScrollY的值。典型的例子就是在一个子节点正在使用Scroller进行滑动动画时将会被执行。所以,从该方法的注释来看,继承这个方法的话一般都会有Scroller对象出现。

在往下就是介绍比较具体的开发思路

首先我们要初始化一些变量,其中的多数代码已经在上面做出介绍了

Java代码:

void init(Context context) {
                mScroller = new Scroller(getContext());
                setFocusable(true);
                setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
                setWillNotDraw(false);
                final ViewConfiguration configuration = ViewConfiguration.get(context);
                mTouchSlop = configuration.getScaledTouchSlop();
                mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
                mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();

        }
然后我们申明一个用来处理滑动操作的方法fling(int velocityY)

代码如下:

public void fling(int velocityY) {
        if (getChildCount() > 0) {
                mScroller.fling(getScrollX(), getScrollY(), 0, velocityY, 0, 0, 0,
                                maxScrollEdge);
                final boolean movingDown = velocityY > 0;
                awakenScrollBars(mScroller.getDuration());
                invalidate();
        }
}

在这个方法里只是使用Scroller的fling方法开始执行fling手势动作了,关于其中的各种参数就不一一解释了。
awakenScrollBars(int startDelay)方法根据我对注释的理解就是在这里给出动画开始的延时,当参数startDelay为0时动画将立刻开始,其实就是一个延迟的作用

下面是对VelocityTracker的初始化以及资源释放的方法

Java代码:

private void obtainVelocityTracker(MotionEvent event) {
        if (mVelocityTracker == null) {
                mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(event);
}

private void releaseVelocityTracker() {
        if (mVelocityTracker != null) {
                mVelocityTracker.recycle();
                mVelocityTracker = null;
        }
}
onTouchEvent(MotionEvent event)方法的重写

Java代码:

public boolean onTouchEvent(MotionEvent event) {
                if (event.getAction() == MotionEvent.ACTION_DOWN
                                && event.getEdgeFlags() != 0) {
                        return false;
                }

                obtainVelocityTracker(event);

                final int action = event.getAction();
                final float x = event.getX();
                final float y = event.getY();

                switch (action) {
                case MotionEvent.ACTION_DOWN:
                        LogUtil.log(TAG, "ACTION_DOWN#currentScrollY:" + getScrollY()
                                        + ", mLastMotionY:" + mLastMotionY,
                                        LogUtil.LOG_E);
                        if (!mScroller.isFinished()) {
                                mScroller.abortAnimation();
                        }
                        mLastMotionY = y;
                        break;

                case MotionEvent.ACTION_MOVE:
                        final int deltaY = (int) (mLastMotionY - y);
                        mLastMotionY = y;
                        if (deltaY < 0) {
                                if (getScrollY() > 0) {
                                        scrollBy(0, deltaY);
                                } 
                        } else if (deltaY > 0) {
                                mIsInEdge = getScrollY() <= childTotalHeight - height;
                                if (mIsInEdge) {
                                        scrollBy(0, deltaY);
                                }
                        }
                        break;

                case MotionEvent.ACTION_UP:
                        final VelocityTracker velocityTracker = mVelocityTracker;
                        velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                        int initialVelocity = (int) velocityTracker.getYVelocity();

                        if ((Math.abs(initialVelocity) > mMinimumVelocity)
                                        && getChildCount() > 0) {
                                fling(-initialVelocity);
                        }

                        releaseVelocityTracker();
                        break;
                }

                return true;
        }

在onTouchEvent方法中,当手势执行到ACTION_UP时获得当时手势的速度值然后判断这个速度值是否大于可滑动的最小速度,如果符合条件那么就执行fling(int velocityY)方法,通过fling方法中的日志发现,在执行了invalidate()方法之后,程序便会执行computeScroll()方法,在computeScroll()方法中执行scrollTo方法主要是因为mScrollX、mScrollY这两个变量的修饰符为portected,无法在扩展类里面无法对这两个变量直接进行操作,那么就需要使用scrollTo方法对这两个变量进行操作,以刷新当前的UI控件,

下面附上computeScroll()方法的代码

Java代码:

public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
                int scrollX = getScrollX();
                int scrollY = getScrollY();
                int oldX = scrollX;
                int oldY = scrollY;
                int x = mScroller.getCurrX();
                int y = mScroller.getCurrY();
                scrollX = x;
                scrollY = y;
                scrollY = scrollY + 10;
                scrollTo(scrollX, scrollY);
                postInvalidate();
        }
}

其中的mScroller.computeScrollOffset()是用来判断动画是否完成,如果没有完成返回true继续执行界面刷新的操作,各种位置信息将被重新计算用以重新绘制最新状态的界面。

关于scrollTo方法,我们需要看一下该方法的代码(来自View中):

Java代码:

public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                invalidate();
            }
        }
    }

我们可以看到,当传递进来的x、y的值与控件当前的mScrollX、mScrollY的值不相同时对界面进行重新计算,根据日志打印的情况来看似乎awakenScrollBars()返回的总是true, 这样的话每执行一次computeScroll()方法,就需要执行一次postInvalidate()方法来刷新界面,而postInvalidate()方法会通过内部线程重新调用invalidate()已达到界面刷新的效果,产生手势离开屏幕之后的惯性滑动效果。

在这里总结一下,大概的思路如下:

  1. 首先我们通过VelocityTracker、ViewConfiguration类得到一些惯性滑动所必须的变量,比如手势离开屏幕时的初始速度,允许进行手势操作的最小距离以及允许手势操作的速度边界值;
  2. 创建Scroller的对象,使用它的fling方法供我们控制界面滑动使用;
  3. 重写onTouchEvent方法,当我们用手指在屏幕上来回滑动时此时执行的是scrollBy方法来刷新界面,当手指离开屏幕,此时就要开始执行ACTION_UP后面的操作了;
  4. 通过对手指离开屏幕时的速度进行判断是否能够进行惯性滑动操作,如果能够执行那么就使用Scroller类的fling方法启动滑动动画,
    这时需要调用一下invalidate()方法来间接的调用computeScroll方法,
    在computeScroll方法中对Scroller的动画是否执行完成做了判断,
    如果动画没有完成(mScroller.computeScrollOffset() == true)那么就使用scrollTo方法对mScrollX、mScrollY的值进行重新计算刷新界面,
    调用postInvalidate()方法重新绘制界面,postInvalidate()方法会调用invalidate()方法,invalidate()方法又会调用computeScroll方法,
    就这样周而复始的相互调用,直到mScroller.computeScrollOffset() 返回false才会停止界面的重绘动作

总结,滑动效果来看,它依然是在不停的计算控件的位置刷新屏幕,不停的绘制新的图片替换旧的图片,当然每次刷新的速度很快,从而给人一种是在快速滑动的感觉,写到这里我发现,现在所谓的动画总是逃脱不了电影的那种模式,每秒播放多少帧的图片来达到连续播放的效果欺骗人的眼睛。
而且,关于android一些酷炫效果的开发,还是要自己多动手,熟悉View、ViewGroup中每个绘制方法、位置计算方法的调用方式以及顺序,那么至少是在2D动画开发中,也就是一种方式,逃脱不了不停重新绘制的这个圈。
关于熟悉View、ViewGroup中每个绘制方法、位置计算方法的调用方式以及顺序的问题,我建议最好自己写一个简单的自定义View或ViewGroup的扩展类,重载那些绘制、位置计算的方法打个日志出来一看自然就明白了,虽然这个方法很笨,但是很容易出效果的

Android应用开发之自定义View触摸相关工具类全解 public int getScaledPagingTouchSlop() {} //不推荐使用,推荐getScaledDoubleTapSlop()代替;判断是否双击的阈值 public static int getDoubleTapSlop() {} public int getScaledDoubleTapSlop() {} //不推荐使用,推荐getScaledWindowTouchSlop()代替;触摸窗体边沿区域判断 public static int getWindowTouchSlop() 阅读详情

相关推荐

第 25 章:View 系统与输入事件分发

LayoutInflater 运行时将 XML 布局资源转换成 View 对象,连接 res/layout 下声明式 XML 和内存中程序视图树。来源:frameworks/base/core/java/android/view/LayoutInflater.java (第 74 行)...创建自定义 View 需要理解并实现正确的回调方法。默认 onMeasure 实现 wrap_content 会填满父容器;自定义 View 需要正确处理测量模式。@Override// 固有尺寸像素。

dengfuma的专栏 536

OverScroller 原理解析

滚动计算使用物理公式(如基于速度和加速度的公式)来计算滚动动画的每一步位置。这允许它模拟现实世界中物体滚动的行为,包括惯性。边界处理能够识别滚动边界,并在达到边界时停止滚动或执行过界滚动效果(如果启用了)。Fling 手势:当用户快速滑动并释放时,可以接收起始位置和速度等参数,并基于这些参数启动一个滚动动画,模拟惯性滚动的效果。时间插值器允许你设置时间插值器(Interpolator),以控制动画的速度曲线,使滚动看起来更自然。

望佑 1033

嵌套滑动机制实现的联合fling滚动

利用android嵌套滑动机制实现的联合fling滚动demo

让控件如此丝滑Scroller和VelocityTracker的API讲解与实战——Android高级UI

目录 一、前言 二、Scroller 三、VelocityTracker 四、实战——带惯性滑动的柱状图 五、写在最后 一、前言 自定义控件中,难免会遇到需要滑动的场景。而Canvas提供的scrollTo和scrollBy方法只能达到移动的效果,需要达到真正的滑动便需要我们今天分享的两把基础利器Scroller和VelocityTracker。老规矩,先上实战图,再进行分享。 带惯性滑动的柱...

weixin_34010949的博客 658

大一Python之循环解决实际问题

pythin之循环解决实际问题

weixin_66263546的博客 2287

从零到一:基于JasperGold的FPV实战入门与避坑指南

本文详细介绍了基于JasperGold的形式化验证(FPV)实战入门指南,涵盖环境搭建、SVA断言编写、TCL脚本调试等关键步骤。通过实际案例解析,帮助IC设计工程师掌握FPV技术,有效验证RTL设计中的复杂逻辑和corner case,提升验证效率与质量。

weixin_30899023的博客 364

Android的Scroller和VelocityTracker

一. Android的Scroller介绍     public class Scroller extends Object     java.lang.Object      android.widget.Scroller二. 概述 这个类封装了滚动操作。滚动的持续时间可以通过构造函数传递,并且可以指定滚动动作的持续的最长时间。经过这段时间,滚动会自动定位到最终位置,并且通过computeScro

0.0 996

MIPI转LVDS芯片调试实战:从Kernel到UEFI的完整点亮流程

本文详细解析了MIPI转LVDS芯片调试的全流程,从Kernel驱动开发到UEFI早期初始化,涵盖I2C通信、电源时序控制等核心挑战。通过LT9211芯片实战案例,提供设备树配置、电源时序优化及显示异常排查等实用技巧,助力工程师高效完成屏幕调试工作。

weixin_30314813的博客 450

OverScroller的使用 自定义一个ScrollView

简介 本文主要介绍OverScroller的使用。而本文示例演示综合使用OverScroller和EdgeEffect来实现自定义ScrollerView。OverScroller的基本使用与Scroller的使用非常类似,官方推荐使用OverScroller代替Scroller。关于使用OverScroller还是像Scroller那样,三个步骤:定义滚动,触发滚动,然后不断更新滚动。具体看Scroller的使用这篇文章。 git项目: https://gitee.com/guaishoun/over_s

Gyso的博客 749

VC Spyglass CDC实战:从TCL脚本到SDC约束的完整避坑指南

本文是一份针对数字IC验证工程师的VC Spyglass CDC实战指南。文章详细阐述了从TCL脚本环境搭建、SDC约束编写到典型CDC违规(如SETUP_PORT_UNCONSTRAINED)诊断与修复的完整流程,旨在帮助读者高效避坑,深入理解工具逻辑,从而完成可靠的时钟域交叉验证。

go5gopher的博客 383

位置点位到达判断FC(Easy+Codesys+博途平台)

IN rActPositon REAL 轴当前实际位置 1IN arAbsPosition REAL[10] 各工作点位坐标 2IN rDeadZone REAL 位置达到判断域值 范围 3OUT abInAbsPos BOOL[10] 到达各绝对定位点位标志 位置到达输出 4VAR i INT 循环计数 5VAR rErr REAL 位置偏差 6。

RXXW_Dor的博客 536

Android应用开发之自定义View触摸相关工具类全解,已整理成文档

… } 有了上面这个工具类,我们在自定义控件处理滑动手势等判断时就可以很方便的判断出临界值等问题,不用我们再去自己测试定义一个近似的值来代替。 特别注意: ViewConfiguration还有一个在support包中的兼容类ViewConfigurationCompat,使用时请注意一下。 【工匠若水 http://blog.youkuaiyun.com/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我】 Scroller加强版OverScroller回弹工具类 ==================

m0_64319166的博客 230

第1集:认识 Claude Code —— AI 编程新纪元

1. 定义 Claude Code:终端中的 AI 编程代理Claude Code 是 Anthropic 公司于 2025 年 5 月正式推出的命令行 AI 编程工具,它不只是一个代码补全工具,而是一个能够理解项目、自主执行任务的**终端 AI 代理(Agent)。它的核心理念是 **"生于终端,活于终端"** (lives in your terminal)。与深度嵌入图形界面(GUI)的工具不同,Claude Code 选择直接融入开发者最原始、最核心的工作环境——命令行。

dianziyao_的博客 865

Android自定义View滑动事件处理总结

滑动处理需要用到的各种工具类: android.view.VelocityTracker android.view.OverScroller android.view.ViewConfiguration VelocityTracker类 主要用跟踪触摸屏事件(flinging事件和其他gestures手势事件)的速率。 用addMovement(MotionEven...

weixin_30301449的博客 144

如何用 Claude Code 做代码重构,而不是重写

摘要: 在使用Claude Code进行代码优化时,AI容易“重写”而非“重构”,导致改动过大、review困难。本文提出3个关键约束方法:1)限制修改范围(如仅修改指定函数);2)明确不改变代码行为;3)要求最小必要修改。通过分步骤重构、先获取重构建议再修改、使用标准模板(保持函数签名/输入输出/结构不变)等方式,可使AI输出更符合工程需求的小范围安全重构。对于彻底混乱的代码,才建议直接重写。核心在于通过提示词精准约束AI的修改行为,将其转化为高效的代码重构助手。

sg_knight的专栏 981

RT-DETR训练自己的数据集(从代码下载到实例测试)

在Windows10上配置CUDA环境教程是百度提出的一种实时目标检测模型。RT-DETR采用了与DETR相同的编码器和解码器结构,但对其进行了大量的优化,在保持较高检测精度的同时,实现了实时目标检测,为目标检测领域提供了一种新的有效解决方案,具有广泛的应用前景,如自动驾驶、智能监控、机器人等领域。以上是RT-DETR官方的论文和代码,详细介绍了RT-DETR的构成和实现。在YOLOv10中也实现了RT-DETR的代码,所以本文使用YOLOv10的项目文件进行RT-DETR的训练和测试。YOLOv10。

Limiiiing的博客 4万+

Android View滑动相关的基础知识点

本文涉及到的知识点:MotionEvent、ViewConfiguration、VelocityTracker 、GestureDetector、scrollTo、scrollBy、Scroller、OverScroller ###MotionEvent ACTION_DOWN :手指刚接触到屏幕 ACTION_MOVE :手指在屏幕上移动 ACTION_UP :手指在屏幕上松开的一刹那 A...

weixin_34023863的博客 96

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

仿淘宝商品详情页面下拉黏滞效果

项目中需要用到淘宝商品详情页面的下拉黏滞效果,刚开始的想法比较复杂,是通过投机取巧的方式来大致实现的,但是效果很不好,勉强可以使用,这怎么能行?后来自己尝试着去优化,感觉一个ListView就可以实现,于是就去用listView去实现了一下,主要用到了ListView的smoothScrollToPosition这个方法,做到最后,发现smoothScrollToPosition这个方法的一个bu

ly985557461的专栏 4846

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

VelocityTrackerView VelocityTracker 与 Scroller 实现View的滑动

VelocityTrackerView  VelocityTracker 与 Scroller 实现View的滑动

越努力,越幸运 1448

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Android 手把手进阶自定义View(十三)- 滚动速度 VelocityTracker 及滚动计算 Scroller、OverScroller

一、前言 Scroller 和 OverScroller,这两个是 Android UI 框架下实现滚动效果的最关键的类,ScrollView 内部的实现也是使用的 OverScroller,所以熟练的使用这两个类的相关API,可以让我们满足大部分的滚动开发需求。 二、Scroller 在 View 类里面,有两个和滚动相关的类 scrollTo() 和 scrollBy。这两个方法可...

lerendan的博客 1684
上一篇: Android VelocityTracker使用总结
下一篇: Android 打造炫目的圆形菜单 秒秒钟高仿建行圆形菜单
coder_nice
博客等级 码龄12年 33粉丝 23原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值