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 @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 @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 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 224 android:id="@+id/parent_layout" 225 android:layout_width="match_parent" 226 android:layout_height="match_parent" 227 tools:context=".MainActivity" > 228 229 <ImageButton android:id="@+id/button" 230 android:layout_width="wrap_content" 231 android:layout_height="wrap_content" 232 android:background="@null" 233 android:src="@drawable/icon" /> 234 </RelativeLayout> 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 @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 @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 @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