<h2>创建视图交互</h2> <p>图形用户界面只是创建自定义视图的一部分。您还需要使视图以模仿现实世界行动相似的方式响应用户输入。对象始终应像真正对象做的一样。例如,图像应不立即弹出并重现在某个地方别的地方,因为在现实世界中的对象不会这样做。相反,图像应从一个位置移动到另一个位置。</p> <p>用户也感觉到细微的行为或界面上响应最佳模仿现实世界中的细微之处。例如,当用户甩动一个 UI 对象,他们应该感觉动作继续,摩擦然后在最终停止,最后的位置超出甩动发生时的位置。</p> <p>这节课演示如何使用 Android 框架的功能,将这些真实世界的行为添加到您的自定义视图。</p> <h3>处理输入的手势</h3> <p>像许多其他 UI 框架,android 系统支持输入的事件模型。用户操作都变成触发回调的事件,您可以重写自定义您的应用程序如何响应用户的回调。在 Android 系统中最常见的输入的事件是触摸,而触发 <a href="http://developer.android.com/reference/android/view/View.html#onTouchEvent(android.view.MotionEvent)" target="_blank">onTouchEvent(android.view.MotionEvent)</a>。重写此方法以处理事件:</p> <div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre> @Override <span style="color: #0000ff">public</span> <span style="color: #0000ff">boolean</span> onTouchEvent(MotionEvent event) { <span style="color: #0000ff">return</span> <span style="color: #0000ff">super</span>.onTouchEvent(event); }</pre> </div>
<br />
<p>触控事件本身不是特别有用的。现代触摸 Ui 定义交互的点击、 拉、 推、 甩动和放大的手势。若要将原始触控事件转换手势,Android 提供了 <a href="http://developer.android.com/reference/android/view/GestureDetector.html" target="_blank">GestureDetector</a>。</p>
<p>通过传入一个实现 <a href="http://developer.android.com/reference/android/view/GestureDetector.OnGestureListener.html" target="_blank">GestureDetector.OnGestureListener</a> 类的一个实例构造 <a href="http://developer.android.com/reference/android/view/GestureDetector.html" target="_blank">GestureDetector</a>。如果您只想要处理几个手势,您可以扩展 <a href="http://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html" target="_blank">GestureDetector.SimpleOnGestureListener</a>,而不是实现 <a href="http://developer.android.com/reference/android/view/GestureDetector.OnGestureListener.html" target="_blank">GestureDetector.OnGestureListener</a> 接口。例如,此代码创建一个类,扩展了 <a href="http://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html" target="_blank">GestureDetector.SimpleOnGestureListener</a> 和重写 <a href="http://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html#onDown(android.view.MotionEvent)" target="_blank">onDown(MotionEvent)</a>。</p>
<div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre><span style="color: #0000ff">class</span> mListener <span style="color: #0000ff">extends</span> GestureDetector.SimpleOnGestureListener { @Override <span style="color: #0000ff">public</span> <span style="color: #0000ff">boolean</span> onDown(MotionEvent e) { <span style="color: #0000ff">return</span> <span style="color: #0000ff">true</span>; } } mDetector = <span style="color: #0000ff">new</span> GestureDetector(PieChart.<span style="color: #0000ff">this</span>.getContext(), <span style="color: #0000ff">new</span> mListener());</pre> </div>
<br />
<p>无论是否使用 <a href="http://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html" target="_blank">GestureDetector.SimpleOnGestureListener</a>,您必须实现一个 <a href="http://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html#onDown(android.view.MotionEvent)" target="_blank">onDown()</a> 方法,返回 true。此步骤是必需的因为所有的手势开始与 <a href="http://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html#onDown(android.view.MotionEvent)" target="_blank">onDown()</a> 消息。如果您从 <a href="http://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html#onDown(android.view.MotionEvent)" target="_blank">onDown()</a> 返回 false,如同 <a href="http://developer.android.com/reference/android/view/GestureDetector.SimpleOnGestureListener.html" target="_blank">GestureDetector.SimpleOnGestureListener</a>,系统将假定您想要忽略其余的姿态,<a href="http://developer.android.com/reference/android/view/GestureDetector.OnGestureListener.html" target="_blank">GestureDetector.OnGestureListener</a> 的其他方法永远不会被调用。只有如果您真正想要忽略整个手势时应该返回 false 。<a href="http://developer.android.com/reference/android/view/GestureDetector.OnGestureListener.html" target="_blank">GestureDetector.OnGestureListener</a> 一旦和创建的 <a href="http://developer.android.com/reference/android/view/GestureDetector.html" target="_blank">GestureDetector</a> 实例,您可以使用您的 <a href="http://developer.android.com/reference/android/view/GestureDetector.html" target="_blank">GestureDetector</a> 来解释您收到在 <a href="http://developer.android.com/reference/android/view/View.html#onTouchEvent(android.view.MotionEvent)" target="_blank">onTouchEvent()</a> 中的触摸事件。</p>
<div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre>@Override <span style="color: #0000ff">public</span> <span style="color: #0000ff">boolean</span> onTouchEvent(MotionEvent event) { <span style="color: #0000ff">boolean</span> result = mDetector.onTouchEvent(event); <span style="color: #0000ff">if</span> (!result) { <span style="color: #0000ff">if</span> (event.getAction() == MotionEvent.ACTION_UP) { stopScrolling(); result = <span style="color: #0000ff">true</span>; } } <span style="color: #0000ff">return</span> result; }</pre> </div>
<br />
<p>当您传递 <a href="http://developer.android.com/reference/android/view/View.html#onTouchEvent(android.view.MotionEvent)" target="_blank">onTouchEvent()</a> 一个触摸事件,它不能识别作为一种姿态的一部分时,它将返回 false。然后,您可以运行您自己的自定义手势检测代码。</p>
<p> </p>
<h3>创建物理运动</h3>
<p>手势是控制触摸屏设备,功能强大的方法,但他们可以是违反直觉的很难记住除非他们产生物理可信的结果。一个很好的例子是甩动姿势,用户快速地在屏幕上移动手指,然后举起它。这种姿态是如果用户界面响应快速移动的方向的甩动,然后放慢,犹如该用户已推上飞轮和设置它旋转的道理的。</p>
<p>但是,模拟飞轮的感觉并很简单。物理和数学的很多都需要得到正常的飞轮模型。幸运的是,Android 提供了帮助器类来模拟这和其他的行为。<a href="http://developer.android.com/reference/android/widget/Scroller.html" target="_blank">Scroller</a> 类是用于处理飞轮样式甩动手势的基础。</p>
<p>若要启动甩动,调用 <a href="http://developer.android.com/reference/android/widget/Scroller.html#fling(int, int, int, int, int, int, int, int)" target="_blank">fling()</a> 与甩动起始的速度和最小和最大 x 和 y 值。对于速度值,您可以使用由 <a href="http://developer.android.com/reference/android/view/GestureDetector.html" target="_blank">GestureDetector</a> 为您计算的值。</p>
<div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre>@Override <span style="color: #0000ff">public</span> <span style="color: #0000ff">boolean</span> onFling(MotionEvent e1, MotionEvent e2, <span style="color: #0000ff">float</span> velocityX, <span style="color: #0000ff">float</span> velocityY) { mScroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY); postInvalidate(); }</pre> </div>
<div style="border-left: #258aaf 4px solid; padding-bottom: 0.5em; padding-left: 10px; padding-right: 0px; padding-top: 0px"><strong>注:</strong> 虽然按 GestureDetector 计算的速度是物理上准确的,但许多开发人员觉得甩动动画使用此值太快。它是通常使用 x 和 y 除 4 至 8 倍的速度。 </div>
<p>调用 <a href="http://developer.android.com/reference/android/widget/Scroller.html#fling(int, int, int, int, int, int, int, int)" target="_blank">fling()</a> 设置一个甩动手势。之后,您需要通过定时调用 <a href="http://developer.android.com/reference/android/widget/Scroller.html#computeScrollOffset()" target="_blank">Scroller.computeScrollOffset()</a> 来更新 <a href="http://developer.android.com/reference/android/widget/Scroller.html" target="_blank">Scroller</a>,<a href="http://developer.android.com/reference/android/widget/Scroller.html#computeScrollOffset()" target="_blank">computeScrollOffset()</a> 通过甩动设置的初始属性与当前时间,计算当时的 x 与 y 坐标。调用 <a href="http://developer.android.com/reference/android/widget/Scroller.html#getCurrX()" target="_blank">getCurrX()</a> 与 <a href="http://developer.android.com/reference/android/widget/Scroller.html#getCurrY()" target="_blank">getCurY()</a> 获取该值。</p>
<p>多数视图通过调用 <a href="http://developer.android.com/reference/android/view/View.html#scrollTo(int, int)" target="_blank">scrollTo()</a> 直接设置 <a href="http://developer.android.com/reference/android/widget/Scroller.html" target="_blank">Scroller</a> 的 x,y。PieChart 示例中稍有不同:它使用当前滚动 y 坐标设置饼的旋转角度。</p>
<div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre><span style="color: #0000ff">if</span> (!mScroller.isFinished()) { mScroller.computeScrollOffset(); setPieRotation(mScroller.getCurrY()); }</pre> </div>
<p><a href="http://developer.android.com/reference/android/widget/Scroller.html" target="_blank">Scroller</a> 类可以为您计算滚动坐标,但它不会自动应用到您的视图。您需要获取和应用平滑的滚动动画。有两种方式实现:</p>
<ul> <li>在调用 <a href="http://developer.android.com/reference/android/widget/Scroller.html#fling(int, int, int, int, int, int, int, int)" target="_blank">fling()</a> 之后调用 <a href="http://developer.android.com/reference/android/view/View.html#postInvalidate()" target="_blank">postInvalidate()</a> 强制重绘。这种技术需要您在 <a href="http://developer.android.com/reference/android/view/View.html#onDraw(android.graphics.Canvas)" target="_blank">onDraw()</a> 中计算滚动偏移并在每次滚动偏移改变时使用 <a href="http://developer.android.com/reference/android/view/View.html#postInvalidate()" target="_blank">postInvalidate()</a>。 </li>
<li>设置一个 <a href="http://developer.android.com/reference/android/animation/ValueAnimator.html" target="_blank">ValueAnimator</a> 处理甩动动画并使用 <a href="http://developer.android.com/reference/android/animation/ValueAnimator.html#addUpdateListener(android.animation.ValueAnimator.AnimatorUpdateListener)" target="_blank">addUpdateListener()</a> 持续处理动画更新。 </li> </ul>
<p>PieChart 示例使用了第二种方法。这种技术稍复杂,但它与动画系统密切工作,能够减少不必要的重绘请求。缺点是 ViewAnimator 不在 API 11 版本之前提供,所以不能在 Android 版本低于 3.0 的设备上使用。</p>
<div style="border-left: #258aaf 4px solid; padding-bottom: 0.5em; padding-left: 10px; padding-right: 0px; padding-top: 0px"><strong>注:</strong> 虽然 ValueAnimator 不能在 API 11 之前使用,但你仍可以在低于这个 API 级别上运行。您只需要在运行时进行版本测试,如果低于 11 就活力视图动画请求。</div>
<p> </p>
<div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre> mScroller = <span style="color: #0000ff">new</span> Scroller(getContext(), <span style="color: #0000ff">null</span>, <span style="color: #0000ff">true</span>); mScrollAnimator = ValueAnimator.ofFloat(0,1); mScrollAnimator.addUpdateListener(<span style="color: #0000ff">new</span> ValueAnimator.AnimatorUpdateListener() { @Override <span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> onAnimationUpdate(ValueAnimator valueAnimator) { <span style="color: #0000ff">if</span> (!mScroller.isFinished()) { mScroller.computeScrollOffset(); setPieRotation(mScroller.getCurrY()); } <span style="color: #0000ff">else</span> { mScrollAnimator.cancel(); onScrollFinished(); } } });</pre> </div>
<h3></h3>
<br />
<h3>平滑过渡</h3>
<p>用户所期待的现代UI在各状态间平滑过渡。UI 元素淡入淡出而不是直接显示与消失。动作平滑的开始和结束而非突然开始和停止。Android 3.0 提供了<a href="http://developer.android.com/guide/topics/graphics/prop-animation.html" target="_blank">属性动画框架</a>让平滑过渡更简单。</p>
<p>若要使用动画系统,将会影响视图外观的属性更改时,不要直接更改该属性。相反,使用 <a href="http://developer.android.com/reference/android/animation/ValueAnimator.html" target="_blank">ValueAnimator</a> 来进行更改。在以下示例中,修改当前所选的饼图扇区在饼图中使整个图表旋转,使选择指针位于所选切片的中心。<a href="http://developer.android.com/reference/android/animation/ValueAnimator.html" target="_blank">ValueAnimator</a> 在一段几百毫秒时间内更改旋转,而不是立即设置新的旋转值。</p>
<div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre>mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.<span style="color: #0000ff">this</span>, "<span style="color: #8b0000">PieRotation</span>", 0); mAutoCenterAnimator.setIntValues(targetAngle); mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION); mAutoCenterAnimator.start();</pre> </div>
<br />
<p>如果您要设置的值是基本 View 属性,做动画就更简单了,因为视图内建了 ViewPropertyAnimator 更优化并能同时支持多个属性值的动画。例如:</p>
<div style="border-bottom: #ddd 1px solid; border-left: #ddd 1px solid; padding-bottom: 1em; margin: 0px 0px 1em; padding-left: 1em; padding-right: 1em; background: #f7f7f7; overflow: auto; border-top: #ddd 1px solid; border-right: #ddd 1px solid; padding-top: 1em"> <pre>animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();</pre> </div>
来源:oschina
链接:https://my.oschina.net/u/1012974/blog/116220