基础知识
什么是View
在 Android 中,View
是用户界面(UI)中的基本组件,用于绘制图形和处理用户交互。所有的 UI 组件(如按钮、文本框、图片等)都是 View
的子类。可以说,View
是构建 Android 应用界面的基础。
View
是一种界面层的控件的一种抽象,它代表了一个控件。除了 View
还有 ViewGroup
,里面包含了许多个控件,即一组 View
。在Android设计中,ViewGroup
也继承了 View
,也就意味着 View
本身可以是单个控件也可以是由多个控件组成的一组控件。
View的位置参数
View
的位置主要由它的四个顶点决定:top、left、right、bottom,top是左上角纵坐标,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标。这些坐标都是相对于 View
的父容器来说的,是一种相对坐标。
width = right - left
height = bottom - top
Left = getLeft()
Right = getRight()
Top = getTop()
Bottom = getBottom()
从Android3.0开始,View增加了额外的几个参数:x、y、translationX、translationY,其中x和y是View左上角的坐标,translationX和translationY是View左上角相对于父容器的偏移量。
y = top + translationY
MotionEvent 和 TouchSlop
-
MotionEvent
MotionEvent
是 Android 中用于描述触摸屏幕的事件类。当用户在屏幕上进行触摸操作(如点击、滑动、拖动等)时,系统会生成一个MotionEvent
对象并传递给相应的View
的触摸事件处理方法(例如onTouchEvent()
)。
MotionEvent 的主要方法和常量:
- 常用事件类型(通过
getAction()
获取):ACTION_DOWN
:表示手指刚刚触碰屏幕,此时可以记录触摸的起始坐标。ACTION_MOVE
:表示手指在屏幕上移动,通常用于检测滑动、拖动等操作。ACTION_UP
:表示手指离开屏幕,通常在这里结束触摸操作或触发点击事件。ACTION_CANCEL
:表示触摸事件被中断,比如手指从屏幕上滑动到不可触摸区域。
- 坐标获取:
getX()
和getY()
:获取事件发生点相对于当前View
的 x 和 y 轴坐标。getRawX()
和getRawY()
:获取事件发生点相对于屏幕的绝对 x 和 y 轴坐标。
- 多点触控:
MotionEvent
支持多点触控,可以通过getPointerCount()
来获取触控点数量,或通过getPointerId(int index)
来获取特定触控点的 ID。
MotionEvent 使用场景
MotionEvent
通常用于实现复杂的手势或触控效果,比如检测滑动方向、双指缩放、拖动等。通过组合 ACTION_DOWN
、ACTION_MOVE
和 ACTION_UP
的坐标变化,可以实现自定义的滑动或手势检测逻辑。
-
TouchSlop
TouchSlop
是一个阈值,用于判断用户的触摸是否足够显著,足以被认为是“滑动”而不是“轻微抖动”或“点击”。在触摸屏幕时,有时用户会产生轻微的抖动,而TouchSlop
的作用就是过滤掉这种无意的微小移动。TouchSlop
的值在设备中是固定的(基于屏幕密度),可以通过ViewConfiguration.get(context).getScaledTouchSlop()
来获取。TouchSlop
的单位是像素,通常的使用方式是当手指移动距离超过TouchSlop
时,才认为这是一个有效的滑动操作。
TouchSlop 使用场景
TouchSlop
常用于判断滑动是否开始,例如在处理自定义滑动手势时,可以使用以下伪代码来判断滑动:
// 假设 downX 和 downY 是手指按下时的初始坐标
float deltaX = currentX - downX;
float deltaY = currentY - downY;
if (Math.sqrt(deltaX * deltaX + deltaY * deltaY) > touchSlop) {
// 开始滑动
}
使用 TouchSlop
可以避免在轻微抖动时触发滑动,从而提高手势的识别精度。
VelocityTracker、GestureDetector和Scroller
在 Android 中,VelocityTracker
、GestureDetector
和 Scroller
是处理触摸事件和手势操作的三个常用工具,适用于实现复杂的滑动、手势识别、惯性滚动等效果。以下是对它们的介绍和应用场景:
-
VelocityTracker
VelocityTracker
用于追踪触摸事件的移动速度,特别是在实现滑动和快速滑动手势(如快速滑动删除、甩动等)时非常有用。-
主要方法:
addMovement(MotionEvent event)
:将当前的触摸事件加入到VelocityTracker
中,用于计算滑动速度。computeCurrentVelocity(int units)
:计算速度,units
参数通常设为1000
,表示每秒的像素速率。getXVelocity()
和getYVelocity()
:获取 x 和 y 方向的滑动速度,返回值是每秒的像素速度。clear()
:清除VelocityTracker
中的事件。
-
使用场景:通常在实现滑动或甩动删除功能时会用到,例如根据手指的滑动速度来判断是否应触发滑动效果。
-
速度 = (终点位置 - 起点位置) / 时间段
示例代码:
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);
float xVelocity = velocityTracker.getXVelocity();
float yVelocity = velocityTracker.getYVelocity();
-
GestureDetector
GestureDetector
用于检测标准的手势操作,例如单击、双击、长按、滑动等。它简化了常见手势的识别,不需要手动计算每个MotionEvent
的位置和时间。-
常用手势方法:
onSingleTapUp(MotionEvent e)
:单击。onLongPress(MotionEvent e)
:长按。onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
:滑动。onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY)
:快速滑动。onDoubleTap(MotionEvent e)
:双击。
-
使用场景:
GestureDetector
可以简化各种常见手势的实现,适用于需要检测点击、长按、双击等的场景。例如在图片查看应用中实现缩放或拖拽手势。
示例代码:
GestureDetector gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override public boolean onSingleTapUp(MotionEvent e) { // 单击事件 return true; } @Override public void onLongPress(MotionEvent e) { // 长按事件 } @Override public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // 滑动事件 return true; } @Override public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // 快速滑动事件 return true; } }); // 在触摸事件中使用 GestureDetector @Override public boolean onTouchEvent(MotionEvent event) { return gestureDetector.onTouchEvent(event); }
-
-
Scroller
Scroller
是 Android 中用于实现滚动和惯性滑动的工具类。它本身不会滚动View
,而是提供计算滚动位置的接口,通常与View
的computeScroll()
方法结合使用。-
主要方法:
startScroll(int startX, int startY, int dx, int dy, int duration)
:设置起点、滚动距离和持续时间。fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)
:使用速度(通常配合VelocityTracker
)和边界来实现甩动效果。computeScrollOffset()
:判断滚动是否完成,用于在computeScroll()
方法中进行判断。getCurrX()
和getCurrY()
:获取当前滚动位置。
-
使用场景:通常用于实现平滑滚动效果,如实现自定义滑动
View
,滑动菜单等。在ScrollView
中,Scroller
可用于实现惯性滚动效果。
示例代码:
Scroller scroller = new Scroller(context); // 触发滚动 scroller.startScroll(0, 0, deltaX, deltaY, 1000); // 在 View 中重写 computeScroll 方法 @Override public void computeScroll() { if (scroller.computeScrollOffset()) { scrollTo(scroller.getCurrX(), scroller.getCurrY()); invalidate(); // 刷新界面,继续执行滚动 } }
-
View的滑动
使用scrollTo/scrollBy
下面是scrollTo和scrollBy的源码:
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();
}
}
}
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
可以看到scrollBy实际上也调用了scrollTo方法,实现了基于当前位置的相对滑动,而scrollTo实现了基于所传递参数的绝对滑动。
View
边缘是指 View
的位置,由四个顶点组成,而 View
内容边缘是指 View
中的内容的边缘,scrollToTo和scrollBy只能改变 View
内容的位置而不能改变 View
在布局中的位置。mScrollX和mScrollY的单位为像素,并且当 View
左边缘在 View
内容左边缘的右边时,mScrollX为正值,反之为负值;当 View
上边缘在View内容上边缘的下边时,mScrollY为正值,反之为负值。换句话说,如果从左向右滑动,那么mScrollX为负值,反之为正值;如果从上往下滑动,那么mScrollY为负值,反之为正值。
改变布局参数
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) button.getLayoutParams();
params.width += 100;
params.leftMargin += 100;
button.requestLayout();
可以通过改变LayoutParams的方式去实现View的滑动。
弹性滑动
使用Scroller
实现平滑滚动:
public class SmoothScrollView extends View {
private Scroller scroller;
public SmoothScrollView(Context context) {
super(context);
scroller = new Scroller(context);
}
// 定义平滑滚动到指定位置的方法
public void smoothScrollTo(int destX, int destY) {
int deltaX = destX - getScrollX(); // x方向滚动的距离
int deltaY = destY - getScrollY(); // y方向滚动的距离
// 设定滚动参数
scroller.startScroll(getScrollX(), getScrollY(), deltaX, deltaY, 1000); // 1秒滚动完成
invalidate(); // 触发重绘
}
@Override
public void computeScroll() {
if (scroller.computeScrollOffset()) {
scrollTo(scroller.getCurrX(), scroller.getCurrY());
postInvalidate(); // 保持刷新
}
}
}
通过动画
使用动画来实现滑动天然就具有弹性效果,下面代码可以让一个View100ms内向右移动100像素:
ObjectAnimator.ofFloat(button, "translationX", 0, 100).setDuration(100).start();
如果模仿Scroller来实现View的弹性滑动,利用动画的特性,我们可以采用如下方式:
final int startX = 0;
final int deltaX = 100;
ValueAnimator animator = ValueAnimator.ofInt(0, 1).setDuration(1000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(@NonNull ValueAnimator animation) {
float fraction = animator.getAnimatedFraction();
button.scrollTo(startX + (int)(deltaX * fraction), 0);
}
});
animator.start();
-
startX
表示按钮的初始水平滚动位置,这里设为0
。 -
deltaX
表示滚动的水平距离(即目标位置与起始位置的差距),设为100
。在动画过程中,按钮内容的滚动位置会从startX
移动到startX + deltaX
。 -
ValueAnimator.ofInt(0, 1)
创建了一个整数类型的ValueAnimator
对象,该对象的值从0
变为1
,并且setDuration(1000)
设置动画的持续时间为 1 秒(1000 毫秒)。这里将动画的数值范围设定为
0
到1
,这意味着动画本身并不依赖具体的值变化,而是利用动画的进度来计算滚动的距离。
addUpdateListener
为animator
添加了一个更新监听器AnimatorUpdateListener
。在动画进行过程中,每一帧都会回调onAnimationUpdate()
方法。- 在
onAnimationUpdate()
方法中,使用animator.getAnimatedFraction()
获取当前动画的完成度(即已运行时间占总时间的百分比)。这个完成度fraction
是一个0
到1
的小数,0
表示动画刚开始,1
表示动画结束。 button.scrollTo(startX + (int)(deltaX * fraction), 0);
:调用scrollTo()
方法,将按钮内容沿水平轴滚动到startX + (int)(deltaX * fraction)
位置。deltaX * fraction
表示当前滚动的位置,每一帧会根据完成度fraction
计算当前的滚动距离,使得滚动效果在整个动画持续时间内平滑进行。- 例如,当
fraction
为0.5
时,滚动的位置为startX + deltaX * 0.5 = 50
,即按钮内容滚动到距离初始位置50
的位置。
已经到底啦!!