1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view; 18 19 import android.graphics.Rect; 20 import android.view.MotionEvent; 21 import android.view.View; 22 import android.view.ViewConfiguration; 23 24 /** 25 * Helper class to handle situations where you want a view to have a larger touch area than its 26 * actual view bounds. The view whose touch area is changed is called the delegate view. This 27 * class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an 28 * instance that specifies the bounds that should be mapped to the delegate and the delegate 29 * view itself. 30 * <p> 31 * The ancestor should then forward all of its touch events received in its 32 * {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}. 33 * </p> 34 */ 35 public class TouchDelegate { 36 37 /** 38 * View that should receive forwarded touch events 39 */ 40 private View mDelegateView; 41 42 /** 43 * Bounds in local coordinates of the containing view that should be mapped to the delegate 44 * view. This rect is used for initial hit testing. 45 */ 46 private Rect mBounds; 47 48 /** 49 * mBounds inflated to include some slop. This rect is to track whether the motion events 50 * should be considered to be be within the delegate view. 51 */ 52 private Rect mSlopBounds; 53 54 /** 55 * True if the delegate had been targeted on a down event (intersected mBounds). 56 */ 57 private boolean mDelegateTargeted; 58 59 /** 60 * The touchable region of the View extends above its actual extent. 61 */ 62 public static final int ABOVE = 1; 63 64 /** 65 * The touchable region of the View extends below its actual extent. 66 */ 67 public static final int BELOW = 2; 68 69 /** 70 * The touchable region of the View extends to the left of its 71 * actual extent. 72 */ 73 public static final int TO_LEFT = 4; 74 75 /** 76 * The touchable region of the View extends to the right of its 77 * actual extent. 78 */ 79 public static final int TO_RIGHT = 8; 80 81 private int mSlop; 82 83 /** 84 * Constructor 85 * 86 * @param bounds Bounds in local coordinates of the containing view that should be mapped to 87 * the delegate view 88 * @param delegateView The view that should receive motion events 89 */ TouchDelegate(Rect bounds, View delegateView)90 public TouchDelegate(Rect bounds, View delegateView) { 91 mBounds = bounds; 92 93 mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop(); 94 mSlopBounds = new Rect(bounds); 95 mSlopBounds.inset(-mSlop, -mSlop); 96 mDelegateView = delegateView; 97 } 98 99 /** 100 * Will forward touch events to the delegate view if the event is within the bounds 101 * specified in the constructor. 102 * 103 * @param event The touch event to forward 104 * @return True if the event was forwarded to the delegate, false otherwise. 105 */ onTouchEvent(MotionEvent event)106 public boolean onTouchEvent(MotionEvent event) { 107 int x = (int)event.getX(); 108 int y = (int)event.getY(); 109 boolean sendToDelegate = false; 110 boolean hit = true; 111 boolean handled = false; 112 113 switch (event.getAction()) { 114 case MotionEvent.ACTION_DOWN: 115 Rect bounds = mBounds; 116 117 if (bounds.contains(x, y)) { 118 mDelegateTargeted = true; 119 sendToDelegate = true; 120 } 121 break; 122 case MotionEvent.ACTION_UP: 123 case MotionEvent.ACTION_MOVE: 124 sendToDelegate = mDelegateTargeted; 125 if (sendToDelegate) { 126 Rect slopBounds = mSlopBounds; 127 if (!slopBounds.contains(x, y)) { 128 hit = false; 129 } 130 } 131 break; 132 case MotionEvent.ACTION_CANCEL: 133 sendToDelegate = mDelegateTargeted; 134 mDelegateTargeted = false; 135 break; 136 } 137 if (sendToDelegate) { 138 final View delegateView = mDelegateView; 139 140 if (hit) { 141 // Offset event coordinates to be inside the target view 142 event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2); 143 } else { 144 // Offset event coordinates to be outside the target view (in case it does 145 // something like tracking pressed state) 146 int slop = mSlop; 147 event.setLocation(-(slop * 2), -(slop * 2)); 148 } 149 handled = delegateView.dispatchTouchEvent(event); 150 } 151 return handled; 152 } 153 } 154