**
* Helper for tracking the velocity of touch events, for implementing
* flinging and other such gestures. Use {@link #obtain} to retrieve a
* new instance of the class when you are going to begin tracking, put
* the motion events you receive into it with {@link #addMovement(MotionEvent)},
* and when you want to determine the velocity call
* {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()}
* and {@link #getYVelocity()}.
*/
// Helper类用于跟踪触摸事件的速度,实现如抛掷等手势。使用{@link #obtain}在开始跟踪时检索类的新实例,将您接收到的motion事件放入其中,并在您想要确定速度时调用
// {@link #computeCurrentVelocity(int)},然后调用{@link #getXVelocity()}和{@link #getYVelocity()}。
// 在Android应用程序的开发过程中,可以实现更绚丽的效果,比如让界面切换平滑的滚动,系统提供的应用在特效这方面提供简单的动画接口。自定义控件开发需要对布局控件以及其子控件的尺寸进行精确控制,还需要根据手势情况实现惯性滑动的效果。为了实现滑动效果,我们需要使用以下工具:
// - android.view.VelocityTracer
// - android.view.Scroller
// - android.view.ViewConfiguration
// 从字面意思理解,VelocityTracker就是速度追踪器。在滑动效果的开发中,通常都需要使用该类来计算出当前手势的初始速度。对应的方法是:
// computeCurrentVelocity(int)
要实现一个可以进行惯性滑动的控件,我们需要遵循以下步骤:
1. 获取初始速度(initialVelocity)。
2. 创建一个Scroller对象。
3. 重写onTouchEvent方法,处理触摸事件。
4. 重写computeScroll方法,计算滚动位置。
5. 使用Scroller的fling方法控制界面滑动。
首先,我们需要通过VelocityTracker和ViewConfiguration类获取一些惯性滑动所需的变量,例如手势离开屏幕时的初始速度、允许进行手势操作的最小距离以及允许手势操作的速度边界值。这些值可以通过以下方法获取:
- configuration.getScaledTouchSlop():获得能够进行手势滑动的距离。
- configuration.getScaledMinimumFlingVelocity():获得允许执行一个fling手势动作的最小速度值。
- configuration.getScaledMaximumFlingVelocity():获得允许执行一个fling手势动作的最大速度值。
接下来,我们需要创建一个Scroller对象,并使用它的fling方法来控制界面滑动。这可以通过以下代码实现:
```java
// 创建VelocityTracker对象
VelocityTracker velocityTracker = VelocityTracker.obtain();
// 设置触摸事件监听器
view.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
// 更新速度轨迹
velocityTracker.addMovement(event);
// 判断是否需要进行惯性滑动
if (event.getAction() == MotionEvent.ACTION_MOVE && velocityTracker.getVelocity(event.getPointerId(0)) != 0) {
float initialVelocity = getInitialVelocity(v, event);
float minX = configuration.getScaledTouchSlop();
float maxX = v.getWidth() - minX;
float minY = configuration.getScaledTouchSlop();
float maxY = v.getHeight() - minY;
Scroller scroller = new Scroller(context);
fling(scroller, v, initialVelocity, minX, maxX, minY, maxY, v.getTop(), v.getBottom());
view.post(scroller::startScroll);
} else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_POINTER_UP) {
// 停止滚动动画
Scroller scroller = (Scroller) v.getTag();
if (scroller != null) {
scroller.abortAnimation();
}
} else if (velocityTracker.getVelocity(event.getPointerId(0)) == 0) {
// 如果手指离开屏幕,停止滚动动画
Scroller scroller = (Scroller) v.getTag();
if (scroller != null) {
scroller.abortAnimation();
}
}
return true;
}
});
```
最后,我们需要实现以下几个方法:
1. getInitialVelocity(View view, MotionEvent event):根据触摸事件计算初始速度。
2. fling(Scroller scroller, View view, float initialVelocityX, float minX, float maxX, float minY, float maxY, int top, int bottom):使用Scroller的fling方法控制视图在水平和垂直方向上的滚动。
第三,重写onTouchEvent方法。当我们用手指在屏幕上来回滑动时,此时执行的是scrollBy方法来刷新界面。当手指离开屏幕时,此时就要开始执行ACTION_UP后面的操作了。通过对手指离开屏幕时的速度进行判断,是否能够进行惯性滑动操作。如果能够执行,那么就使用Scroller类的fling方法启动滑动动画。这时需要调用一下invalidate()方法来间接地调用computeScroll方法。
在computeScroll方法中,对Scroller的动画是否执行完成做了判断。如果动画没有完成(mScroller.computeScrollOffset() == true),那么就使用scrollTo方法对mScrollX、mScrollY的值进行重新计算,刷新界面。然后调用postInvalidate()方法重新绘制界面。postInvalidate()方法会调用invalidate()方法,而invalidate()方法又会调用computeScroll方法。就这样周而复始地相互调用,直到mScroller.computeScrollOffset()返回false才会停止界面的重绘动作。
总结来说,滑动效果来看,它依然是在不停地计算控件的位置刷新屏幕,不停地绘制新的图片替换旧的图片。当然每次刷新的速度很快,从而给人一种快速滑动的感觉。写到这里我发现,现在所谓的动画总是逃脱不了电影的那种模式:每秒播放多少帧的图片来达到连续播放的效果欺骗人的眼睛。
而且,关于Android一些酷炫效果的开发,还是要自己多动手,熟悉View、ViewGroup中每个绘制方法、位置计算方法的调用方式以及顺序。这样至少是在2D动画开发中,也就是一种方式,逃脱不了不停重新绘制的这个圈。关于熟悉View、ViewGroup中每个绘制方法、位置计算方法的调用方式以及顺序的问题,我建议最好自己写一个简单的自定义View或ViewGroup的扩展类,重载那些绘制、位置计算的方法打个日志出来一看自然就明白了。虽然这个方法很笨,但是很容易出效果的。