blob: 4e9d53a5e773e974423ddf77e14fddee111c141d [file] [log] [blame]
Scott Main50e990c2012-06-21 17:14:39 -07001page.title=Making the View Interactive
2parent.title=Creating Custom Views
3parent.link=index.html
4
5trainingnavtop=true
6previous.title=Custom Drawing
7previous.link=custom-drawing.html
8next.title=Optmizing the View
9next.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"
32class="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
39to user input in a
40way that closely resembles the real-world action you're mimicking. Objects should always act in the
41same way that real
42objects do. For example, images should not immediately pop out of existence and reappear somewhere
43else, because objects
44in 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
47mimic the real world.
48For example, when users fling a UI object, they should sense friction at the beginning that delays
49the motion, and then
50at 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
53behaviors to your
54custom 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>
90class mListener extends GestureDetector.SimpleOnGestureListener {
91 &#64;Override
92 public boolean onDown(MotionEvent e) {
93 return true;
94 }
95}
96mDetector = 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
121public 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
165public 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>
198if (!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
230on 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
234target lower API levels. You just need to make sure to check the current API level
235at 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>
277mAutoCenterAnimator = ObjectAnimator.ofInt(PieChart.this, "PieRotation", 0);
278mAutoCenterAnimator.setIntValues(targetAngle);
279mAutoCenterAnimator.setDuration(AUTOCENTER_ANIM_DURATION);
280mAutoCenterAnimator.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>
291animate().rotation(targetAngle).setDuration(ANIM_DURATION).start();
292</pre>