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 @Override 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 @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 @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 @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> You can use {@link android.animation.ValueAnimator} in 233 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 @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