View基础知识-位置大小和滑动

news2024/11/14 21:43:44

前言

这篇文章可以作为基础看看,但是有时候基础就是细节,不一定所有人都记得,所以基础也要记录一下。都熟悉的话也可以看看其他系列文章:

View事件分发机制(源码分析篇)

Android一步一步追踪View的工作原理

View是Android中所有控件的基类,是一种界面层的控件的一种抽象,它代表了一个控件。除了View,还有ViewGroup,其内部可以包含多个控件,即一组View。在Android的设计中,ViewGroup也继承了View,这就意味着View本身就可以是单个控件也可以是由多个控件组成的一组控件,通过这种关系形成了View树的结构。

View的位置参数

View的位置参数主要由它的四个顶点来决定,分别对应View的四个属性:top、left、right、bottom,其中top是左上角的纵坐标,left是左上角的横坐标,right是右下角的横坐标,bottom是右下角的纵坐标。需要注意的是,这些坐标都是相对View的父容器来说的,因此它是一种相对坐标。另外在Android中,x轴和y轴的正方向分别是右和下。

根据View的位置参数可以得到View的宽高和坐标的关系如下:

width = right - left;
height = bottom - top;

在View的源码中View的四个位置参数对应了mLeft、mRight、mTop和mBottom这四个成员变量,View中提供了对应的getXX()方法来获取对应的位置参数。

在Android3.0开始,View增加了额外的几个参数:x、y、translationX和translationY,其中x和y是View左上角的坐标,而translationX和translationY是View左上角相对于父容器的偏移量。这几个参数也是相对于父容器的坐标,并且translationX和translationY的默认值为0,View同样提供了get/set方法。

几个参数之间的关系如下:

x = left + translationX;
y = top + translationY;

需要注意的是,View在平移的过程中,top和left表示的是原始左上角的位置信息,其值并不会发生改变,此时发生改变的是x、y、translationX和translationY这四个参数。

MotionEvent和TouchSlop

MotionEvent

在手指接触屏幕后所产生的一系列事件中,典型的事件有如下几种:

  • ACTION_DOWN——手指刚接触屏幕;
  • ACTION_MOVE——手指在屏幕上滑动;
  • ACTION_UP——手指从屏幕上松开的瞬间。

正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件,例如点击屏幕滑动一会再松开,事件序列为DWON->MOVE->…->MOVE->UP。

上述的三种情况是典型的事件序列,同时通过MotionEvent对象可以得到点击事件发生的x和y坐标。系统提供了两组方法:getX/getY和getRawX/getRawY。getX/getY返回的是相对于当前View左上角的x和y坐标,而getRawX/getRawY返回的是相对于手机屏幕左上角的x和y坐标。

 TouchSlop

TouchSlop是系统所能识别出的被认为是滑动的最小距离,换句话说,当手指在屏幕上滑动时,当两次滑动之间的距离小于该值,则认为不是在进行滑动。这是一个常量,和设备有关,在不同设备上这个值可能是不同的,通过以下方式获取得到这个常量:

ViewConfiguration.get(context).getScaledTouchSlop()

通常可以利用该值对用户的滑动进行过滤。

VelocityTracker、GestureDetector和Scroller

VelocityTracker

VelocityTracker用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。使用的一个例子如下所示,在View的onTouchEvent方法中使用速度追踪。

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (velocityTracker == null) {
        velocityTracker = VelocityTracker.obtain();
    }
    velocityTracker.addMovement(event);
    switch(event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            // 速度追踪
            velocityTracker.computeCurrentVelocity(1000);
            int xVelocity = (int) velocityTracker.getXVelocity();
            int yVelocity = (int) velocityTracker.getYVelocity();
            Toast.makeText(mContext, "移动速度X方向:" + xVelocity + 
            "\nY方向:" + yVelocity, Toast.LENGTH_SHORT).show();
            break;            
    }
    return true;
}

需要注意的两点:

第一,获取速度之前必须先计算速度,即getXVelocity和getYVelocity这两个方法的前面必须调用computeCurrentVelocity方法;

第二,这里的速度是指一段时间内手指所滑过的像素数。注意,速度可以为负数,当手指从右向左滑动时,水平方向速度即为负值。

这里的终点位置是根据addMovement中添加进入的event来进行判断的,每个event都有一个对应的坐标,当调用computeCurrentVelocity方法进行计算速度时将在添加进去的event中获取最后一个evnet的坐标作为终点位置。

另外,当不需要使用速度跟踪器时,需要调用clear方法来重置并回收内存。

// 回收
velocityTracker.clear();
velocityTracker.recycle();

GestureDetector

手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。手势检测的使用并不复杂,首先创建一个GestureDetetor对象并实现OnGestureListener接口,根据需要可以实现OnDoubleTapListener接口从而监听双击行为。

// 创建并传入OnGestureListener接口的实现
GestureDetector gestureDetector = new GestureDetector(context, this);
// 解决长按屏幕后无法拖动的现象
gestureDetector.setIsLongpressEnabled(false);
// 设置双击行为监听接口
gestureDetector.setOnDoubleTapListener(this);

接着接管目标View的onTouchEvent方法,在待监听View的onTouchEvent方法中,添加如下实现。

boolean consume = gestureDetector.onTouchEvent(event);
return consume;

事实上就是将View的事件处理从onTouchEvent委托到GestureDetector中,将待监听的View的事件处理委托到GestureDetector中进行处理。OnGestureListener接口和OnDoubleTabListener接口中的方法介绍如下所示。

方法名描述所属接口
onDown手指轻轻触摸屏幕的一瞬间,由1个ACTION_DOWN触发onGestureListener
onShowPress手指轻轻触摸屏幕,尚未松开或拖动,由1个ACTION_DOWN触发onGestureListener
onSingleTapUp手指(轻轻触摸屏幕)松开,伴随着1个ACTION_UP而触发,这是单击行为onGestureListener
onScroll手指按下屏幕并拖动,由1个ACTION_DOWN,多个ACTION_MOVE触发,这是拖动行为onGestureListener
onLongPress用户长久按着屏幕不放,即长按onGestureListener
onFling用户按下触摸屏、快速滑动后松开,由1个ACTION_DOWN,多个ACTION_MOVE和1个ACTION_UP触发,这是快速滑动行为onGestureListener
onDoubleTap双击,由2次连续的单击组成,它不可能和onSingleTapConfirmed共存onDoubleTabListener
onSingleTabConfirmed严格的单击行为。如果触发了该行为,后面紧跟的另一个单击行为只可能是单击,而不可能是双击中的一次单击。onDoubleTabListener
onDoubleTabEvent表示发生了双击行为,在双击期间,ACTION_DOWN、ACTION_MOVE和ACTION_UP都会触发此回调。onDoubleTabListener

在实际开发中,可以不使用GestureDetector,完全可以在View的onTouchEvent方法中实现所需的监听。建议可以这样:如果只是监听滑动相关的,建议在onTouchEvent中实现,如果要监听双击行为,那么就使用GestureDetector。

View的滑动

使用scrollTo/scrollBy

为了实现View的滑动,View提供了专门的方法来实现这个功能,那就是scrollTo和scrollBy。根据源码,scrollBy实际上也是调用了scrollTo方法,它实现了基于当前位置的相对滑动,而scrollTo则实现了基于所传递参数的绝对滑动。

在滑动过程中View内部有两个属性mScrollX和mScrollY其改变规则:在滑动过程中,mScrollX的值总是等于View左边缘和View内容左边缘在水平方向的距离,而mScrollY的值总是等于View上边缘和View内容上边缘在垂直方向上的距离。

注意的一点是,mScrollX和mScrollY的单位是像素,并且当View左边缘在View内容左边缘右边时,mScrollX为正值;当View上边缘在View内容下边时,mScrollY为正值。

需要经过上述的描述,可以发现使用scrollTo和scrollBy来实现View的滑动,只能将View的内容进行移动,而不能将View本身进行移动。并且,scrollTo和scrollBy的滑动过程是瞬间完成的。

使用动画

使用动画来移动View,主要是操作View的translationX和translationY属性,既可以采用传统的补间动画,也可以采用属性动画,如果采用属性动画的话,为了能够兼容3.0以下版本,需要采用开源动画库nineoldandroids。如下为使用属性动画实现View滑动的方式。

// 使用属性动画实现View的滑动——平移
ObjectAnimator.ofFloat(view, "translationX", 0f, 100f)
    .setDuration(100)
    .start();

改变布局参数

View的第三种实现滑动的方式是改变布局参数,即改变LayoutParams。通过改变LayoutParams的方式去实现View的滑动是一种很灵活的方法,需要根据不同的情况去做不同的处理。实现方式为设置LayoutParams里的marginLeft和marginTop参数。

// 通过改变布局参数使得View滑动
ViewGroup.MarginLayoutParams layoutParams = 
    (ViewGroup.MarginLayoutParams) view.getLayoutParams();
layoutParams.leftMargin = x + (rawX - startX);
layoutParams.topMargin = y + (rawY - startY);
view.requestLayout();  // 或者 view.setLayoutParams(layoutParams);

各种滑动方式的对比

scrollTo/scrollBy方式:View提供的原生方法,操作简单并且不影响内部元素的单击事件,但是只能滑动View的内容,不能滑动View本身。适合对View内容的滑动。

动画:采用属性动画来滑动没有任何缺点,但是使用补间动画时其无法改变View本身的属性。操作简单,主要适用于没有交互的Veiw和实现复杂的动画效果。

改变布局方式:操作稍微复杂,适用于有交互的View。

View的弹性滑动

对于非弹性滑动,其无法实现渐近式滑动(弹性滑动),用户体验极差,比如scrollTo/scrollBy以及改变布局参数的方法,由于其瞬时性,因此属于非弹性滑动。实现弹性滑动的方式有很多,但是其共同的思想是:将一次大的滑动分成若干次小的滑动并在一个时间段内完成。弹性滑动的实现方式有:Scroller,动画以及延迟策略等。

使用Scroller

Scroller本身无法让View弹性滑动,它需要和View的computeScroll方法配合使用才能共同完成这个功能,使用方式如下。

Scroller mScroller = new Scroller(mContext);

// 缓慢滚动到指定位置
private void smoothScrollTo(int destX, int destY) {
    int scrollX = getScrollX();
    int deltaX = destX - scrollX;
    // 1000ms内滑向destX,效果就是慢慢滑动
    mScroller.startScroll(scrollx, 0, deltaX, 0, 1000);
    invalidate();
}

@Override
public void computeScroll() {
    if(mScroller.computeScrollOffset()) {
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        postInvalidate();
    }
}

调用startScroll方法后,Scroller内部其实什么都没有做,它只是保存了传递的参数。需要注意的是这里的滑动指的是View内容的滑动而非View本身位置的改变。使得View弹性滑动的是invalidate方法,该方法会导致View的重绘,在View中draw方法又会去调用computeScroll方法,而该方法通过调用Scroller的方法进行滑动,在通过调用postInvalidate方法进行二次重绘,以此类推。

而computeScrollOffset会根据时间的流逝来计算当前的scrollX和scrollY的值。

总结一下Scroller的工作原理:Scroller配合View的computerScroll方法完成弹性滑动效果,它不断地让View重绘,而每次重绘距滑动起始时间会有一个时间间隔,通过这个间隔Scroller就可以得出View当前的滑动位置,并通过scrollTo进滑动。

使用动画

动画本身就是一种渐近的过程,因此通过它来实现的滑动天然就具有弹性效果。

在上一个话题中已经介绍了动画的使用方式,相关文章也有很多,不再多做介绍。

使用延时策略

延时策略的核心思想是通过发送一系列延时消息从而达到一种渐进式的效果,具体来说可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。使用Handler实现的一种延时策略如下所示。

private static final int MESSAGE_SCROLL_TO = 0;
private int mCount = 0;
private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        switch(msg.what) {
            case MSSAGE_SCROLL_TO:
                mCount ++;
                if (mCount <= 30) {
                    float fraction = mCount / 30f;
                    int scrollX = (int) (fraction * 100);
                    mImageView.scrollTo(scrollX, 0);
                    mHandler.sentEmptyMessageDelayed(MESSAGE_SCROLL_TO, 33);     
                }
                break;
        }
    }
}

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

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

相关文章

【车辆动力】基于Matlab模拟停车动力学

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

Python 音频处理以及可视化 Amplitude,MFCC,Mel Spectrogram, librosa 库

利用python库 librosa库对于音频文件进行预处理&#xff0c;以及可视化操作。 1. Load Audio Data 导入音频 将音频文件&#xff08;这里使用苹果录音文件 .m4a 格式&#xff09;导入librosa&#xff0c;音频格式可以为其它&#xff08;甚至视频文件mp4也是可以的&#xff09…

【Docker】Dockerfile:常见保留字、使用案例

Dockerfile是用来构建Docker镜像的文本文件&#xff0c;是由一条条构建镜像所需的指令和参数构成的脚本。 官网&#xff1a;链接 构建三步骤 编写Dockerfile文件构建镜像&#xff1a;docker build用镜像运行容器实例&#xff1a;docker run 一、常用的保留字 二、使用案例 要…

Spring MVC统一异常处理的3种方式(附带实例)

在 Spring MVC 应用的开发中&#xff0c;不管是对底层数据库操作&#xff0c;还是业务层或控制层操作&#xff0c;都会不可避免地遇到各种可预知的、不可预知的异常需要处理。 如果每个过程都单独处理异常&#xff0c;那么系统的代码耦合度高&#xff0c;工作量大且不好统一&a…

html5期末大作业:基于html+css+javascript+jquery+bootstarp响应式图书电商HTML模板网上书店(25页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

STC 51单片机51——直流电机PWM

//开发板按钮K3和K4用于调速&#xff0c;直流电机接部件电机模块Vcc和O1 #include"reg52.h" #define u8 unsigned char #define u16 unsigned int sbit O1P1^0;//ULN2003 sbit K3P3^2; //减速 sbit K4P3^3; //加速 u8 Flag; u16 T, PWM, Temp;//T为控制周期…

Java项目:ssm赛事打分系统

作者主页&#xff1a;源码空间站2022 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文末获取源码 项目介绍 赛事评分系统&#xff0c;SSM框架。该项目分管理员、裁判、选手三个用户角色。 管理员主要功能&#xff1a; 首页、选手管理、裁判管理、赛事管…

【2022世界杯开源项目实战】使用docker部署world-cup-2022-cli-dashboard数据看板工具

【2022世界杯开源项目实战】使用docker部署world-cup-2022-cli-dashboard数据看板工具一、world-cup-2022-cli-dashboard介绍1.工具介绍2.数据看板的内容二、检查本地docker环境1.检查docker版本2.检查docker状态三、构建world-cup-2022-cli-dashboard的镜像1.下载world-cup-20…

如何用蓝牙实现无线定位(四)--远程定位显示

1. 待救援定位设备 按照下面的针脚使用杜邦线将待救援定位设备的主蓝牙、从蓝牙连接到主控板上&#xff0c;和本地显示时的连接针脚是一样的&#xff0c;但是由于不需要连接OLED&#xff0c;因此不需要堆叠Bigfish。 参考视频 烧录程序如下&#xff08;human.ino&#xff09;&a…

善网ESG周报(第三期)

ESG报告&#xff1a; 陆金所控股发布2021年ESG报告 以可持续商业模式创造社会价值 从11月28日发布的报告来看&#xff0c;其公司2021年累计帮助超310万小微企业主并开展170场环保公益活动和超610场金融科普活动。 金融界联合济安金信发布首份《京津冀ESG绿色标杆企业报告》&a…

Keras深度学习高级(四)

本篇涉及的内容 如何将模型的结构由层升级成图如何使用 Keras 的回调函数在训练过程中监控模型&#xff0c;并根据模型状态采取行动使用TensorBoard将模型可视化什么是批标准化、深度可分离卷积和残差连接为什么应该使用超参数优化和模型集成 第一部分 Keras的函数式API 一、…

Nexus私服 (一)

(一) Nexus-OSS私服介绍 平时用maven构建项目&#xff0c;pom会默认去maven仓库下载包&#xff0c;网速都比较慢。此时可以选择国内的镜像&#xff08;ex:阿里云仓库&#xff09;如果想要自己管理项目的包&#xff0c;亦或者你的开源项目有多个人维护的时候&#xff0c;就需要考…

Allegro如何添加泪滴操作指导

Allegro如何添加泪滴操作指导 Allegro支持添加泪滴操作,保证焊接的可靠性,还可以调整泪滴的大小和形状,类似下图 具体操作如下 以给下图的pin和孔加泪滴为例 首先设置参数,route-Gloss-Parameters 点击Fillet and Taper Trace前面的方框 勾选下方的参数,Max size的值…

自己编写程序publish出kitti数据集,可视化kitti数据集

目的:不使用kitti2bag,因为kitti2bag的格式都是固定的,如果将来自己要添加什么东西,这个就会变得非常麻烦,自己编写程序就会右很多的变化。 开始之前仍然要下载kitti数据集,下载方式参考Ubuntu1804里进行KITTI数据集可视化操作_FYY2LHH的博客-CSDN博客 1、先执行roscor…

一款.Net7前后端分离、跨平台的通用权限管理框架

今天给大家推荐一个开源项目&#xff0c;基于.NetCore开发的、多租户的、前后端分离的企业开发框架。 项目简介 这是一个前后端分离、跨平台的、基于RBAC的通用框架&#xff1b;支持多租户、任务调度、缓存、国际化&#xff0c;前端支持Vue2/3&#xff0c;支持分表分库。 框…

【图像分类损失】SimLoss:一个适合于年龄估计的分类损失

Roses are red, violets are blue, both are somehow similar, but the classifier has no clue. 论文题目&#xff1a;《SimLoss: Class Similarities in Cross Entropy》&#xff08;2020年&#xff09; 论文地址&#xff1a;https://arxiv.org/pdf/2003.03182v1.pdf 1.背景…

Hbuilder打包成APP流程,以及遇到的坑

1.打包项目 期间遇到的坑&#xff0c;提前说下&#xff0c;避免重复工作。 【因为很多网友说自己打包的APP是白屏&#xff0c;这是需要实名认证才能使用的工具&#xff0c;灰灰产还是用其他的工具吧】 我打包的安卓APP给大家欣赏一下&#xff1a; https://wwttl.lanzout.co…

Spring MVC使用SessionLocaleResolver实现用户自定义切换语言实例

在许多成熟的商业软件系统中可以让用户自由切换语言&#xff0c;而不是修改浏览器的语言设置。一旦用户选择了自己需要使用的语言环境&#xff0c;整个系统的语言环境将一直是这种语言环境。 Spring MVC 也可以允许用户自行选择程序语言。本章通过 Web 应用 springMVCDemo09 演…

Linux安装使用Minio

目录简介安装方式1(推荐)安装方式2使用简介 需要一个靠谱的文件管理系统&#xff0c;所以想到了minio。在此记录过程。使用树莓派搭建。Linux下载不同的包即可。 官网地址&#xff1a;https://www.minio.org.cn/ 官方下载地址&#xff1a;https://dl.min.io/server/minio/rele…

FFmpeg二次开发

本文主要讲解 FFmpeg 的二次开发&#xff0c;ffmpeg.exe 的命令行功能特别强大&#xff0c;很多需求都能直接用命令行实现&#xff0c;但是总有一些需求用 命令行实现不太好做。 而你实现那些特殊需求&#xff0c;通常需要把 ffmpeg.exe 里面的某部分代码抄过来&#xff0c;本…