0%

Android开发:创建自定义视图–让视图可以交互

画UI只是创建自定义视图的一部分。你也需要让你的视图对用户的输入以一种closely resembles the real-world action you’re mimicking的方式作出反应(拟物?).对象应该跟真实的对象表现一样。举个例子, 图片不应该在已存在的物件上马上弹出,在某处重复出现,因为真实世界里的对象不会这样。图片应该从一个地方移动到另一个地方。 用户也感知微妙的行为或者一个界面,对模拟真实世界反应最好。举个例子,当用户fling(快速滑动,松开,listView里有)一个UI控件时,他们应该感觉到摩擦力让滚动慢下来。 本节示范如何使用Android框架的特性给你拉自定义视图加上这些真实世界的行为 处理输入手势 就像其他UI框架,Android支持输入事件模型。用户动作被转换成回调事件,你可以覆盖这些回调事件来定制你的应用对用户的响应。在Android系统中最常见的输入事件是Touch,它会触发onTouchEvent(android.view.MotionEvent)方法. 覆盖这个方法: Handle Input Gestures

   @Override
   public boolean onTouchEvent(MotionEvent event) {
    return super.onTouchEvent(event);
   }

它们自己的Touch事件不见得有用。现代的触控UI控件根据手势(像tapping轻击,pulling拉,pushing推,flinging抛,zooming缩放)来定义反应。为了把原始的Touch事件转换成手势,Android提供了GestureDetector类. 通过传入一个实现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,你必须实现返回true的onDown()方法,这一步是必需的,因为所有的手势从onDown()开始。如果你返回false,系统就假设你要忽略处理手势,不会执行GestureDetector.OnGestureListener的其他方法。只有当你真要忽略整个手势时,才返回false. 一旦你实现了GestureDetector.OnGestureListener,并且创建了GestureDetector的实例,你就可以在onTouchEvent()方法里用GestureDetector处理接收到的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的方向快速滑动,然后慢下来,就像用户拉动飞轮,让它旋转。 然而, 模拟飞轮的效果不是繁琐的。为了让飞轮模型能正常工作,需要一堆数学和物理知识。幸运的是,Android提供了帮助类来模拟这个和其他行为。Scroller类为操控飞轮形式的fling手势打下了基础。 开始一个fling,通过fling的传入速率和x,y最小最大值来调用fling(),速率,你可以使用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();
}

注意:尽管velocity是GestureDetector通过精确物理计算而来,但很多开发者觉得用这个值动画太快.通常把x,y速率值除以4~8. 调用fling()方法为fling手势设置了物理模型,然后,你需要通过定期调用 Scroller.computeScrollOffset()更新Scroller。 computeScrollOffset()通过读取当前时间并且使用物理模型计算x,y位置来更新Scroller对象的内部状态。调用getCurrX() 和 getCurrY() 来获取他们的值 大多数视图把Scroller对象的x,y值直接传给scrollTo()方法。PieChart案例有一些不同:它使用滚动y位置来设置图表的转动角。

if (!mScroller.isFinished()) {
    mScroller.computeScrollOffset();
    setPieRotation(mScroller.getCurrY());
}

Scroller类帮你计算滚动位置,但它不能自动将你的view应用这些位置。经常读取坐标,并应用新坐标,让滚动动画更流畅,是你的责任。有两种方法: 1、在调用fling()方法后,调用 postInvalidate(),强制重绘。这项技术要求你在onDraw()计算滚动偏移,并且在每次滚动偏移改变后,调用postInvalidate() 2、给fling期间设置一个ValueAnimator动画,通过调用 addUpdateListener()添加一个监听器来处理动画事件。 PieChart案例使用了第二种方法。这项技术设置稍稍复杂一点,但它紧依动画系统,并且不需要潜在的没必要的让视图失效(强制重绘)。缺点是ValueAnimator不支持API Level 11以前的版本,所以不能在android 3.0以前的版本运行。 注意:虽然ValueAnimator不支持api 11以前版本,但你仍然可以在面向低版本api的应用中使用。你只要在运行时检测当前的api版本,如果低于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做改变。接下来的案例中,修改当前选中的扇形图会导致整个图表旋转。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();