blob: 27b49dbb377c348236fa784f4e8a4ce8d65a93b6 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.view;
18
19import android.graphics.Rect;
20import android.view.MotionEvent;
21import android.view.View;
22import 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 */
35public 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 */
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 */
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}