使视图可交互
绘制UI界面只是创建自定义视图中的一部分工作,你还应该去响应用户的输入,并且让这种响应更贴近现实生活,比如现实世界中的一些物理现象,一些人们的行为习惯等等,
处理输入手势
像其他的UI框架一样,Android也提供了一个输入事件模型。用户的触摸动作被转化为了一些事件,并且Android会进行事件回调,你可以通过覆写回调方法来处理用户的触屏事件,响应用户进行交互。像触屏事件会回调
onTouchEvent(android.view.MotionEvent) 方法,你只需要覆写它来处理该事件
@Override public boolean onTouchEvent(MotionEvent event) { return super.onTouchEvent(event); }
触摸事件本身并不是特别的重要,现代触摸用户界面定义了以手势为主的交互动作,像轻敲、拉、推、抛和放大缩小手势,为了将原始触摸事件转化为手势,Android提供了
GestureDetector 类。构建
GestureDetector 时需要传递一个实现了
GestureDetector.OnGestureListener
接口的类的实例,如果你不想处理所有的手势,你可以通过继承
GestureDetector.SimpleOnGestureListener
类来代替实现
GestureDetector.OnGestureListener
接口
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 ,那么Android系统会认为你想忽略手势识别剩余的操作,并且
GestureDetector.SimpleOnGestureListener
方法将得不到调用,当你确实想忽略整个手势时你可以返回
false 。一旦你实现了
GestureDetector.OnGestureListener
接口并且创建出来了一个实例,那么你就可以使用你的
GestureDetector 来表明触屏事件是从
onTouchEvent() 接收过来的
@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上可以表现为在手指移动方向上快速滑动,然后减慢,就像用户在推一个飞轮并让它转动一样
然而,模拟推飞轮的感觉并不是一件很简单的事,要让一个飞轮模型正确的工作,需要用到大量的物理和数学知识。幸运的是,Android提供了一些工具类来帮助我们模拟这个和其它的一些行为,
Scroller 类是处理
flywheel-style
抛手势最基本的一个类
@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();
}
调用
fling() 方法只是为抛手势建立起了一个物理模型,然后,我们应该每隔一个常规合理的时间来调用
Scroller.computeScrollOffset() 更新
Scroller
,
computeScrollOffset()
方法更新
Scroller
对象的内部状态借助于在当前的时间去渲染它和使用物理模型去计算x轴和y轴的坐标,我们可以调用
getCurrX()
和
getCurrY() 来获取这些值
if (!mScroller.isFinished()) { mScroller.computeScrollOffset(); setPieRotation(mScroller.getCurrY());
}
Scroller
为你计算出滚动的位置,但是它并不会自动的把这些位置信息应用到你的视图上,获得和应用新的坐标信息能够让你的滑动看起来顺畅平滑,但这项工作需要你手动来做,有两种方式来做到这点:
1、在调用
fling()
后调用
postInvalidate()
方法,这样可以迫使视图重绘,这项技术需要你在
onDraw()
方法中计算好滚动的偏移量和滚动偏移量每次改变后都要调用
postInvalidate()
方法
提供的例子中使用的是第二种方法,这项技术稍微复杂点,但是它看起来更贴近Android中的动画系统并且不要求潜在的不需要的无效视图,
ValueAnimator
在
API level 11
以上版本中提供,但是我们可以在程序运行的时候动态的检测手机操作系统的版本来决定是否使用
mScroller = new Scroller(getContext(), null, true); mScrollAnimator = ValueAnimator.ofFloat(0,1); mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { if (!mScroller.isFinished()) { mScroller.computeScrollOffset(); setPieRotation(mScroller.getCurrY()); } else { mScrollAnimator.cancel(); onScrollFinished(); } } });
让你的视图中的动画平滑的播放
用户期待一个更现代的UI,在不同的状态间转换平滑。UI元素的淡入淡出,让用户动作的开始和结束过渡的尽量平滑,在Android3.0以上版本,可以很简单的做到这些。
使用Android的动画系统,即使是一个属性的改变也会影响到视图的显示效果,不要直接的去改变属性。可以使用
ValueAnimator
来代替这种改变
mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0); mAutoCenterAnimator.setIntValues(targetAngle); mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION); mAutoCenterAnimator.start();
如果你想改变的是视图基础属性的值,制作动画就更简单了,因为视图提供了一个内置的
ViewPropertyAnimator 类 ,并且这个类同时优化了动画的多个属性
animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();