Android自定义 View惯性滚动效果(不使用Scroller)

效果图:

前言:

看了网上很多惯性滚动方案,都是通过Scroller 配合 computeScroll实现的,但在实际开发中可能有一些场景不合适,比如协调布局,内部子View有特别复杂的联动效果,需要通过偏移来配合。我通过VelocityTracker(速度跟踪器)实现了相同的效果,感觉还行🤣,欢迎指正,虚拟机有延迟,真机效果更好。

1. 布局文件 activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><com.example.flingscrollview.LinerScrollViewandroid:id="@+id/mScrollView"android:orientation="vertical"android:layout_height="match_parent"android:layout_width="match_parent"/></FrameLayout>

2. 演示View

package com.example.flingscrollview;import android.content.Context;
import android.graphics.Color;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;import androidx.annotation.Nullable;public class LinerScrollView extends LinearLayout {final Handler mHandler;private final int mTouchSlop; // 移动的距离大于这个像素值的时候,会认为是在滑动private final int mMinimumVelocity; // 最小的速度private final int mMaximumVelocity; // 最大的速度private VelocityTracker mVelocityTracker; // 速度跟踪器private int mScrollPointerId; // 当前最新放在屏幕伤的手指private int mLastTouchX; // 上一次触摸的X坐标private int mLastTouchY; // 上一次触摸的Y坐标private int mInitialTouchX; // 初始化触摸的X坐标private int mInitialTouchY; // 初始化触摸的Y坐标public final int SCROLL_STATE_IDLE = -1; // 没有滚动public final int SCROLL_STATE_DRAGGING = 1; // 被手指拖动情况下滚动public final int SCROLL_STATE_SETTLING = 2; // 没有被手指拖动情况下,惯性滚动private int mScrollState = SCROLL_STATE_IDLE; // 滚动状态// 在测试过程中,通过速度正负值判断方向,方向有概率不准确// 所以我在onTouchEvent里自己处理private boolean direction = true; // true:向上 false:向下private FlingTask flingTask; // 惯性任务public LinerScrollView(Context context, @Nullable AttributeSet attrs) {super(context, attrs);mHandler = new Handler(Looper.getMainLooper());// 一些系统的预定义值:ViewConfiguration configuration = ViewConfiguration.get(getContext());mTouchSlop = configuration.getScaledTouchSlop();mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();initView();}/*** 初始化视图*/private void initView() {for (int i = 0; i < 50; i++) {TextView textView = new TextView(getContext());ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 350);textView.setLayoutParams(params);textView.setText("index:" + i);textView.setTextColor(Color.BLACK);textView.setTextSize(30);textView.setBackgroundColor(Color.CYAN);textView.setGravity(Gravity.CENTER_VERTICAL);addView(textView);}}boolean notUp = false; // 是否 不能再向上滑了boolean notDown = false; // 是否 不能再向下滑了int listMaxOffsetY = 0; // 列表最大滑动Y值/*** 滚动列表* @param offsetY 偏移Y值*/private void translationViewY(int offsetY) {if (listMaxOffsetY == 0) {listMaxOffsetY = (350 * 50) - getHeight();}if (mScrollState == SCROLL_STATE_DRAGGING) {if (direction) { // 向上滑动if (Math.abs(getChildAt((getChildCount() - 1)).getTranslationY()) < listMaxOffsetY) {notUp = false;}} else { // 向下滑动if (getChildAt(0).getTranslationY() < 0) {notDown = false;}}}for (int i = 0; i < getChildCount(); i++) {View childView = getChildAt(i);int yv = (int) (childView.getTranslationY() + offsetY);if (direction) { // 向上滑动notDown = false;if (!notUp) {if (Math.abs(yv) >= listMaxOffsetY) {notUp = true;}}if (!notUp) childView.setTranslationY(yv);} else { // 向下滑动notUp = false;if (!notDown) {if (yv >= 0) {notDown = true;}}if (!notDown) childView.setTranslationY(yv);}}}/*** 惯性任务* @param velocityX X轴速度* @param velocityY Y轴速度* @return*/private boolean fling(int velocityX, int velocityY) {if (Math.abs(velocityY) > mMinimumVelocity) {flingTask = new FlingTask(Math.abs(velocityY), mHandler, new FlingTask.FlingTaskCallback() {@Overridepublic void executeTask(int dy) {if (direction) { // 向上滑动translationViewY(-dy);} else { // 向下滑动translationViewY(dy);}}@Overridepublic void stopTask() {setScrollState(SCROLL_STATE_IDLE);}});flingTask.run();setScrollState(SCROLL_STATE_SETTLING);return true;}return false;}/*** 停止惯性滚动任务*/private void stopFling() {if (mScrollState == SCROLL_STATE_SETTLING) {if (flingTask != null) {flingTask.stopTask();setScrollState(SCROLL_STATE_IDLE);}}}@Overridepublic boolean onTouchEvent(MotionEvent event) {super.onTouchEvent(event);boolean eventAddedToVelocityTracker = false;// 获取一个新的VelocityTracker对象来观察滑动的速度if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);// 返回正在执行的操作,不包含触摸点索引信息。即事件类型,如MotionEvent.ACTION_DOWNfinal int action = event.getActionMasked();int actionIndex = event.getActionIndex();// Action的索引// 复制事件信息创建一个新的事件,防止被污染final MotionEvent copyEv = MotionEvent.obtain(event);switch (action) {case MotionEvent.ACTION_DOWN: { // 手指按下stopFling();// 特定触摸点相关联的触摸点id,获取第一个触摸点的idmScrollPointerId = event.getPointerId(0);// 记录down事件的X、Y坐标mInitialTouchX = mLastTouchX = (int) (event.getX() + 0.5f);mInitialTouchY = mLastTouchY = (int) (event.getY() + 0.5f);}break;case MotionEvent.ACTION_POINTER_DOWN: { // 多个手指按下// 更新mScrollPointerId,表示只会响应最近按下的手势事件mScrollPointerId = event.getPointerId(actionIndex);// 更新最近的手势坐标mInitialTouchX = mLastTouchX = (int) (event.getX() + 0.5f);mInitialTouchY = mLastTouchY = (int) (event.getY() + 0.5f);}break;case MotionEvent.ACTION_MOVE: { // 手指移动setScrollState(SCROLL_STATE_DRAGGING);// 根据mScrollPointerId获取触摸点下标final int index = event.findPointerIndex(mScrollPointerId);// 根据move事件产生的x,y来计算偏移量dx,dyfinal int x = (int) (event.getX() + 0.5f);final int y = (int) (event.getY() + 0.5f);int dx = Math.abs(mLastTouchX - x);int dy = Math.abs(mLastTouchY - y);// 在手指拖动状态下滑动if (mScrollState == SCROLL_STATE_DRAGGING) {if (mLastTouchY - y > 0.5f) {direction = true;// Log.d("TAG", "向上");translationViewY(-dy);} else if (y - mLastTouchY > 0.5f) {direction = false;// Log.d("TAG", "向下");translationViewY(dy);}}mLastTouchX = x;mLastTouchY = y;}break;case MotionEvent.ACTION_POINTER_UP: { // 多个手指离开// 选择一个新的触摸点来处理结局,重新处理坐标onPointerUp(event);}break;case MotionEvent.ACTION_UP: { // 手指离开,滑动事件结束mVelocityTracker.addMovement(copyEv);eventAddedToVelocityTracker = true;// 计算滑动速度// mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);// 最后一次 X/Y 轴的滑动速度final float xVel = -mVelocityTracker.getXVelocity(mScrollPointerId);final float yVel = -mVelocityTracker.getYVelocity(mScrollPointerId);if (!((xVel != 0 || yVel != 0) && fling((int) xVel, (int) yVel))) {setScrollState(SCROLL_STATE_IDLE); // 设置滑动状态}resetScroll(); // 重置滑动}break;case MotionEvent.ACTION_CANCEL: { //手势取消,释放各种资源cancelScroll(); // 退出滑动}break;}if (!eventAddedToVelocityTracker) {// 回收滑动事件,方便重用,调用此方法你不能再接触事件mVelocityTracker.addMovement(copyEv);}// 回收滑动事件,方便重用copyEv.recycle();return true;}/*** 有新手指触摸屏幕,更新初始坐标* @param e*/private void onPointerUp(MotionEvent e) {final int actionIndex = e.getActionIndex();if (e.getPointerId(actionIndex) == mScrollPointerId) {// Pick a new pointer to pick up the slack.final int newIndex = actionIndex == 0 ? 1 : 0;mScrollPointerId = e.getPointerId(newIndex);mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f);mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f);}}/*** 手指离开屏幕*/private void cancelScroll() {resetScroll();setScrollState(SCROLL_STATE_IDLE);}/*** 重置速度*/private void resetScroll() {if (mVelocityTracker != null) {mVelocityTracker.clear();}}/*** 更新 滚动状态* @param state*/private void setScrollState(int state) {if (state == mScrollState) {return;}mScrollState = state;}}

3. 惯性滚动任务类(核心类)

package com.example.flingscrollview;import android.os.Handler;
import android.util.Log;class FlingTask implements Runnable {private Handler mHandler;private int velocityY = 0;private int originalVelocityY = 0;private FlingTaskCallback flingTaskCallback;public FlingTask(int velocityY, Handler handler, FlingTaskCallback callback) {this.velocityY = velocityY;this.mHandler = handler;this.originalVelocityY = velocityY;this.flingTaskCallback = callback;}boolean initSlide = false; // 初始化滑动int average = 0; // 平均速度int tempAverage = 1;boolean startSmooth = false; // 开始递减速度平滑处理int sameCount = 0; // 值相同次数// 这里控制平均每段滑动的速度private int getAverageDistance(int velocityY) {int t = velocityY;if (t < 470) {t /= 21;}// divide by zeroif (t == 0) return 0;int v = Math.abs(velocityY / t);if (v < 21) {t /= 21;if (t > 20) {t /= 5;}}return t;}@Overridepublic void run() {// 速度完全消耗完才结束任务,和view滚动结束不冲突// 这个判断是为了扩展,将没消耗完的速度,转给指定的滚动view// if (velocityY > 0) {// 只要view滚动结束,立刻结束任务if (tempAverage > 0 && velocityY > 0) {if (!initSlide) {average = getAverageDistance(velocityY);initSlide = true;}float progress = (float) velocityY / originalVelocityY;float newProgress = 0f;if (average > 300) {newProgress = getInterpolation(progress);} else {newProgress = getInterpolation02(progress);}int prTemp = tempAverage;if (!startSmooth) tempAverage = (int) (average * newProgress);// 递减速度平滑处理if (prTemp == tempAverage) {sameCount++;if (sameCount > 1 && tempAverage > 0) { // 这个值越大,最后衰减停止时越生硬,0 - 30tempAverage--;sameCount = 0;startSmooth = true;}}flingTaskCallback.executeTask(tempAverage);velocityY -= tempAverage;// 这里这样写是为了扩展,将没消耗完的速度,转给其他滚动列表// 判断语句需要改成 if (velocityY > 0)if (tempAverage == 0) { // view滚动停止时// 如果速度没有消耗完,继续消耗velocityY -= average;}// Log.d("TAG", "tempAverage:" + tempAverage + " --- velocityY:" + velocityY + " --- originalVelocityY:" + originalVelocityY);mHandler.post(this);} else {flingTaskCallback.stopTask();stopTask();}}public void stopTask() {mHandler.removeCallbacks(this);initSlide = false;}// 从加速度到逐步衰减(AccelerateDecelerateInterpolator插值器 核心源码)public float getInterpolation(float input) {return (float) (Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;}// 速度逐步衰减(DecelerateInterpolator插值器 核心源码)public float getInterpolation02(float input) {return (float) (1.0f - (1.0f - input) * (1.0f - input));}interface FlingTaskCallback {void executeTask(int dy);void stopTask();}
}

4. Activity

package com.example.flingscrollview;import androidx.appcompat.app.AppCompatActivity;import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;public class MainActivity extends AppCompatActivity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/136507.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用Net2FTP轻松打造免费的Web文件管理器并公网远程访问

文章目录 1.前言2. Net2FTP网站搭建2.1. Net2FTP下载和安装2.2. Net2FTP网页测试 3. cpolar内网穿透3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 文件传输可以说是互联网最主要的应用之一&#xff0c;特别是智能设备的大面积使用&#xff0c;无论是个人…

“Python+高光谱遥感数据处理与机器学习教程

详情点击链接&#xff1a;“Python高光谱遥感数据处理与机器学习教程 第一&#xff1a;高光谱 一&#xff1a;高光谱遥感 01)高光谱遥感 02)光的波长 03)光谱分辨率 04)高光谱遥感的历史和发展 二&#xff1a;高光谱传感器与数据获取 01)高光谱遥感成像原理与传感器 02…

ubuntu 20.04 server安装

ubuntu 20.04 server安装 ubuntu-20.04.6-live-server-amd64.iso 安装 安装ubuntu20.04 TLS系统后&#xff0c;开机卡在“A start job is running for wait for network to be Configured”等待连接两分多钟。 cd /etc/systemd/system/network-online.target.wants/在[Servi…

Node Sass version 9.0.0 is incompatible with ^4.0.0.

1.错误产生原因&#xff1a; node、 node-sass 和sass-loader的版本对应问题 2.解决方案&#xff1a; 删除之前的 npm uninstall node-sass sass-loader 安装指定的 npm i node-sass4.14.1 sass-loader7.3.1 --save -dev

排序算法之-冒泡

顺序排序算法原理 从头开始遍历未排序数列&#xff0c;遍历时比较相邻的两个元素&#xff0c;前面的大于后面的&#xff0c;则双方交换位置&#xff0c;一直比较到末尾&#xff0c;这样最大的元素会出现在末尾&#xff0c;接着再依次从头开始遍历剩余未排序的元素&#xff0c;…

[架构之路-246]:目标系统 - 设计方法 - 软件工程 - 需求工程- 需求开发:获取、分析、定义、验证

目录 前言&#xff1a; 架构师为什么需要了解需求分析 一、需求工程概述 1.1 概述 1.2 需求工程的两大部分 &#xff08;1&#xff09;需求开发&#xff1a;系统工程师的职责、目标系统开发角度 &#xff08;2&#xff09;需求管理&#xff1a;项目管理者的职责、项目管…

微服务注册中心之安装+实例搭建zookeeper

1.下载安装包并上传到Linux服务器 Apache ZooKeeper 可以使用wget或者curl命令 wget http://mirror.bit.edu.cn/apache/zookeeper/zookeeper-3.7.1/apache-zookeeper-3.7.1-bin.tar.gz连接失败也可以本地下载之后上传到服务器 scp /本地/文件的/路径 用户名远程服务器IP或主…

CAN总线协议的理解以及移植stm32代码并使用

什么是CAN总线协议 是一种异步半双工的通讯协议&#xff0c;只有CAN_High与CAN_Low两条信号线。 有两种连接形式&#xff1a;闭环总线&#xff08;高速&#xff09;和开环总线&#xff08;远距离&#xff09; 他使用的是一种差分信号来传输电信号 所谓差分信号就是两条信号线…

接口--抽象方法

回答问题&#xff1a; 1.接口是什么&#xff1f; 2.接口中可以包含什么内容&#xff1f; 3.如何定义接口格式&#xff1f; 4.接口定义抽象方法格式&#xff1f; Code //接口是公共规范标准&#xff0c;类似于“模具” //如何定义接口格式&#xff1f;/** public interface 接…

CSS 对齐、组合选择符、伪类、伪元素、导航栏

一、CSS 对齐&#xff1a; 1&#xff09;、元素居中对齐&#xff1a; 水平居中对齐一个元素&#xff0c;可以使用margin&#xff1a;auto&#xff0c;设置到元素的宽度将防止它溢出到容器的边缘。元素通过指定宽度&#xff0c;并将两边的空外边距平均分配。示例&#xff1a; …

【前端】Jquery UI +PHP 实现表格拖动排序

目的&#xff1a;使用jquery ui库实现对表格拖拽排序&#xff0c;并且把排序保存到数据库中 效果如下 一、准备工作&#xff1a; 1、下载jquery ui库&#xff0c;可以直接引用线上路径 <link rel"stylesheet" href"https://code.jquery.com/ui/1.12.1/them…

IS-LM模型:从失衡到均衡的模拟

IS-LM模型&#xff1a;从失衡到均衡的模拟 文章目录 IS-LM模型&#xff1a;从失衡到均衡的模拟[toc] 1 I S − L M 1 IS-LM 1IS−LM模型2 数值模拟2.1 长期均衡解2.2 政府部门引入2.3 价格水平影响2.4 随机扰动因素 1 I S − L M 1 IS-LM 1IS−LM模型 I S − L M IS-LM IS−LM是…

如何在CPU上进行高效大语言模型推理

大语言模型&#xff08;LLMs&#xff09;已经在广泛的任务中展示出了令人瞩目的表现和巨大的发展潜力。然而&#xff0c;由于这些模型的参数量异常庞大&#xff0c;使得它们的部署变得相当具有挑战性&#xff0c;这不仅需要有足够大的内存空间&#xff0c;还需要有高速的内存传…

无需标注海量数据,目标检测新范式OVD

当前大火的多模态GPT-4在视觉能力上只具备目标识别的能力&#xff0c;还无法完成更高难度的目标检测任务。而识别出图像或视频中物体的类别、位置和大小信息&#xff0c;是现实生产中众多人工智能应用的关键&#xff0c;例如自动驾驶中的行人车辆识别、安防监控应用中的人脸锁定…

智慧工地源码 手册文档 app 数据大屏、硬件对接、萤石云

智慧工地解决方案依托计算机技术、物联网、云计算、大数据、人工智能、VR、AR等技术相结合&#xff0c;为工程项目管理提供先进技术手段&#xff0c;构建工地现场智能监控和控制体系&#xff0c;弥补传统方法在监管中的缺陷&#xff0c;最终实现项目对人、机、料、法、环的全方…

ZZ308 物联网应用与服务赛题第E套

2023年全国职业院校技能大赛 中职组 物联网应用与服务 任 务 书 &#xff08;E卷&#xff09; 赛位号&#xff1a;______________ 竞赛须知 一、注意事项 1.检查硬件设备、电脑设备是否正常。检查竞赛所需的各项设备、软件和竞赛材料等&#xff1b; 2.竞赛任务中所使用的…

文件包含漏洞培训

CTF介绍 MISC(Miscellaneous)类型,即安全杂项,题目或涉及流量分析、电子取证、人肉搜索、数据分析等等。CRYPTO(Cryptography)类型,即密码学,题目考察各种加解密技术,包括古典加密技术、现代加密技术甚至出题者自创加密技术。PWN类型,PWN在黑客俚语中代表着攻破、取得权限…

21 移动网络的前世今生

1、移动网络的发展历程 发展过程就是&#xff1a;2G,3G,4G,5G的过程&#xff0c;用2G看txt&#xff0c;用3G看jpg&#xff0c;用4G看avi。 2、2G网络 手机本来是用来打电话的&#xff0c;不是用来上网的&#xff0c;所以原来在2G时代&#xff0c;上网使用的不是IP网络&#…

关于视频封装格式和视频编码格式的简介

文章目录 简介视频封装格式&#xff08;Video Container Format&#xff09;视频编码格式&#xff08;Video Compression Format&#xff09;两者关系总结webm 格式简介webm视频编码格式webm音频编码格式webm总结 简介 视频封装格式&#xff08;Video Container Format&#x…

sql学习

因为之前sql学的太烂了&#xff0c;想整理一下 一.什么是 SQL&#xff1f; SQL 是用于访问和处理数据库的标准的计算机语言。 SQL 指结构化查询语言SQL 使我们有能力访问数据库SQL 是一种 标准计算机语言 二.SQL 能做什么&#xff1f; SQL 面向数据库执行查询SQL 可从数据库…