【Android】View的解析—滑动篇

news2024/11/27 22:51:26

1.View与ViewGroup

在这里插入图片描述

  1. View
    • View是Android中所有UI组件的基类,提供了绘制(draw)、布局(layout)和事件处理(event handling)的基础功能。
    • 它是一个抽象类,不能直接实例化,但可以通过其子类来创建具体的UI组件,如TextViewButtonImageView等。
  2. ViewGroup
    • ViewGroup是一个容器类,继承自View,可以包含其他View对象(即子视图)。
    • 它负责管理子视图的布局和事件分发。
    • ViewGroup本身也是一个View,这意味着它可以包含在另一个ViewGroup中,从而构建复杂的UI层次结构。
  3. 关系
    • 继承关系:ViewGroup继承自View,这意味着ViewGroup拥有View的所有属性和方法,并且可以包含子视图。
    • 组合关系:ViewGroup通过组合多个View对象来构建复杂的UI界面。每个ViewGroup可以包含一个或多个子View,这些子View可以是View的任何子类,包括其他ViewGroup
    • 布局管理:ViewGroup负责其子视图的布局管理,包括确定子视图的位置和大小。不同的ViewGroup子类(如LinearLayoutRelativeLayout等)提供了不同的布局策略。
  4. 常见的ViewGroup子类
    • LinearLayout:线性布局,子视图按垂直或水平方向排列。
    • RelativeLayout:相对布局,子视图可以相对于彼此或父视图进行定位。
    • FrameLayout:帧布局,用于叠加多个子视图,通常用于覆盖显示。
    • TableLayout:表格布局,子视图按照表格的行和列排列。
    • ConstraintLayout:约束布局,提供了更灵活的布局方式,允许通过约束来定义视图之间的关系。

2.坐标系

Android系统当中有两种坐标系,分别为Android坐标与View坐标

2.1Android坐标系

在Android当中我们将左上角的顶点作为Android坐标系的原点,向右为X轴正方向,向下为Y轴正方向,在getRawX方法和getRawY方法获得的也是Android坐标系的坐标

2.2View坐标系

在这里插入图片描述

View获取自身的宽和高

View自身就有getWidth()getHeight()方法,方法本质是什么:
在这里插入图片描述

View自身的坐标

通过如下方法可以获得View到其父控件ViewGroup的距离:
getTop():获取View自身顶边到其父布局顶边的距离

getLeft():获取View自身左边到其父布局左边的距离

getRight():获取View自身右边到其父布局左边的距离

getBottom():获取View自身底边到其父布局顶边的距离

MotionEvent提供的方法

getX():获取点击事件距离控件左边的距离,即视图坐标

getY():获取点击事件距离控件顶边的距离,即视图坐标

getRawX():获取点击事件距离整个屏幕左边的距离,即绝对坐标

getRawY():获取点击事件距离整个屏幕顶边的距离,即绝对坐标

2.3View的滑动

  1. layout方法

此时我们需要自定义一个View并重写它的onTouchEvent 方法

  • 这是 View 类的一个回调方法,用于处理触摸事件。当用户触摸屏幕时,系统会调用这个方法
  • 方法的参数 MotionEvent event 包含了触摸事件的详细信息,如触摸点的坐标、触摸动作等
@Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            //监控的是你对控件的触碰,当你触摸控件就会进入此循环,此时会记下控件的坐标
            case MotionEvent.ACTION_DOWN:
                lastX = x;
                lastY = y;
                break;
            //当你的手指进行移动的时候,让当前的位置减去上一次的位置
            case MotionEvent.ACTION_MOVE:
                int offsetX = x - lastX;
                int offsetY = y - lastY;
                layout(getLeft() + offsetX,getTop() + offsetY,
                        getRight() + offsetX,getBottom() + offsetY);
                break;
        }
        return true;
    }

其实这个方法就是将之前的View的坐标掌握之后根据距离算出此时控件的所在位置。

  1. offsetLeftAndRight()offsetTopAndBottom()

其实根据这个方法的名字你就会想到,将上面的MOVE里面的方法替换掉即可,没错就是这样的:

offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
  1. LayoutParams(改变布局参数)

LayoutParams主要保存了一个View的布局参数,因此我们可以根据改变布局参数来改变它相应的位置,整体思路还是一样的将手指移动的变量传给布局参数,还是对上面的方法进行修改:

ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);

此处使用的约束布局因此getTop是距你所设的Top控件的距离

  1. 动画

我们将动画XML文件放在anim当中

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">

    <translate
        android:fromXDelta="0"
        android:toXDelta="300"/>

</set>
  • <translate>:这是XML动画框架中的一个元素,用于创建平移动画。
  • android:fromXDelta="0":这个属性定义了动画的起始位置。fromXDelta表示动画开始时视图相对于其原始位置的X轴偏移量。值0意味着动画开始时,视图不从其原始位置偏移。
  • android:toXDelta="300":这个属性定义了动画的结束位置。toXDelta表示动画结束时视图相对于其原始位置的X轴偏移量。值300意味着动画结束时,视图将向右(正X方向)移动300像素。

在活动当中只需要一句slideView.setAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));即可,此时运行程序你会发现并没有发生变化,难道没有进行移动吗,其实移动了但是快速的回到了原位,速度太快你并没有看到,当我们想要它停留在所移动的位置只需要在XML文件当中加上android:fillAfter="true"即可,他表示当动画结束是否使其停留在最后的位置。

但是当我们此时按下控件进行移动发现并没有用,控件没有随手指的移动变化而变化,但是当我们按在控件原本的位置进行移动发现控件跟着移动了,这是因为View的动画并不能改变View的位置参数。属性动画就解决了上述的问题,会在下面讲解。

  1. scrollTo与scrollBy

scrollTo(x, y)根据方法我们就可以知道这是将控件移动到具体的一个坐标点,而scrollBy(dx, dy)则表示移动的增量。

在这里插入图片描述
在这里插入图片描述

我们看到scrollto最终还是调用scrollBy方法

移动的都是View的内容,如果在ViewGroup当中使用,则是移动所有的子View。此时仍然改变MOVE里面的代码:((View)getParent()).scrollBy(-offsetX, -offsetY);,此时就随着我们手指的移动进行移动了。

我们先得到了父布局,此时就是对应控件的上一个布局,此时按下控件发现页面所有的内容都跟着移动了。但是你会想到为什么是负的,此时你的布局就像是一个报纸,而手机就像是一个放大镜,当我们移动的时候相当于移动的是放大镜的位置,当我们想要将内容移动到手机的右下角,就是将手机向左上角移动。这样应该就理解了吧。

  1. Scroller

在使用上面的方法的时候我们看到是瞬时完成的,程序一打开就是图标移动后的位置,那我们的体验感就会很不好,Scroller就解决了上述的问题,它的滑动是在一段时间间隔当中完成的。她本身并不能移动,需要与View的computeScroll()方法配合才能达到相应的效果。

接下来就重写方法吧,这是一个用于计算当前滚动的位置和是否需要继续滚动的方法:

@Override
public void computeScroll() {
    super.computeScroll();
    if(mScroller.computeScrollOffset()){
        ((View)getParent()).scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
        invalidate();
    }
}
  • if(mScroller.computeScrollOffset()) 检查 mScroller(一个 Scroller 对象)是否计算出了新的滚动偏移量。computeScrollOffset() 方法返回 true 表示还有剩余的滚动操作需要执行,false 表示滚动已经完成。
  • 如果computeScrollOffset()返回true,则执行滚动操作。
    • getParent() 获取当前 View 的父 View
    • (View) 将父 View 强制类型转换为 View 类型,这是为了确保能够调用 scrollTo() 方法。
    • scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 方法将父 View 滚动到 Scroller 计算出的当前 X 和 Y 坐标位置。
  • invalidate() 方法请求系统重绘当前 View。这是必要的,因为滚动操作改变了 View 的显示状态,需要通过重绘来更新显示内容。

我们再写一个根据接收的参数进行滑动的方法:

public void smoothScrollTo(int destX, int destY) {
    int scrollX = getScrollX();
    int delta = destX - scrollX;
    //2000即为完成滑动的时间限制
    mScroller.startScroll(scrollX, 0, delta, 0, 2000);
    invalidate();
}

此时我们只需要在活动当中调用此方法就可以实现滑动,此时运行程序就看到布局不是直接到了所要滑动到的位置,你会看到一段滑动过程。到底是如何实现的,就做做分析吧,实际是第一次自己没有理解o((>ω< ))o:我们在使用的时候调用的就是我们所写的这个方法,此时我们所传进去的就是所要移动的位置,到了所写的方法之后就会记录下没滑动之前的位置,在此时我们只考虑了水平滑动(竖直滑动也是一样的逻辑),delta即为我们滑动到相应的位置所需滑动多少的距离,调用其开始滑动的方法,此方法传入的就是刚开始的坐标与所需滚动的距离以及完成滚动的时间,好吧还是没有完全弄懂,嗯嗯…看看下面的源码解析吧,看了源码就会理解了!!!

5.Scroller源码

对于构造方法就不多说了,但是要告诉大家一个新的概念插值器(Interpolator),如若不传就会使用默认的插值器(ViscousFluidInterpolator

ViscousFluidInterpolator 是 Android 中 Scroller 类的一个默认插值器,它用于控制滚动动画的速度变化,使得滚动行为更加自然和流畅。ViscousFluidInterpolator 的速度变化类似于粘性流体,开始时滚动较快,然后逐渐减速,直到停止。这种插值器模拟了一种“粘性”或“阻尼”效果,使得滚动看起来更加自然。

插值器(Interpolator)是 Android 中用于控制动画或滚动行为中时间与变化量之间关系的类。它定义了动画或滚动过程中速度变化的规律,使得动画不仅仅是简单的线性变化,而是可以模拟各种物理运动,如加速、减速、弹跳等效果。

插值器的主要作用是:

  1. 控制动画速度:定义动画在不同阶段的快慢变化,比如开始时慢,中间快,结束时慢。
  2. 模拟物理效果:模拟现实中的物理运动,如弹簧的振动、摩擦力等。
  3. 增强用户体验:通过更自然的动画效果提升用户界面的交互体验。

常见的插值器

Android 提供了多种内置的插值器,常用的包括:

  1. LinearInterpolator:线性插值器,动画以恒定速度进行,没有加速或减速。
  2. AccelerateInterpolator:加速插值器,动画开始时慢,然后逐渐加速。
  3. DecelerateInterpolator:减速插值器,动画开始时快,然后逐渐减速。
  4. AccelerateDecelerateInterpolator:先加速后减速插值器,动画开始和结束时慢,中间快。
  5. AnticipateInterpolator:预期插值器,动画开始时向后“弹”一下,然后向前加速。
  6. OvershootInterpolator:超出插值器,动画结束时超出目标位置,然后回弹到目标位置。
  7. BounceInterpolator:弹跳插值器,动画结束时模拟弹跳效果。
  8. FastOutLinearInInterpolatorFastOutSlowInInterpolator:快速退出线性进入插值器和快速退出慢速进入插值器,常用于 Material Design 中的动画。

此处可以对照着上面的View的滚动去看,我们先使用了startScroll方法,看名字你是不是觉得他就执行了View的滚动,其实不然,看看源码他都做了什么吧:

在这里插入图片描述

看到源码并没有执行相关的滑动操作,而是进行了保存参数的作用,因此此方法只是进行了准备工作并没有进行滑动操作。那是如何进行滑动操作的呢?关键就在于我们后面调用的invalidate()方法,此方法会导致View的重绘,View的重绘会调用View的draw方法,draw会调用我们所重写的computeScroll()方法。

我们先获取了当前的scrollX和scrollY,然后调用scrollTo方法进行滑动,接着再让他进行重绘直到滑动到了所要滑动的位置,即无需进行滑动了。明明使用的是scrollTo方法为什么还会有滑动的过程呢?那就看看computeScrollOffset()方法:

public boolean computeScrollOffset() {
    if (mFinished) {
        return false;
    }

    int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);

    if (timePassed < mDuration) {
        switch (mMode) {
        case SCROLL_MODE:
            final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
            mCurrX = mStartX + Math.round(x * mDeltaX);
            mCurrY = mStartY + Math.round(x * mDeltaY);
            break;
        case FLING_MODE:
            final float t = (float) timePassed / mDuration;
            final int index = (int) (NB_SAMPLES * t);
            float distanceCoef = 1.f;
            float velocityCoef = 0.f;
            if (index < NB_SAMPLES) {
                final float t_inf = (float) index / NB_SAMPLES;
                final float t_sup = (float) (index + 1) / NB_SAMPLES;
                final float d_inf = SPLINE_POSITION[index];
                final float d_sup = SPLINE_POSITION[index + 1];
                velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);
                distanceCoef = d_inf + (t - t_inf) * velocityCoef;
            }

            mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;
            
            mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));
            // Pin to mMinX <= mCurrX <= mMaxX
            mCurrX = Math.min(mCurrX, mMaxX);
            mCurrX = Math.max(mCurrX, mMinX);
            
            mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));
            // Pin to mMinY <= mCurrY <= mMaxY
            mCurrY = Math.min(mCurrY, mMaxY);
            mCurrY = Math.max(mCurrY, mMinY);

            if (mCurrX == mFinalX && mCurrY == mFinalY) {
                mFinished = true;
            }

            break;
        }
    }
    else {
        mCurrX = mFinalX;
        mCurrY = mFinalY;
        mFinished = true;
    }
    return true;
}

我们看到当滑动结束的时候即无需进行任何方向的滑动,就会返回false,不再进行滑动。否则系统会算出一个动画持续的时间,如果动画持续的时间小于我们设置的滑动持续时间(mDuration),就会执行switch语句,然后根据插值器来计算在该时间段内所移动的距离,将其赋值给mCurrXmCurrY。就这样不断递归完成了我们的弹性滑动。

文章到这里就结束了!

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

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

相关文章

极狐GitLab 17.6 正式发布几十项与 DevSecOps 相关的功能【三】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

基于混合ABC和A*算法复现

基于混合ABC和A*算法复现 一、背景介绍二、算法原理&#xff08;一&#xff09;A*算法原理&#xff08;二&#xff09;人工蜂群算法原理&#xff08;三&#xff09;混合ABC和A*算法策略 三、代码实现&#xff08;一&#xff09;数据准备&#xff08;二&#xff09;关键函数实现…

linux运行vue编译后的项目

如果你的 Vue 项目使用了 history 模式&#xff08;而非默认的 hash 模式&#xff09;&#xff0c;在纯静态服务器中会出现类似的问题。因为 Vue Router 的 history 模式要求所有未匹配的路径都重定向到 index.html&#xff0c;以便 Vue 前端处理路径。 首先在本地执行npm run…

模拟实现Bash

模拟实现Bash 1.Bash基本认识2.Bash实现3.添加细节4.内置命令5.完整代码 &#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f; &#x1f680;&#x1f680;系列专栏&#xff1a;【Linux的学习】 &#x1f4dd;&#x1f4dd;本篇内容…

sql注入报错分享(mssql+mysql)

mysql mysql的报错内容比较多 网上也有比较多的 这里重复的就不多介绍了。一笔带过 溢出类 bigint 当超过mysql的整形的时候&#xff0c;就会导致溢出&#xff0c;mysql可能会将错误信息带出。这里user()是字母默认为0 取反以后1可能就会导致异常。 报错特征 BIGINT UNSIG…

Hadoop3.3.6集群安装

Hadoop3.3.6 三节点集群安装 准备工作 准备三台机器&#xff0c;大小为4c8g&#xff0c;主节点为 8c16g。并需要保证网络连通性&#xff0c;每台机器都相互ping一下 1、关闭网络防火墙 # 查看网络防火墙状态 sudo systemctl status firewalld # 立即停止 firewalld sudo sy…

如何制作项目网页

一、背景 许多论文里经常会有这样一句话Supplementary material can be found at https://hri-eu.github.io/Lami/&#xff0c;这个就是将论文中的内容或者补充视频放到一个网页上&#xff0c;以更好的展示他们的工作。因此&#xff0c;这里介绍下如何使用前人提供的模板制作我…

JVM调优篇之JVM基础入门AND字节码文件解读

目录 Java程序编译class文件内容常量池附录-访问标识表附录-常量池类型列表 Java程序编译 Java文件通过编译成class文件后&#xff0c;通过JVM虚拟机解释字节码文件转为操作系统执行的二进制码运行。 规范 Java虚拟机有自己的一套规范&#xff0c;遵循这套规范&#xff0c;任…

已存大量数据的mysql库实现主从各种报错----解决方案(看评论)

背景何谓“先死后生”本文使用技术1、实施流程图2、实施2.1、数据库备份2.2、搭建Mysql的Master-Slave2.2.1、准备工作2.2.2、开始部署2.2.3、账号配置2.2.4、slave 同步配置2.2.5、验证 2.3、Master做数据恢复 结语 背景 计划对已有大量数据的mysql库的主从搭建&#xff0c;使…

数据结构 【双向哨兵位循环链表】

链表的结构分为8中&#xff0c;其实搞懂了单链表和双向哨兵位循环链表&#xff0c;这部分的知识也就掌握的差不多了。双向哨兵位循环链表的结构如下&#xff1a; 下面我从0构建一个双向哨兵位循环链表。 1、准备工作 构建节点结构体&#xff0c;双向循环链表的每一个…

高级AI记录笔记(五)

学习位置 B站位置&#xff1a;红豆丨泥 UE AI 教程原作者Youtube位置&#xff1a;https://youtu.be/-t3PbGRazKg?siRVoaBr4476k88gct素材自备 改良近战AI格挡行为 把近战AI的格挡行为从行为树中单独一个任务分块中给删除掉&#xff0c;因为我们希望敌人在受到伤害后立即进行…

彻底解决 macOS 下Matplotlib 中文显示乱码问题

彻底解决 macOS 下Matplotlib 中文显示乱码问题 在使用 Python 的 Matplotlib 库进行数据可视化时&#xff0c;中文字符的显示常常会出现乱码问题&#xff0c;尤其在 macOS 系统上。在网上找了一大堆方法&#xff0c;花了很久&#xff0c;发现不是要安装各种字体就是要改配置&…

11.25.2024刷华为OD

文章目录 HJ76 尼科彻斯定理&#xff08;观察题&#xff0c;不难&#xff09;HJ77 火车进站&#xff08;DFS&#xff09;HJ91 走格子方法&#xff0c;&#xff08;动态规划&#xff0c;递归&#xff0c;有代表性&#xff09;HJ93 数组分组&#xff08;递归&#xff09;语法知识…

突破性算法:让无人机集群在狭窄空间内穿针引线

导读 在建筑救援、森林搜索等任务中&#xff0c;无人机集群经常会遇到狭窄空间限制和动态障碍物变化等挑战。这些挑战会导致集群内部冲突&#xff0c;或在执行任务时因避让动态障碍物而导致系统混乱。实际应用场景和任务的严格特征往往使得全局搜索难以优化&#xff0c;而局部避…

Python中的简单爬虫

文章目录 一. 基于FastAPI之Web站点开发1. 基于FastAPI搭建Web服务器2. Web服务器和浏览器的通讯流程3. 浏览器访问Web服务器的通讯流程4. 加载图片资源代码 二. 基于Web请求的FastAPI通用配置1. 目前Web服务器存在问题2. 基于Web请求的FastAPI通用配置 三. Python爬虫介绍1. 什…

【Shell】运维快捷键及shell各种不同实际运维场景

一&#xff0c;控制台使用技巧 1&#xff0c;操作快捷键 Ctrlr :可以快速查找历史命令 Ctrll :可以清理控制台屏幕 Ctrla \ Ctrle :移动光标到命令行首\行尾 Ctrlw \ Ctrlk :删除光标之前\之后的内容 2&#xff0c;VIM文件编辑快捷键 快捷键ZZ :文件保存并退出 3&#xff…

SlickGrid复选框

分析 1、先在columns首列添加复选框&#xff1b; 2、在SlickGrid注册刚添加的复选框&#xff1b; 3、添加复选框变化事件&#xff1b; 4、注册按钮点击事件&#xff0c;点击获取已选中的行。 展示 代码 复选框样式&#xff08;CSS&#xff09; .slick-cell-checkboxsel {bac…

基于单片机的智慧小区人脸识别门禁系统

本设计基于单片机的智慧小区人脸识别门禁系统。由STM32F103C8T6单片机核心板、显示模块、摄像头模块、舵机模块、按键模块和电源模块组成。可以通过摄像头模块对进入人员人脸数据进行采集&#xff0c;识别成功后&#xff0c;舵机模块动作&#xff0c;模拟门禁打开&#xff0c;门…

【小白学机器学习33】 大数定律python的 pandas.Dataframe 和 pandas.Series基础内容

目录 0 总结 0.1pd.Dataframe有一个比较麻烦琐碎的地方&#xff0c;就是引号 和括号 0.2 pd.Dataframe关于括号的原则 0.3 分清楚几个数据类型和对应的方法的范围 0.4 几个数据结构的构造关系 list → np.array(list) → pd.Series(np.array)/pd.Dataframe 1 python 里…

Edge浏览器保留数据,无损降级退回老版本+禁止更新教程(适用于Chrome)

3 个月前阿虚就已经写文章告警过大家&#xff0c;Chromium 内核的浏览器将在 127 以上版本开始限制仍在使用 Manifest V2 规范的扩展&#xff1a;https://mp.weixin.qq.com/s/v1gINxg5vMh86kdOOmqc6A 像是 IDM、油猴脚本管理器、uBblock 等扩展都会受到影响&#xff0c;后续将无…