• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1page.title=Managing Touch Events in a ViewGroup
2parent.title=Using Touch Gestures
3parent.link=index.html
4
5trainingnavtop=true
6next.title=
7next.link=
8
9@jd:body
10
11<div id="tb-wrapper">
12<div id="tb">
13
14<!-- table of contents -->
15<h2>This lesson teaches you to</h2>
16<ol>
17  <li><a href="#intercept">Intercept Touch Events in a ViewGroup</a></li>
18  <li><a href="#vc">Use ViewConfiguration Constants</a></li>
19  <li><a href="#delegate">Extend a Child View's Touchable Area</a></li>
20</ol>
21
22<!-- other docs (NOT javadocs) -->
23<h2>You should also read</h2>
24
25<ul>
26    <li><a href="http://developer.android.com/guide/topics/ui/ui-events.html">Input Events</a> API Guide
27    </li>
28    <li><a href="{@docRoot}guide/topics/sensors/sensors_overview.html">Sensors Overview</a></li>
29    <li><a href="{@docRoot}training/custom-views/making-interactive.html">Making the View Interactive</a> </li>
30    <li>Design Guide for <a href="{@docRoot}design/patterns/gestures.html">Gestures</a></li>
31    <li>Design Guide for <a href="{@docRoot}design/style/touch-feedback.html">Touch Feedback</a></li>
32</ul>
33
34<h2>Try it out</h2>
35
36<div class="download-box">
37  <a href="{@docRoot}shareables/training/InteractiveChart.zip"
38class="button">Download the sample</a>
39 <p class="filename">InteractiveChart.zip</p>
40</div>
41
42
43</div>
44</div>
45
46<p>Handling touch events in a {@link android.view.ViewGroup} takes special care,
47because it's common for a {@link android.view.ViewGroup} to have children that
48are targets for different touch events than the {@link android.view.ViewGroup}
49itself. To make sure that each view correctly receives the touch events intended
50for it, override the {@link android.view.ViewGroup#onInterceptTouchEvent
51onInterceptTouchEvent()} method.</p>
52
53<h2 id="intercept">Intercept Touch Events in a ViewGroup</h2>
54
55<p>The {@link android.view.ViewGroup#onInterceptTouchEvent onInterceptTouchEvent()}
56method is called whenever a touch event is detected on the surface of a
57{@link android.view.ViewGroup}, including on the surface of its children. If
58{@link android.view.ViewGroup#onInterceptTouchEvent onInterceptTouchEvent()}
59returns {@code true}, the {@link android.view.MotionEvent} is intercepted,
60meaning it will be not be passed on to the child, but rather to the
61{@link android.view.View#onTouchEvent onTouchEvent()} method of the parent.</p>
62
63<p>The {@link android.view.ViewGroup#onInterceptTouchEvent onInterceptTouchEvent()}
64method gives a parent the chance to see any touch event before its children do.
65If you return {@code true} from
66{@link android.view.ViewGroup#onInterceptTouchEvent onInterceptTouchEvent()},
67the child view that was previously handling touch events
68receives an {@link android.view.MotionEvent#ACTION_CANCEL}, and the events from that
69point forward are sent to the parent's
70{@link android.view.View#onTouchEvent onTouchEvent()} method for the usual handling.
71{@link android.view.ViewGroup#onInterceptTouchEvent onInterceptTouchEvent()} can also
72return {@code false} and simply spy on events as they travel down the view hierarchy
73to their usual targets, which will handle the events with their own
74{@link android.view.View#onTouchEvent onTouchEvent()}.
75
76
77<p>In the following snippet, the class {@code MyViewGroup} extends
78{@link android.view.ViewGroup}.
79{@code MyViewGroup} contains multiple child views. If you drag your finger across
80a child view horizontally, the child view should no longer get touch events, and
81{@code MyViewGroup} should handle touch events by scrolling its contents. However,
82if you press buttons in the child view, or scroll the child view vertically,
83the parent shouldn't intercept those touch events, because the child is the
84intended target. In those cases,
85{@link android.view.ViewGroup#onInterceptTouchEvent onInterceptTouchEvent()} should
86return {@code false}, and {@code MyViewGroup}'s
87{@link android.view.View#onTouchEvent onTouchEvent()} won't be called.</p>
88
89<pre>public class MyViewGroup extends ViewGroup {
90
91    private int mTouchSlop;
92
93    ...
94
95    ViewConfiguration vc = ViewConfiguration.get(view.getContext());
96    mTouchSlop = vc.getScaledTouchSlop();
97
98    ...
99
100    &#64;Override
101    public boolean onInterceptTouchEvent(MotionEvent ev) {
102        /*
103         * This method JUST determines whether we want to intercept the motion.
104         * If we return true, onTouchEvent will be called and we do the actual
105         * scrolling there.
106         */
107
108
109        final int action = MotionEventCompat.getActionMasked(ev);
110
111        // Always handle the case of the touch gesture being complete.
112        if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
113            // Release the scroll.
114            mIsScrolling = false;
115            return false; // Do not intercept touch event, let the child handle it
116        }
117
118        switch (action) {
119            case MotionEvent.ACTION_MOVE: {
120                if (mIsScrolling) {
121                    // We're currently scrolling, so yes, intercept the
122                    // touch event!
123                    return true;
124                }
125
126                // If the user has dragged her finger horizontally more than
127                // the touch slop, start the scroll
128
129                // left as an exercise for the reader
130                final int xDiff = calculateDistanceX(ev);
131
132                // Touch slop should be calculated using ViewConfiguration
133                // constants.
134                if (xDiff > mTouchSlop) {
135                    // Start scrolling!
136                    mIsScrolling = true;
137                    return true;
138                }
139                break;
140            }
141            ...
142        }
143
144        // In general, we don't want to intercept touch events. They should be
145        // handled by the child view.
146        return false;
147    }
148
149    &#64;Override
150    public boolean onTouchEvent(MotionEvent ev) {
151        // Here we actually handle the touch event (e.g. if the action is ACTION_MOVE,
152        // scroll this container).
153        // This method will only be called if the touch event was intercepted in
154        // onInterceptTouchEvent
155        ...
156    }
157}</pre>
158
159<p>Note that {@link android.view.ViewGroup} also provides a
160{@link android.view.ViewGroup#requestDisallowInterceptTouchEvent requestDisallowInterceptTouchEvent()} method.
161The {@link android.view.ViewGroup} calls this method when a child does not want the parent and its
162ancestors to intercept touch events with
163{@link android.view.ViewGroup#onInterceptTouchEvent onInterceptTouchEvent()}.
164</p>
165
166<h2 id="vc">Use ViewConfiguration Constants</h2>
167
168<p>The above snippet uses the current {@link android.view.ViewConfiguration} to initialize
169a variable called {@code mTouchSlop}. You can use the {@link
170android.view.ViewConfiguration} class to access common distances, speeds, and
171times used by the Android system.</p>
172
173
174<p>"Touch slop" refers to the distance in pixels a user's touch can wander
175before the gesture is interpreted as scrolling. Touch slop is typically used to
176prevent accidental scrolling when the user is performing some other touch
177operation, such as touching on-screen elements.</p>
178
179<p>Two other commonly used {@link android.view.ViewConfiguration} methods are
180{@link android.view.ViewConfiguration#getScaledMinimumFlingVelocity getScaledMinimumFlingVelocity()}
181and {@link android.view.ViewConfiguration#getScaledMaximumFlingVelocity getScaledMaximumFlingVelocity()}.
182These methods  return the minimum and maximum velocity (respectively) to initiate a fling,
183as measured in pixels per second. For example:</p>
184
185<pre>ViewConfiguration vc = ViewConfiguration.get(view.getContext());
186private int mSlop = vc.getScaledTouchSlop();
187private int mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
188private int mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
189
190...
191
192case MotionEvent.ACTION_MOVE: {
193    ...
194    float deltaX = motionEvent.getRawX() - mDownX;
195    if (Math.abs(deltaX) > mSlop) {
196        // A swipe occurred, do something
197    }
198
199...
200
201case MotionEvent.ACTION_UP: {
202    ...
203    } if (mMinFlingVelocity <= velocityX && velocityX <= mMaxFlingVelocity
204            && velocityY < velocityX) {
205        // The criteria have been satisfied, do something
206    }
207}</pre>
208
209
210<h2 id="delegate">Extend a Child View's Touchable Area</h2>
211
212<p>Android provides the {@link android.view.TouchDelegate} class to make it possible
213for a parent to extend the touchable area of a child view beyond the child's bounds.
214
215This is useful when the child has to be small, but should have a larger touch region. You can
216also use this approach to shrink the child's touch region if need be.</p>
217
218<p>In the following example, an {@link android.widget.ImageButton} is the
219"delegate view" (that is, the child whose touch area the parent will extend).
220Here is the layout file:</p>
221
222<pre>
223&lt;RelativeLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
224     android:id=&quot;@+id/parent_layout&quot;
225     android:layout_width=&quot;match_parent&quot;
226     android:layout_height=&quot;match_parent&quot;
227     tools:context=&quot;.MainActivity&quot; &gt;
228
229     &lt;ImageButton android:id=&quot;@+id/button&quot;
230          android:layout_width=&quot;wrap_content&quot;
231          android:layout_height=&quot;wrap_content&quot;
232          android:background=&quot;@null&quot;
233          android:src=&quot;@drawable/icon&quot; /&gt;
234&lt;/RelativeLayout&gt;
235</pre>
236
237<p>The snippet below does the following:</p>
238
239<ul>
240<li>Gets the parent view and posts a {@link java.lang.Runnable} on the UI thread. This ensures that the parent lays out its children before calling the {@link android.view.View#getHitRect getHitRect()} method. The {@link android.view.View#getHitRect getHitRect()} method gets the child's hit rectangle (touchable area) in the parent's coordinates.</li>
241<li>Finds the {@link android.widget.ImageButton} child view and calls {@link android.view.View#getHitRect getHitRect()} to get the bounds of the child's touchable area.</li>
242<li>Extends the bounds of the {@link android.widget.ImageButton}'s hit rectangle.</li>
243<li>Instantiates a {@link android.view.TouchDelegate}, passing in the expanded hit rectangle and the {@link android.widget.ImageButton} child view as parameters.</li>
244<li>Sets the {@link android.view.TouchDelegate} on the parent view, such that touches within the touch delegate bounds are routed to the child.</li>
245
246</ul>
247
248In its capacity as touch delegate for the {@link android.widget.ImageButton} child view, the
249parent view will receive all touch events. If the touch event occurred within the child's hit
250rectangle, the parent will pass the touch
251event to the child for handling.</p>
252
253
254
255<pre>
256public class MainActivity extends Activity {
257
258    &#64;Override
259    protected void onCreate(Bundle savedInstanceState) {
260        super.onCreate(savedInstanceState);
261        setContentView(R.layout.activity_main);
262        // Get the parent view
263        View parentView = findViewById(R.id.parent_layout);
264
265        parentView.post(new Runnable() {
266            // Post in the parent's message queue to make sure the parent
267            // lays out its children before you call getHitRect()
268            &#64;Override
269            public void run() {
270                // The bounds for the delegate view (an ImageButton
271                // in this example)
272                Rect delegateArea = new Rect();
273                ImageButton myButton = (ImageButton) findViewById(R.id.button);
274                myButton.setEnabled(true);
275                myButton.setOnClickListener(new View.OnClickListener() {
276                    &#64;Override
277                    public void onClick(View view) {
278                        Toast.makeText(MainActivity.this,
279                                "Touch occurred within ImageButton touch region.",
280                                Toast.LENGTH_SHORT).show();
281                    }
282                });
283
284                // The hit rectangle for the ImageButton
285                myButton.getHitRect(delegateArea);
286
287                // Extend the touch area of the ImageButton beyond its bounds
288                // on the right and bottom.
289                delegateArea.right += 100;
290                delegateArea.bottom += 100;
291
292                // Instantiate a TouchDelegate.
293                // "delegateArea" is the bounds in local coordinates of
294                // the containing view to be mapped to the delegate view.
295                // "myButton" is the child view that should receive motion
296                // events.
297                TouchDelegate touchDelegate = new TouchDelegate(delegateArea,
298                        myButton);
299
300                // Sets the TouchDelegate on the parent view, such that touches
301                // within the touch delegate bounds are routed to the child.
302                if (View.class.isInstance(myButton.getParent())) {
303                    ((View) myButton.getParent()).setTouchDelegate(touchDelegate);
304                }
305            }
306        });
307    }
308}</pre>
309
310