Home | History | Annotate | Download | only in gestures
      1 page.title=Managing Touch Events in a ViewGroup
      2 parent.title=Using Touch Gestures
      3 parent.link=index.html
      4 
      5 trainingnavtop=true
      6 next.title=
      7 next.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"
     38 class="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,
     47 because it's common for a {@link android.view.ViewGroup} to have children that
     48 are targets for different touch events than the {@link android.view.ViewGroup}
     49 itself. To make sure that each view correctly receives the touch events intended
     50 for it, override the {@link android.view.ViewGroup#onInterceptTouchEvent
     51 onInterceptTouchEvent()} method.</p>
     52 
     53 <h2 id="intercept">Intercept Touch Events in a ViewGroup</h2>
     54 
     55 <p>The {@link android.view.ViewGroup#onInterceptTouchEvent onInterceptTouchEvent()}
     56 method 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()}
     59 returns {@code true}, the {@link android.view.MotionEvent} is intercepted,
     60 meaning 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()}
     64 method gives a parent the chance to see any touch event before its children do.
     65 If you return {@code true} from
     66 {@link android.view.ViewGroup#onInterceptTouchEvent onInterceptTouchEvent()},
     67 the child view that was previously handling touch events
     68 receives an {@link android.view.MotionEvent#ACTION_CANCEL}, and the events from that
     69 point 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
     72 return {@code false} and simply spy on events as they travel down the view hierarchy
     73 to 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
     80 a 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,
     82 if you press buttons in the child view, or scroll the child view vertically,
     83 the parent shouldn't intercept those touch events, because the child is the
     84 intended target. In those cases,
     85 {@link android.view.ViewGroup#onInterceptTouchEvent onInterceptTouchEvent()} should
     86 return {@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.
    161 The {@link android.view.ViewGroup} calls this method when a child does not want the parent and its
    162 ancestors 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
    169 a variable called {@code mTouchSlop}. You can use the {@link
    170 android.view.ViewConfiguration} class to access common distances, speeds, and
    171 times used by the Android system.</p>
    172 
    173 
    174 <p>"Touch slop" refers to the distance in pixels a user's touch can wander
    175 before the gesture is interpreted as scrolling. Touch slop is typically used to
    176 prevent accidental scrolling when the user is performing some other touch
    177 operation, 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()}
    181 and {@link android.view.ViewConfiguration#getScaledMaximumFlingVelocity getScaledMaximumFlingVelocity()}.
    182 These methods  return the minimum and maximum velocity (respectively) to initiate a fling,
    183 as measured in pixels per second. For example:</p>
    184 
    185 <pre>ViewConfiguration vc = ViewConfiguration.get(view.getContext());
    186 private int mSlop = vc.getScaledTouchSlop();
    187 private int mMinFlingVelocity = vc.getScaledMinimumFlingVelocity();
    188 private int mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
    189 
    190 ...
    191 
    192 case 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 
    201 case 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
    213 for a parent to extend the touchable area of a child view beyond the child's bounds.
    214 
    215 This is useful when the child has to be small, but should have a larger touch region. You can
    216 also 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).
    220 Here is the layout file:</p>
    221 
    222 <pre>
    223 &lt;RelativeLayout xmlns:android=&quot;http://schemas.android.com/apk/res/android";
    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 
    248 In its capacity as touch delegate for the {@link android.widget.ImageButton} child view, the
    249 parent view will receive all touch events. If the touch event occurred within the child's hit
    250 rectangle, the parent will pass the touch
    251 event to the child for handling.</p>
    252 
    253 
    254 
    255 <pre>
    256 public 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