https://developer.android.com/training/custom-views/making-interactive
绘制一个UI仅是自定义视图中的一部分。你还需要让你的视图可对用户输入有所反馈。
处理输入手势
像其他UI框架一样,安卓支持一个输入事件模型。用户动作被转换成会触发回调的事件,而且你可以重载这些回调事件来自定义如何反馈。最常用的输入事件是touch,它会触发onTouchEvent(android.view.MotionEvent)回调。重载这个方法来处理这个事件。
@Override
public boolean onTouchEvent(MotionEvent event) {
return super.onTouchEvent(event);
}
只有Touch事件并不是很有用。现代触摸UI定义了交互手势,比如tapping, pulling, pushing, flinging, and zooming。可以使用GestureDetector方法来转化纯粹的touch事件为手势。
通过传入一个GestureDetector.OnGestureListener的实例来构造一个GestureDetector。如果你只想处理一部分手势,你可以继承GestureDetector.SimpleOnGestureListener而不是实现GestureDetector.OnGestureListener接口。如下代码继承了GestureDetector.SimpleOnGestureListener并重载了onDown(MotionEvent)。
class mListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onDown(MotionEvent e) {
return true;
}
}
mDetector = new GestureDetector(PieChart.this.getContext(), new mListener());
无论你是否使用GestureDetector.SimpleOnGestureListener,你都需要让onDown()方法返回true。所有的手势皆由一个onDown()消息开始。如果onDown()方法返回false,正如GestureDetector.SimpleOnGestureListener所做,系统假设你想忽略其他手势,而GestureDetector.OnGestureListener的其他方法永远不会被调到。唯一一种onDown()返回false的情况是你真的想要忽略所有手势。一旦你实现GestureDetector.OnGestureListener并且创建GestureDetector的实例,你可以用GestureDetector来翻译onTouchEvent()中的touch事件。
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = mDetector.onTouchEvent(event);
if (!result) {
if (event.getAction() == MotionEvent.ACTION_UP) {
stopScrolling();
result = true;
}
}
return result;
}
当你传给onTouchEvent()一个而该方法无法识别成已知手势的touch事件时,该方法返回false。随后你可以执行你的自定义手势识别代码 。
创建物理上合理的动作
手势是用来控制触屏设备的非常有效的方式,但它们可能会违反直觉,难以记忆,除非它们产生了物理上可信的结果。 一个很好的例子就是fling手势,用户在屏幕上快速移动手指然后提起它。 当UI的反馈是飞快地在fling的方向上快速滑动,随后滑动速度减慢时,该姿势是有合理的,就好像用户已经转动了轮子并让它随着惯性继续转。
然而,模拟轮子的感觉不是那么容易得。让转轮模型正常工作需要很多物理和数学知识。幸运的是,安卓提供helper类来模拟这些行为。Scroller类是用来处理滚轮类型的fling事件的基本。
要启动一个fling,要调用fling()方法,并设置starting velocity、最大值、最小值、x和y值。
对于velocity值,可以使用GestureDetector计算出的值。
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mScroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
postInvalidate();
}
注意: 尽管GestureDetector计算出的velocity是物理上准确的,很多开发者感觉用这个值做fling动画太快了。推荐将改值除以4到8。
调用fling()启动了fling手势的物理模型。随后,你需要用定期调用Scroller.computeScrollOffset()来更新Scroller。computeScrollOffset()通过读取当前时间,并用物理模型计算当时的x和y位置更新Scroller的内部状态。调用getCurrX()和getCurrY()以得到这些值。
大部分视图将Scroller对象的x和y位置直接传给scrollTo()方法。PieChart例子有一个小小的不同:它用当前的scroll y position来设置图表的旋转角度。
if (!mScroller.isFinished()) {
mScroller.computeScrollOffset();
setPieRotation(mScroller.getCurrY());
}
Scroller类会自动计算滚动位置,但是它不自动将这些值设置到自定义视图上。你需要手动在自定义视图上设置这些值,并足够频繁地更新坐标,以让动画看起来顺滑。有两种方式可以达成:
- 在调用 fling()之后调用postInvalidate(),让界面重绘。这样做的话你需要在onDraw() 中计算scroll offsets,并在每次scroll offset改变时调用postInvalidate()。
- 设置一个ValueAnimator来处理长时间的动画,通过addUpdateListener()来处理动画的更新.
PieChart使用第二种方法。它的设置稍微有些复杂,但是它与动画系统的合作更紧密,而且不需要潜在的冗余视图重绘。却显示ValueAnimator 在API level 11之前不兼容。
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean result = mDetector.onTouchEvent(event);
if (!result) {
if (event.getAction() == MotionEvent.ACTION_UP) {
stopScrolling();
result = true;
}
}
return result;
}
让状态切换更流畅
用户预期UI可以在状态间流畅地切换。UI元素渐入渐出而不是突然出现或消失。动作顺滑地开始和结束,而不是突兀地。property animation framework(Android 3.0加入)可以轻易地实现顺滑转换。
要使用动画系统,无论什么时候一个影响视图外观的属性变化时,都不需要直接改变这个属性。使用ValueAnimator来进行这个操作. 下例中,更改当先算中的pie slice导致整个chart旋转,以使得选中指针居中。ValueAnimator在几百毫秒中改变了旋转角度,而不是立马设置一个旋转角度。
mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0);
mAutoCenterAnimator.setIntValues(targetAngle);
mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
mAutoCenterAnimator.start();
如果设置的值是View基本属性之一,设置动画更简单,因为有内建ViewPropertyAnimator,它给同时做多个属性的动画做了优化。例如:
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();
---总结---
如果需要在自定义视图中处理手势:
- 在onTouchEvent中实现OnGestureListener,并让onDown方法返回true;
- 在onTouchEvent()
回调中处理该手势。用GestureDetector翻译touch事件 - fling事件之后调用scroller的fling方法,使用GestureDetector计算出的velocity的1/4或1/8。
之后要写代码改变scroller的offset,然后postInvalidate。或直接使用动画。
4.使用ValueAnimator/ObjectAnimator来改变界面属性值,让显示更流畅。
更多:
Input Events
Property Animation.