1 View
和ViewGroup
View
是Android
中所有控件的基类,不管是简单的Button
、TextView
还是复杂的RelativeLayout
和ListView
,它们的共同基类都是View
。所以说,View
是一种界面层的控件的一种抽象,它代表了一个控件。除了View
还有ViewGroup
,从名字上看,可以翻译成控件组,即一组View
。ViewGroup
可以包含很多View
以及ViewGroup
,而它包含的ViewGroup
又可以包含View
和ViewGroup
,以此类推,形成一个View
树。
在Android
的设计中,ViewGroup
也继承了View
,这就意味着View
本身就可以是单个控件也可以是由多个控件组成的一组控件。
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource { }
public abstract class ViewGroup extends View implements ViewParent, ViewManager { }
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener { }
public class Button extends TextView { }
public class ImageView extends View { }
public class LinearLayout extends ViewGroup { }
public class RelativeLayout extends ViewGroup { }
public class ListView extends AbsListView { }
public abstract class AbsListView extends AdapterView<ListAdapter> implements TextWatcher,
ViewTreeObserver.OnGlobalLayoutListener, Filter.FilterListener,
ViewTreeObserver.OnTouchModeChangeListener,
RemoteViewsAdapter.RemoteAdapterConnectionCallback { }
public abstract class AdapterView<T extends Adapter> extends ViewGroup { }
2 坐标系
Android
系统中有两种坐标系,分别为Android
坐标系和View
坐标系。
2.1 Android
坐标系
在Andriod
中,将屏幕左上角的顶点作为Android
坐标系的原点,这个原点向右是X
轴正方向,向下是Y
轴正方向。在触控事件中,使用getRawX()
和getRawY()
方法获得的坐标是Android
坐标系的坐标。
raw [rɔː] 生的;未加工的;阴冷的;刺痛的;擦掉皮的;无经验的;(在艺术等方面)不成熟的
2.2 View
坐标系
View
坐标系,它与Android
坐标系并不冲突,两者是共同存在的。它们可以帮助开发者更好的控制 View
。
2.2.1 View
自身的坐标
View
的位置主要是由它的四个顶点来决定的,分别对应View
的四个属性:top
、left
、right
、bottom
, 其中top
是左上角的纵坐标,left
是左上角横坐标,right
是右下角很坐标,bottom
是右下角纵坐标。这些坐标都是相对于View
的父容器来说的,是一种相对坐标。
View
的四个参数,在源码中分别对应于mLeft
、mRight
、mTop
和mBottom
这四个成员变量:
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
protected int mLeft;
protected int mRight;
protected int mTop;
protected int mBottom;
public final int getLeft() { return mLeft; }
public final int getRight() { return mRight; }
public final int getTop() { return mTop; }
public final int getBottom() { return mBottom; }
}
通过如下方法可以获得View
到其父控件(ViewGroup
)的距离:
getLeft()
:获取View
自身左边到其父布局左边的距离getRight()
:获取View
自身右边到其父布局左边的距离getTop()
:获取View
自身顶边到其父布局顶边的距离getBottom()
:获取View
自身底边到父布局顶边的距离
从Android 3.0
开始,View
增加了几个额外的参数:x
、y
、translationX
和translationY
,其中x
和y
是view
相对于父容器左上角的坐标,而translationX
和translationY
是View
左上角相对于父容器的偏移量,并且translationX
和translationY
的默认值是0
, 几个参数的换算关系如下:
x = left + translationX
y = top + translationY
public float getX() {
return mLeft + getTranslationX();
}
public float getY() {
return mTop + getTranslationY();
}
在View
的平移过程中,top
和left
表示的是原始左上角的位置信息,其值并不会发生改变,发生改变的是x
、y
、translationX
和translationY
。
2.2.2 View
获取自身的宽高
系统提供了获取View
宽和高的方法,以下是View
的源码:
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
public final int getWidth() {
return mRight - mLeft;
}
public final int getHeight() {
return mBottom - mTop;
}
}
getWidth()
用来获取View
的宽度getHeight()
用来获取View
自身的高度
3 MotionEvent
和TouchSlop
motion [ˈmoʊʃn] 动作;移动;手势;请求;意向;议案 slop [slɑːp] 漫不经心地涂(或敷)
3.1 MotionEvent
无论是View
还是ViewGroup
,最终的点击事件都会由onTouchEvent(MotionEvent event)
方法来处理。MotionEvent
在用户交互过程中作用重大,其内部提供了很多事件常量,典型的事件类型有如下几种:
ACTION_DOWN
:手指刚接触屏幕ACTION_MOVE
:手指在屏幕上移动ACTION_UP
:手指从屏幕上松开的一瞬间
正常情况下,一次手指触摸屏幕的行为会出发一系列点击事件,考虑如下几种情况:
- 点击屏幕后离开,事件序列为
DOWN->UP
; - 点击屏幕滑动一会后离开,事件序列为
DOWN->MOVE...MOVE->MOVE->UP
此外,MotionEvent
也提供了获取焦点坐标的各种方法:
getX()
:获取点击事件距离控件左边的距离,即视图坐标getY()
:获取点击事件距离控件定边的距离,即视图坐标getRawX()
:获取点击事件距离整个屏幕左边的距离,即绝对坐标getRawY()
:获取点击事件距离整个屏幕顶边的距离,即绝对坐标
3.2 TouchSlop
TouchSlop
是系统所能识别出的被认为是滑动的最小距离,如果两次滑动之间的距离小于这个常量,那么系统不认为是在进行滑动操作。这个常量和设备相关,在不同的设备上这个值是不同的。通过以下方式可以获取这个常量ViewConfiguration.get(getContext()).getScaledTouchSlop();
4 VelocityTracker
速度追踪
velocity [vəˈlɑːsəti]【物】速度 tracker [ˈtrækər] 拉纤者,纤夫;追踪系统,[自] 跟踪装置;追踪者
速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向。 它的使用过程很简单:
@Override
public boolean onTouchEvent(MotionEvent event) {
// 1. 在onTouchEvent事件中追踪当前点击事件的速度
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
// 2. 获取当前速度,获取之前先计算速度,需要设置时间间隔(单位是毫秒),注意,速度可以是负数
velocityTracker.computeCurrentVelocity(1000);
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();
return super.onTouchEvent(event);
}
当不需要它的时候,需要调用clear
方法来重制并回收内存:
velocityTracker.clear();
velocityTracker.recycle();
5 GestureDetector
手势检测
gesture [ˈdʒestʃər] 手势,姿势;姿态,表示 detector [dɪˈtektər] 探测器;检测器;发现者;侦察器
手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。
首先,实现实现GestureDetector.OnGestureListener
接口,根据需要还可以实现GestureDetector.OnDoubleTapListener
从而能监听双击行为:
public class RoundImageView extends AppCompatImageView implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener {
@Override
public boolean onDown(MotionEvent e) {
return false;
}
@Override
public void onShowPress(MotionEvent e) {
}
@Override
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
return false;
}
@Override
public void onLongPress(MotionEvent e) {
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
return false;
}
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
return false;
}
}
其次,创建GestureDetector
对象:
GestureDetector gestureDetector = new GestureDetector(this);
gestureDetector.setIsLongpressEnabled(false); // 解决长按屏幕后无法拖动的现象
之后,接管目标View
的onTouchEvent
方法,在View
的onTouchEvent
方法中添加如下实现:
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
完成之后,就可以有选择的实现GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener
中的方法了。
在实际开发中,如果只是监听滑动相关的,建议在onTouchEvent
中实现,如果要监听双击这种行为的话,就使用GestureDetector
。