Android 自定义view《一》知识体系之基础知识(从入门到巅峰)
阅读原文时间:2021年04月20日阅读:1

1.View和ViewGroup

2.MotionEvent和TouchSlop

2.1 MotionEvent

2.2 TouchSlop

3.手势(Gesture)

3.1 VelocityTracker

3.2 GestureDetector

3.2.1 概述

3.2.2 构造函数

3.2.3 重要方法详细介绍

4.View的坐标系

4.1静态坐标系

4.2 滑动坐标系

5.View的滑动

5.1 scrollTo与scrollBy

5.2 Scroller

5.3 layout

5.4 offsetLeftAndRight 和 offsetTopAndBottom

5.5 LayoutParams

5.6 其他方式

1.View和ViewGroup

Android应用的所有UI组件都是继承了View类,View组件非常类似于Swing编程的JPanel,它表示一个空白的矩形区域。View类还有一个很重要的子类:ViewGroup,但是ViewGroup通常作为其他组件的容器使用。Android的所有UI组件都是建立在View、ViewGroup基础之上的,Android采用了“组合器”模式来设计View和ViewGroup:ViewGroup是View的子类,因此ViewGroup也可以被当做View使用。对于一个Android应用的图形用户界面来说,ViewGroup作为容器来盛装其他组件,而ViewGroup里除了可以包含普通View组件之外,还可以再次包含ViewGroup组件。 

2.MotionEvent和TouchSlop

2.1 MotionEvent

当手指触摸屏幕是(View或ViewGroup派生的控件),将产生Touch事件。Touch事件的相关细节(触摸发生的位置、事件及怎么的触摸)被封装成MotionEvent对象。 

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

事件类型 具体动作

MotionEvent.ACTION_DOWN 手指刚接触屏幕

MotionEvent.ACTION_UP 手指从屏幕上松开的一瞬间

MotionEvent.ACTION_MOVE 手指在屏幕上移动

MotionEvent.ACTION_CANCEL 动作取消(非人为)

正常情况下,一次手指触摸屏幕的行为会触发一系列点击事件,一般会有两种情况: 

(1)点击屏幕后离开松开,事件序列为 DOWN -> UP; 

(2)点击屏幕滑动一会再松开,事件序列为 DOWN -> MOVE ->….-> MOVE -> UP; 

在上述典型的三种Tocuh事件,我们可以通过TotionEvent对象得到点击事件发生的x和y的坐标。为此,系统提供了两组方法:getX/getY和getRawX/getRawY。区别如下: 

(1)getX/getY:返回的是相对于当前View左上角的 x 和 y 坐标 

(2)getRawX/getRawY : 返回的是相对于手机屏幕左上角的 x 和 y 坐标

2.2 TouchSlop

TouchSlop是系统所能识别出的被认为是滑动的最小距离,即当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。原因是:滑动的距离太短,系统不认为它不是滑动。这是个常量,和设备有关,在不同的设备上这个值是不同的,可通过:ViewConfiguration.get(getContext()).getScaledTouchSlop()来获取这个常量。 

这个常量的意义在于:当我们在处理滑动时,可以利用这个常量来做一些过滤,比如当两次滑动事件的滑动距离小于这个值,我们就可以认为未达到滑动距离的临界值,因此就可以认为它们不是滑动,这样做可以有更好的用户体验。

3.手势(Gesture)

所谓手势,其实是指用户手指在触摸屏上的连续碰撞行为,比如在屏幕上从左至右划出的一个动作,就是手势;在比如在屏幕上画出一个圆圈也是手势。手势这种连续的碰撞会形成某个方向上的移动趋势,也会形成一个不规则的几何图形。Android 对于这两种手势行为都提供了支持。 

对于第一种手势行为,Android 提供了手势检测,并为手势检测提供了相应的监听器。 

对于第二种手势行为,Android 允许开发者添加手势,并提供相应的API识别用户手势。 

下面,来介绍手势相关的重要的类。

3.1 VelocityTracker

速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度,过程也很简单。首先,在 View 的onTouchEvent 方法中追踪当前点击事件的速度。

        VelocityTracker tracker = VelocityTracker.obtain();

        tracker.addMovement(event);

1

2

接着,当我们想知道当前的滑动速度时,这个时候可以采用如下方式来获取当前的速度:

        tracker.computeCurrentVelocity(1000);//时间间隔的单位是:毫秒(ms)

        float xVelocity = tracker.getXVelocity();

        float yVelocity = tracker.getYVelocity();

1

2

3

需要注意的是: 

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

(2)这里的速度是指一段时间内手指所滑动过的像素。 

比如讲时间间隔设为 1000ms 时,在 1s 内,手指在水平方向从左向右滑动 100 像素,那么水平速度就是 100。注意速度可以是负数,当手指从右向左滑动时,水平方向速度即为负值。速度的计算公式可以表示如下:

速度 = (终点位置 - 起点位置) / 时间段

3.2 GestureDetector

GestureDetector 这个类对外提供了3个接口和一个静态内部类,通过一个表格简单介绍这4个监听器:

监听器 简介

OnGestureListener 手势检测,主要有事件类型为:按下(Down)、快速滑动(Fling)、长按(LongPress)、滚动(Scroll)、触摸反馈(ShowPress)和单击抬起(SingleTapUp)

OnDoubleTapListener 双击检测,有三个回调类型:双击(DoubleTap)、单击确认(SingleTapConfirmed)和双击事件回调(DoubleTapEvent)

OnContextClickListener 这是Android6.0(23)才添加的,用于检测外部设备上的按钮是否按下,一般情况下可忽略

SimpleOnGestureListener 这个是上述三个接口的空实现,一般情况下使用这个比较多,也比较方便

--------------------- 

3.2.3 重要方法详细介绍

方法名 描述 所属接口

onDwon 手指触摸屏幕的一瞬间,由1个ACTION_DOWN触发 OnGestureListener

onShowPress 手指触摸屏幕,尚未松开或拖动,由1个ACTION_DOWN触发。注意:和onDwon的区别,它强调的是没有松开或拖动的状态 OnGestureListener

onSingleTapUp 手指触摸屏幕后松开,伴随着1个ACTION_UP而触发,这是单击行为 OnGestureListener

onScroll 手指按下屏幕并拖动,由一个ACTION_DOWN和多个ACTION_MOVE触发,这是拖动行为 OnGestureListener

onLongPress 用户长久按着屏幕不放,即长按 OnGestureListener

onFling 用户按下触摸屏、快速滑动后松开,由1个ACTION_DOWN、多个ACTION_MOVE和1个ACTION_UP触发,这是快速滑动行为 OnGestureListener

onDoubleTap 双击,有两次连续的单击组成,它不可能和 onSingleTapConfirmed共存 OnDoubleTapListener

onSingleTapConfirmed 严格的单击行为。注意:它和onSingleTapUp的区别,如果触发了onSingleTapConfirmed,那么后面不可能在紧跟着另一个单击行为,即这只可能是单击,而不可能是双击中的一次单击 OnDoubleTapListener

onDoubleTapEvent 表示发生了双击行为,在双击的期间,ACTION_DWON、ACTION_MOVE和ACTION_UP都会触发此回调 OnDoubleTapListener

--------------------- 

4.View的坐标系

View 的位置主要由它的四个顶点来决定的,分别对应于 View 的四个属性:top、left、right、bottom。其中: 

top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标。 

需要注意的是:这些坐标都是相当于 View 的父容器来说的,这是一种相对坐标。View 的坐标和父容器的关系如下: 

--------------------- 

4.1静态坐标系

View坐标 参数含义

left = getLeft() View自身左侧到父View左侧的距离

top = getTop() View自身顶部到父View顶部的距离

right = getRight() View自身右侧到父View左侧的距离

botton = getBotton() View自身底部到父View顶部的距离

getTranslationX() View左上角相对父View的X轴偏移量

getTranslationY() View左上角相对父View的Y轴偏移量

getX() 其值为:getLeft()+getTranslationX(),当setTranslationX 时,getLeft()不会变,getX会变

getY() 其值为:getTop()+getTranslationY(),当setTranslationY 时,getTop()不会变,getY会变

MationEvent 触摸事件坐标:

MotionEvent坐标方法 参数含义

getX() 当前触摸点距离当前 View 自身左边的距离

getY() 当前触摸点距离当前 View 自身顶部的距离

getRawX() 当前触摸点距离屏幕左边的距离(Android绝对坐标系)

getRawY() 当前触摸点距离屏幕顶部的距离(Android绝对坐标系)

--------------------- 

4.2 滑动坐标系

关于View提供的与坐标息息相关的另一组常用的重要方法就是滚动或者滑动相关的方法,为了实现 View 的滑动,View 提供了专门的方法来实现这个功能,那就是 scrollTo 和 scrollBy,我们先来看看这两个方法的源码:

/**
 * Set the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the x position to scroll to
 * @param y the y position to scroll to
 */
public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
        int oldX = mScrollX;
        int oldY = mScrollY;
        mScrollX = x;
        mScrollY = y;
        invalidateParentCaches();
        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
        if (!awakenScrollBars()) {
        postInvalidateOnAnimation();
        }
        }
        }

/**
 * Move the scrolled position of your view. This will cause a call to
 * {@link #onScrollChanged(int, int, int, int)} and the view will be
 * invalidated.
 * @param x the amount of pixels to scroll by horizontally
 * @param y the amount of pixels to scroll by vertically
 */
public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
        }
        ---------------------

从源码可以看出,scrollBy 实际也是调用了 scrollTo 方法,它实现了基于当前位置的相对滑动,而 scrollTo 则实现了基于所传递参数的绝对滑动。这里我们要注意的是:我们要明白滑动过程中 View 内部的两个属性 mScrollX 和 mScrollY 的改变规则,这两个属性可以通过 getScrollX 和 getScrollY 方法分别获取到。简单概括一下,在滑动过程中,mScrollX 的值总是等于 View 左边缘和 View 内容左边缘在水平方向的距离,而 mScrollY 的值总是等于View 上边缘和View 内容上边缘在竖直方向的距离。View 边缘是指 View 的位置,由四个顶点组成,而 View 内容边缘是指 View 中的内容的边缘。 scrollTo 和 scrollBy 只能改变 View 内容的位置而不能改变 View 在布局中的位置。 mScrollX 和 mScrollY 单位是像素,

并且当 View 左边缘在 View 内容左边缘的右侧是,mScrollX 为正值,反之为负值;

当 View 上边缘在 View 内容上边缘的下边时,mScrollY 为正值,反之为负值。换句话说,如果从左向右滑动,那么 mScrollX 为负值,反之为正值

;如果从上往下滑动,那么 mScrollY 为负值,反之为正值。

--------------------- 

5.1 scrollTo与scrollBy

为了实现 View 的滑动,View 提供了专门的方法来实现这个功能,那就是 scrollTo 和 scrollBy 。实际上在 4.2滑动坐标系 中大体介绍的了相关内容。这里我们详细说明一下。

scrollTo(int x,int y): 

如果偏移位置发生了改变,就会给 mScrollX 和 mScrollY 赋新值,mScrollX表示里视图起始位置的x水平放的偏移量(getScrollX()可以获取到),mScrollY 表示离视图起始位置的y垂直方向的偏移量(getScrollY()可以获取到)。改变当前位置。

注意:x , y 代表的不是坐标点,而是偏移量

scrollBy(int x,int y): 

从源码中看出,它实际上是调用 了 scrollTo(mScrollX + x,mScrollY + y);

注意:mScrollX + x 和 mScrollY + y ,即代表在原先偏移的基础上再发生偏移,通俗的讲就是相对我们当前位置的偏移。

我要移动view到坐标点(100,100),那么我的偏移量就是(0,,0)  - (100,100) = (-100 ,-100)  ,我就要执行view.scrollTo(-100,-100),达到这个效果

看源代码:

scrollBy:多次调用,多次移动,  scrollTo 则实现了基于所传递参数的相对滑动      滑动了

scrollto:只调用一次,移动在一个位置就不动了                             绝对滑动       滑动到

总结:scrollTo()指的是移动到制定的(x,y)位置,而scrollBy(x,y)指的是,在当前位置在移动(x,y)个位置

5.2 Scroller

使用scrollTo()scrollBy()来实现 View 的滑动的时候并不多,因为这两个方法产生的滑动时不连贯的,跳跃的,最终的效果也不够平滑。所以,Android提供了Scroller这个类来实现平滑的滑动。

startScroll()来开启平滑移动过程,前两个参数的意思是起始坐标,接下来的两个坐标是偏移量,最后一个是显示的时长。Scroller类提供了 computeScrollOffset()方法来判断是否完成了滑动,同时也提供了 getCurrX()和getCurrY()来获取当前滑动坐标。需要注意的是,invalidate()方法,因为只能在computeScroll()方法中获取滑动过程中的scrollX和scrollY的坐标。但是computeScroll()方法是不会自动调用的,只能通过 invalidate() –>draw() –> computeScroll() 来间接调用computeScroll()方法,所以,需要在上述代码中调用 invalidate(),实现循环获取 scrollX和scrollY的目的。当滑动结束后,scroller.computeScrollOffset()方法会返回false,从而实现整个平滑移动的过程。

--------------------- 

5.3 layout

在View进行绘制时,会调用 onLayout()方法来设置显示的位置。当热,可以通过修改View的 left,top,right,bottom 四个属性来控制View 的坐标。在onTouchEvent()方法时调用onLayout()方法。

5.4 offsetLeftAndRight 和 offsetTopAndBottom

这个方法相当于系统提供的一个对上下、左右移动的封装。当计算出偏移量后,只要使用如下的自定义View 也可以跟随手指移动:

5.5 LayoutParams

LayoutParams保存了一个View的布局参数。因此可以在程序中,通过改变LayoutParams来动态地修改一个布局的位置参数,从而达到改变View位置的效果。我们可以很方便地在程序中使用getLayoutParams()来获取一个View的LayouParams。当然,计算偏移量的方法与在Layout方法中计算offset也是一样。当获取到偏移量之后,就可以通过setLayoutParams来改变其LayoutParams,

--------------------- 

参考博客:

https://blog.csdn.net/wanliguodu/article/details/81412951