Home | History | Annotate | Download | only in custom-views
      1 page.title=Making the View Interactive
      2 parent.title=Creating Custom Views
      3 parent.link=index.html
      4 
      5 trainingnavtop=true
      6 previous.title=Custom Drawing
      7 previous.link=custom-drawing.html
      8 next.title=Optmizing the View
      9 next.link=optimizing-view.html
     10 
     11 @jd:body
     12 
     13 <div id="tb-wrapper">
     14     <div id="tb">
     15 
     16         <h2>This lesson teaches you to</h2>
     17         <ol>
     18             <li><a href="#inputgesture">Handle Input Gestures</a></li>
     19             <li><a href="#motion">Create Physically Plausible Motion</a></li>
     20             <li><a href="#makesmooth">Make Your Transitions Smooth</a></li>
     21         </ol>
     22 
     23         <h2>You should also read</h2>
     24         <ul>
     25             <li><a href="{@docRoot}guide/topics/ui/ui-events.html">Input Events</a></li>
     26             <li><a href="{@docRoot}guide/topics/graphics/prop-animation.html">Property Animation</a>
     27             </li>
     28         </ul>
     29 <h2>Try it out</h2>
     30 <div class="download-box">
     31 <a href="{@docRoot}shareables/training/CustomView.zip"
     32 class="button">Download the sample</a>
     33 <p class="filename">CustomView.zip</p>
     34 </div>
     35     </div>
     36 </div>
     37 
     38 <p>Drawing a UI is only one part of creating a custom view. You also need to make your view respond
     39 to user input in a
     40 way that closely resembles the real-world action you're mimicking. Objects should always act in the
     41 same way that real
     42 objects do. For example, images should not immediately pop out of existence and reappear somewhere
     43 else, because objects
     44 in the real world don't do that. Instead, images should move from one place to another.</p>
     45 
     46 <p>Users also sense subtle behavior or feel in an interface, and react best to subtleties that
     47 mimic the real world.
     48 For example, when users fling a UI object, they should sense friction at the beginning that delays
     49 the motion, and then
     50 at the end sense momentum that carries the motion beyond the fling.</p>
     51 
     52 <p>This lesson demonstrates how to use features of the Android framework to add these real-world
     53 behaviors to your
     54 custom view.
     55 
     56 <h2 id="inputgesture">Handle Input Gestures</h2>
     57 
     58 <p>Like many other UI frameworks, Android supports an input event model. User actions are turned
     59     into events that
     60     trigger callbacks, and you can override the callbacks to customize how your application responds
     61     to the user. The
     62     most common input event in the Android system is <em>touch</em>, which triggers {@link
     63     android.view.View#onTouchEvent(android.view.MotionEvent)}. Override this method to handle the
     64     event:</p>
     65 
     66 <pre>
     67    &#64Override
     68    public boolean onTouchEvent(MotionEvent event) {
     69     return super.onTouchEvent(event);
     70    }
     71 </pre>
     72 
     73 <p>Touch events by themselves are not particularly useful. Modern touch UIs define interactions in
     74     terms of gestures
     75     such as tapping, pulling, pushing, flinging, and zooming. To convert raw touch events into
     76     gestures, Android
     77     provides {@link android.view.GestureDetector}.</p>
     78 
     79 <p>Construct a {@link android.view.GestureDetector} by passing in an instance of a class that
     80     implements {@link
     81     android.view.GestureDetector.OnGestureListener}. If you only want to process a few gestures, you
     82     can extend {@link
     83     android.view.GestureDetector.SimpleOnGestureListener} instead of implementing the {@link
     84     android.view.GestureDetector.OnGestureListener}
     85     interface. For instance, this code creates a class that extends {@link
     86     android.view.GestureDetector.SimpleOnGestureListener} and overrides {@link
     87     android.view.GestureDetector.SimpleOnGestureListener#onDown}.</p>
     88 
     89 <pre>
     90 class mListener extends GestureDetector.SimpleOnGestureListener {
     91    &#64;Override
     92    public boolean onDown(MotionEvent e) {
     93        return true;
     94    }
     95 }
     96 mDetector = new GestureDetector(PieChart.this.getContext(), new mListener());
     97 </pre>
     98 
     99 <p>Whether or not you use {@link
    100     android.view.GestureDetector.SimpleOnGestureListener}, you must always implement an
    101     {@link android.view.GestureDetector.OnGestureListener#onDown onDown()} method that
    102     returns {@code true}. This step is necessary because all gestures begin with an
    103     {@link android.view.GestureDetector.OnGestureListener#onDown onDown()} message. If
    104     you return {@code
    105     false} from {@link android.view.GestureDetector.OnGestureListener#onDown onDown()}, as
    106     {@link android.view.GestureDetector.SimpleOnGestureListener} does, the system assumes that
    107     you want to ignore the
    108     rest of the gesture, and the other methods of
    109     {@link android.view.GestureDetector.OnGestureListener} never get called. The
    110     only time you should
    111     return {@code false} from {@link android.view.GestureDetector.OnGestureListener#onDown onDown()}
    112     is if you truly want to ignore an entire gesture.
    113 
    114     Once you've implemented {@link android.view.GestureDetector.OnGestureListener}
    115     and created an instance of {@link android.view.GestureDetector}, you can use
    116     your {@link android.view.GestureDetector} to interpret the touch events you receive in {@link
    117     android.view.GestureDetector#onTouchEvent onTouchEvent()}.</p>
    118 
    119 <pre>
    120 &#64;Override
    121 public boolean onTouchEvent(MotionEvent event) {
    122    boolean result = mDetector.onTouchEvent(event);
    123    if (!result) {
    124        if (event.getAction() == MotionEvent.ACTION_UP) {
    125            stopScrolling();
    126            result = true;
    127        }
    128    }
    129    return result;
    130 }
    131 </pre>
    132 
    133 <p>When you pass {@link android.view.GestureDetector#onTouchEvent onTouchEvent()} a touch event that
    134     it doesn't
    135     recognize as part of a gesture, it returns {@code false}. You can then run your own custom
    136     gesture-detection
    137     code.</p>
    138 
    139 <h2 id="motion">Create Physically Plausible Motion</h2>
    140 
    141 <p>Gestures are a powerful way to control touchscreen devices, but they can be counterintuitive and
    142     difficult to
    143     remember unless they produce physically plausible results. A good example of this is the <em>fling</em>
    144     gesture, where the
    145     user quickly moves a finger across the screen and then lifts it. This gesture makes sense if the UI
    146     responds by moving
    147     quickly in the direction of the fling, then slowing down, as if the user had pushed on a
    148     flywheel and set it
    149     spinning.</p>
    150 
    151 <p>However, simulating the feel of a flywheel isn't trivial. A lot of physics and math are required
    152     to get a flywheel
    153     model working correctly. Fortunately, Android provides helper classes to simulate this and other
    154     behaviors. The
    155     {@link android.widget.Scroller} class is the basis for handling flywheel-style <em>fling</em>
    156     gestures.</p>
    157 
    158 <p>To start a fling, call {@link android.widget.Scroller#fling fling()} with the starting velocity
    159     and the minimum and
    160     maximum x and y values of the fling. For the velocity value, you can use the value computed for
    161     you by {@link android.view.GestureDetector}.</p>
    162 
    163 <pre>
    164 &#64;Override
    165 public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    166    mScroller.fling(currentX, currentY, velocityX / SCALE, velocityY / SCALE, minX, minY, maxX, maxY);
    167    postInvalidate();
    168 }
    169 </pre>
    170 
    171 <p class="note"><strong>Note:</strong> Although the velocity calculated by
    172     {@link android.view.GestureDetector} is physically accurate,
    173     many developers feel
    174     that using this value makes the fling animation too fast. It's common to divide the x and y
    175     velocity by a factor of
    176     4 to 8.</p>
    177 
    178 <p>The call to {@link android.widget.Scroller#fling fling()} sets up the physics model for the fling
    179     gesture.
    180     Afterwards, you need to update the {@link android.widget.Scroller Scroller} by calling {@link
    181     android.widget.Scroller#computeScrollOffset Scroller.computeScrollOffset()} at regular
    182     intervals. {@link
    183     android.widget.Scroller#computeScrollOffset computeScrollOffset()} updates the {@link
    184     android.widget.Scroller
    185     Scroller} object's internal state by reading the current time and using the physics model to calculate
    186     the x and y position
    187     at that time. Call {@link android.widget.Scroller#getCurrX} and {@link
    188     android.widget.Scroller#getCurrY} to
    189     retrieve these values.</p>
    190 
    191 <p>Most views pass the {@link android.widget.Scroller Scroller} object's x and y position directly to
    192     {@link
    193     android.view.View#scrollTo scrollTo()}. The PieChart example is a little different: it
    194     uses the current scroll
    195     y position to set the rotational angle of the chart.</p>
    196 
    197 <pre>
    198 if (!mScroller.isFinished()) {
    199     mScroller.computeScrollOffset();
    200     setPieRotation(mScroller.getCurrY());
    201 }
    202 </pre>
    203 
    204 <p>The {@link android.widget.Scroller Scroller} class computes scroll positions for you, but it does
    205     not automatically
    206     apply those positions to your view. It's your responsibility to make sure you get and apply new
    207     coordinates often
    208     enough to make the scrolling animation look smooth. There are two ways to do this:</p>
    209 
    210 <ul>
    211     <li>Call {@link android.view.View#postInvalidate() postInvalidate()} after calling
    212         {@link android.widget.Scroller#fling(int, int, int, int, int, int, int, int) fling()},
    213         in order to
    214         force a redraw. This
    215         technique requires that you compute scroll offsets in {@link android.view.View#onDraw onDraw()}
    216         and call {@link android.view.View#postInvalidate() postInvalidate()} every
    217         time the scroll offset changes.
    218     </li>
    219     <li>Set up a {@link android.animation.ValueAnimator} to animate for the duration of the fling,
    220         and add a listener to process animation updates
    221         by calling {@link android.animation.ValueAnimator#addUpdateListener addUpdateListener()}.
    222     </li>
    223 </ul>
    224 
    225 <p>The PieChart example uses the second approach. This technique is slightly more complex to set up, but
    226     it works more
    227     closely with the animation system and doesn't require potentially unnecessary view
    228     invalidation. The drawback is that {@link android.animation.ValueAnimator}
    229     is not available prior to API level 11, so this technique cannot be used
    230 on devices running Android versions lower than 3.0.</p>
    231 
    232 <p class="note"><strong>Note:</strong> {@link android.animation.ValueAnimator} isn't available
    233         prior to API level 11, but you can still use it in applications that
    234 target lower API levels. You just need to make sure to check the current API level
    235 at runtime, and omit the calls to the view animation system if the current level is less than 11.</p>
    236 
    237 <pre>
    238        mScroller = new Scroller(getContext(), null, true);
    239        mScrollAnimator = ValueAnimator.ofFloat(0,1);
    240        mScrollAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    241            &#64;Override
    242            public void onAnimationUpdate(ValueAnimator valueAnimator) {
    243                if (!mScroller.isFinished()) {
    244                    mScroller.computeScrollOffset();
    245                    setPieRotation(mScroller.getCurrY());
    246                } else {
    247                    mScrollAnimator.cancel();
    248                    onScrollFinished();
    249                }
    250            }
    251        });
    252 </pre>
    253 
    254 <h2 id="makesmooth">Make Your Transitions Smooth</h2>
    255 
    256 <p>Users expect a modern UI to transition smoothly between states. UI elements fade in and out
    257     instead of appearing and
    258     disappearing. Motions begin and end smoothly instead of starting and stopping abruptly. The
    259     Android <a
    260             href="{@docRoot}guide/topics/graphics/prop-animation.html">property animation
    261         framework</a>, introduced in
    262     Android 3.0, makes smooth transitions easy.</p>
    263 
    264 <p>To use the animation system, whenever a property changes that will affect your view's appearance,
    265     do not change the
    266     property directly. Instead, use {@link android.animation.ValueAnimator} to make the change. In
    267     the following
    268     example, modifying the
    269     currently selected pie slice in PieChart causes the entire chart to rotate so that the selection
    270     pointer is centered
    271     in the selected slice. {@link android.animation.ValueAnimator} changes the rotation over a
    272     period of several
    273     hundred milliseconds,
    274     rather than immediately setting the new rotation value.</p>
    275 
    276 <pre>
    277 mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0);
    278 mAutoCenterAnimator.setIntValues(targetAngle);
    279 mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
    280 mAutoCenterAnimator.start();
    281 </pre>
    282 
    283 <p>If the value you want to change is one of the base {@link android.view.View} properties, doing
    284     the animation
    285     is even easier,
    286     because Views have a built-in {@link android.view.ViewPropertyAnimator} that is optimized for
    287     simultaneous animation
    288     of multiple properties. For example:</p>
    289 
    290 <pre>
    291 animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();
    292 </pre>
    293