293 lines
12 KiB
Plaintext
293 lines
12 KiB
Plaintext
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> {@link android.animation.ValueAnimator} isn't available
|
|
prior to API level 11, but you can still use it 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>
|