| page.title=Making the View Interactive | |
| parent.title=Creating Custom Views | |
| parent.link=index.html | |
| trainingnavtop=true | |
| previous.title=Custom Drawing | |
| previous.link=custom-drawing.html | |
| next.title=Optmizing the View | |
| next.link=optimizing-view.html | |
| @jd:body | |
| <div id="tb-wrapper"> | |
| <div id="tb"> | |
| <h2>This lesson teaches you to</h2> | |
| <ol> | |
| <li><a href="#inputgesture">Handle Input Gestures</a></li> | |
| <li><a href="#motion">Create Physically Plausible Motion</a></li> | |
| <li><a href="#makesmooth">Make Your Transitions Smooth</a></li> | |
| </ol> | |
| <h2>You should also read</h2> | |
| <ul> | |
| <li><a href="{@docRoot}guide/topics/ui/ui-events.html">Input Events</a></li> | |
| <li><a href="{@docRoot}guide/topics/graphics/prop-animation.html">Property Animation</a> | |
| </li> | |
| </ul> | |
| <h2>Try it out</h2> | |
| <div class="download-box"> | |
| <a href="{@docRoot}shareables/training/CustomView.zip" | |
| class="button">Download the sample</a> | |
| <p class="filename">CustomView.zip</p> | |
| </div> | |
| </div> | |
| </div> | |
| <p>Drawing a UI is only one part of creating a custom view. You also need to make your view respond | |
| to user input in a | |
| way that closely resembles the real-world action you're mimicking. Objects should always act in the | |
| same way that real | |
| objects do. For example, images should not immediately pop out of existence and reappear somewhere | |
| else, because objects | |
| in the real world don't do that. Instead, images should move from one place to another.</p> | |
| <p>Users also sense subtle behavior or feel in an interface, and react best to subtleties that | |
| mimic the real world. | |
| For example, when users fling a UI object, they should sense friction at the beginning that delays | |
| the motion, and then | |
| at the end sense momentum that carries the motion beyond the fling.</p> | |
| <p>This lesson demonstrates how to use features of the Android framework to add these real-world | |
| behaviors to your | |
| custom view. | |
| <h2 id="inputgesture">Handle Input Gestures</h2> | |
| <p>Like many other UI frameworks, Android supports an input event model. User actions are turned | |
| into events that | |
| trigger callbacks, and you can override the callbacks to customize how your application responds | |
| to the user. The | |
| most common input event in the Android system is <em>touch</em>, which triggers {@link | |
| android.view.View#onTouchEvent(android.view.MotionEvent)}. Override this method to handle the | |
| event:</p> | |
| <pre> | |
| @Override | |
| public boolean onTouchEvent(MotionEvent event) { | |
| return super.onTouchEvent(event); | |
| } | |
| </pre> | |
| <p>Touch events by themselves are not particularly useful. Modern touch UIs define interactions in | |
| terms of gestures | |
| such as tapping, pulling, pushing, flinging, and zooming. To convert raw touch events into | |
| gestures, Android | |
| provides {@link android.view.GestureDetector}.</p> | |
| <p>Construct a {@link android.view.GestureDetector} by passing in an instance of a class that | |
| implements {@link | |
| android.view.GestureDetector.OnGestureListener}. If you only want to process a few gestures, you | |
| can extend {@link | |
| android.view.GestureDetector.SimpleOnGestureListener} instead of implementing the {@link | |
| android.view.GestureDetector.OnGestureListener} | |
| interface. For instance, this code creates a class that extends {@link | |
| android.view.GestureDetector.SimpleOnGestureListener} and overrides {@link | |
| android.view.GestureDetector.SimpleOnGestureListener#onDown}.</p> | |
| <pre> | |
| class mListener extends GestureDetector.SimpleOnGestureListener { | |
| @Override | |
| public boolean onDown(MotionEvent e) { | |
| return true; | |
| } | |
| } | |
| mDetector = new GestureDetector(PieChart.this.getContext(), new mListener()); | |
| </pre> | |
| <p>Whether or not you use {@link | |
| android.view.GestureDetector.SimpleOnGestureListener}, you must always implement an | |
| {@link android.view.GestureDetector.OnGestureListener#onDown onDown()} method that | |
| returns {@code true}. This step is necessary because all gestures begin with an | |
| {@link android.view.GestureDetector.OnGestureListener#onDown onDown()} message. If | |
| you return {@code | |
| false} from {@link android.view.GestureDetector.OnGestureListener#onDown onDown()}, as | |
| {@link android.view.GestureDetector.SimpleOnGestureListener} does, the system assumes that | |
| you want to ignore the | |
| rest of the gesture, and the other methods of | |
| {@link android.view.GestureDetector.OnGestureListener} never get called. The | |
| only time you should | |
| return {@code false} from {@link android.view.GestureDetector.OnGestureListener#onDown onDown()} | |
| is if you truly want to ignore an entire gesture. | |
| Once you've implemented {@link android.view.GestureDetector.OnGestureListener} | |
| and created an instance of {@link android.view.GestureDetector}, you can use | |
| your {@link android.view.GestureDetector} to interpret the touch events you receive in {@link | |
| android.view.GestureDetector#onTouchEvent onTouchEvent()}.</p> | |
| <pre> | |
| @Override | |
| public boolean onTouchEvent(MotionEvent event) { | |
| boolean result = mDetector.onTouchEvent(event); | |
| if (!result) { | |
| if (event.getAction() == MotionEvent.ACTION_UP) { | |
| stopScrolling(); | |
| result = true; | |
| } | |
| } | |
| return result; | |
| } | |
| </pre> | |
| <p>When you pass {@link android.view.GestureDetector#onTouchEvent onTouchEvent()} a touch event that | |
| it doesn't | |
| recognize as part of a gesture, it returns {@code false}. You can then run your own custom | |
| gesture-detection | |
| code.</p> | |
| <h2 id="motion">Create Physically Plausible Motion</h2> | |
| <p>Gestures are a powerful way to control touchscreen devices, but they can be counterintuitive and | |
| difficult to | |
| remember unless they produce physically plausible results. A good example of this is the <em>fling</em> | |
| gesture, where the | |
| user quickly moves a finger across the screen and then lifts it. This gesture makes sense if the UI | |
| responds by moving | |
| quickly in the direction of the fling, then slowing down, as if the user had pushed on a | |
| flywheel and set it | |
| spinning.</p> | |
| <p>However, simulating the feel of a flywheel isn't trivial. A lot of physics and math are required | |
| to get a flywheel | |
| model working correctly. Fortunately, Android provides helper classes to simulate this and other | |
| behaviors. The | |
| {@link android.widget.Scroller} class is the basis for handling flywheel-style <em>fling</em> | |
| gestures.</p> | |
| <p>To start a fling, call {@link android.widget.Scroller#fling fling()} with the starting velocity | |
| and the minimum and | |
| maximum x and y values of the fling. For the velocity value, you can use the value computed for | |
| you by {@link android.view.GestureDetector}.</p> | |
| <pre> | |
| @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(); | |
| } | |
| </pre> | |
| <p class="note"><strong>Note:</strong> Although the velocity calculated by | |
| {@link android.view.GestureDetector} is physically accurate, | |
| many developers feel | |
| that using this value makes the fling animation too fast. It's common to divide the x and y | |
| velocity by a factor of | |
| 4 to 8.</p> | |
| <p>The call to {@link android.widget.Scroller#fling fling()} sets up the physics model for the fling | |
| gesture. | |
| Afterwards, you need to update the {@link android.widget.Scroller Scroller} by calling {@link | |
| android.widget.Scroller#computeScrollOffset Scroller.computeScrollOffset()} at regular | |
| intervals. {@link | |
| android.widget.Scroller#computeScrollOffset computeScrollOffset()} updates the {@link | |
| android.widget.Scroller | |
| Scroller} object's internal state by reading the current time and using the physics model to calculate | |
| the x and y position | |
| at that time. Call {@link android.widget.Scroller#getCurrX} and {@link | |
| android.widget.Scroller#getCurrY} to | |
| retrieve these values.</p> | |
| <p>Most views pass the {@link android.widget.Scroller Scroller} object's x and y position directly to | |
| {@link | |
| android.view.View#scrollTo scrollTo()}. The PieChart example is a little different: it | |
| uses the current scroll | |
| y position to set the rotational angle of the chart.</p> | |
| <pre> | |
| if (!mScroller.isFinished()) { | |
| mScroller.computeScrollOffset(); | |
| setPieRotation(mScroller.getCurrY()); | |
| } | |
| </pre> | |
| <p>The {@link android.widget.Scroller Scroller} class computes scroll positions for you, but it does | |
| not automatically | |
| apply those positions to your view. It's your responsibility to make sure you get and apply new | |
| coordinates often | |
| enough to make the scrolling animation look smooth. There are two ways to do this:</p> | |
| <ul> | |
| <li>Call {@link android.view.View#postInvalidate() postInvalidate()} after calling | |
| {@link android.widget.Scroller#fling(int, int, int, int, int, int, int, int) fling()}, | |
| in order to | |
| force a redraw. This | |
| technique requires that you compute scroll offsets in {@link android.view.View#onDraw onDraw()} | |
| and call {@link android.view.View#postInvalidate() postInvalidate()} every | |
| time the scroll offset changes. | |
| </li> | |
| <li>Set up a {@link android.animation.ValueAnimator} to animate for the duration of the fling, | |
| and add a listener to process animation updates | |
| by calling {@link android.animation.ValueAnimator#addUpdateListener addUpdateListener()}. | |
| </li> | |
| </ul> | |
| <p>The PieChart example uses the second approach. This technique is slightly more complex to set up, but | |
| it works more | |
| closely with the animation system and doesn't require potentially unnecessary view | |
| invalidation. The drawback is that {@link android.animation.ValueAnimator} | |
| is not available prior to API level 11, so this technique cannot be used | |
| on devices running Android versions lower than 3.0.</p> | |
| <p class="note"><strong>Note:</strong> You can use {@link android.animation.ValueAnimator} in | |
| applications that | |
| target lower API levels. You just need to make sure to check the current API level | |
| at runtime, and omit the calls to the view animation system if the current level is less than 11.</p> | |
| <pre> | |
| 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(); | |
| } | |
| } | |
| }); | |
| </pre> | |
| <h2 id="makesmooth">Make Your Transitions Smooth</h2> | |
| <p>Users expect a modern UI to transition smoothly between states. UI elements fade in and out | |
| instead of appearing and | |
| disappearing. Motions begin and end smoothly instead of starting and stopping abruptly. The | |
| Android <a | |
| href="{@docRoot}guide/topics/graphics/prop-animation.html">property animation | |
| framework</a>, introduced in | |
| Android 3.0, makes smooth transitions easy.</p> | |
| <p>To use the animation system, whenever a property changes that will affect your view's appearance, | |
| do not change the | |
| property directly. Instead, use {@link android.animation.ValueAnimator} to make the change. In | |
| the following | |
| example, modifying the | |
| currently selected pie slice in PieChart causes the entire chart to rotate so that the selection | |
| pointer is centered | |
| in the selected slice. {@link android.animation.ValueAnimator} changes the rotation over a | |
| period of several | |
| hundred milliseconds, | |
| rather than immediately setting the new rotation value.</p> | |
| <pre> | |
| mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0); | |
| mAutoCenterAnimator.setIntValues(targetAngle); | |
| mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION); | |
| mAutoCenterAnimator.start(); | |
| </pre> | |
| <p>If the value you want to change is one of the base {@link android.view.View} properties, doing | |
| the animation | |
| is even easier, | |
| because Views have a built-in {@link android.view.ViewPropertyAnimator} that is optimized for | |
| simultaneous animation | |
| of multiple properties. For example:</p> | |
| <pre> | |
| animate().rotation(targetAngle).setDuration(ANIM_DURATION).start(); | |
| </pre> |