blob: dc50fa1d60367c01668179c7e09a3221b1c5cf65 [file] [log] [blame]
Justin Klaassen10d07c82017-09-15 17:58:39 -04001/*
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;
20
21/**
22 * Helper class to handle situations where you want a view to have a larger touch area than its
23 * actual view bounds. The view whose touch area is changed is called the delegate view. This
24 * class should be used by an ancestor of the delegate. To use a TouchDelegate, first create an
25 * instance that specifies the bounds that should be mapped to the delegate and the delegate
26 * view itself.
27 * <p>
28 * The ancestor should then forward all of its touch events received in its
29 * {@link android.view.View#onTouchEvent(MotionEvent)} to {@link #onTouchEvent(MotionEvent)}.
30 * </p>
31 */
32public class TouchDelegate {
33
34 /**
35 * View that should receive forwarded touch events
36 */
37 private View mDelegateView;
38
39 /**
40 * Bounds in local coordinates of the containing view that should be mapped to the delegate
41 * view. This rect is used for initial hit testing.
42 */
43 private Rect mBounds;
44
45 /**
46 * mBounds inflated to include some slop. This rect is to track whether the motion events
Justin Klaassen46c77c22017-10-30 17:25:37 -040047 * should be considered to be within the delegate view.
Justin Klaassen10d07c82017-09-15 17:58:39 -040048 */
49 private Rect mSlopBounds;
50
51 /**
52 * True if the delegate had been targeted on a down event (intersected mBounds).
53 */
54 private boolean mDelegateTargeted;
55
56 /**
57 * The touchable region of the View extends above its actual extent.
58 */
59 public static final int ABOVE = 1;
60
61 /**
62 * The touchable region of the View extends below its actual extent.
63 */
64 public static final int BELOW = 2;
65
66 /**
Justin Klaassen46c77c22017-10-30 17:25:37 -040067 * The touchable region of the View extends to the left of its actual extent.
Justin Klaassen10d07c82017-09-15 17:58:39 -040068 */
69 public static final int TO_LEFT = 4;
70
71 /**
Justin Klaassen46c77c22017-10-30 17:25:37 -040072 * The touchable region of the View extends to the right of its actual extent.
Justin Klaassen10d07c82017-09-15 17:58:39 -040073 */
74 public static final int TO_RIGHT = 8;
75
76 private int mSlop;
77
78 /**
79 * Constructor
80 *
81 * @param bounds Bounds in local coordinates of the containing view that should be mapped to
82 * the delegate view
83 * @param delegateView The view that should receive motion events
84 */
85 public TouchDelegate(Rect bounds, View delegateView) {
86 mBounds = bounds;
87
88 mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
89 mSlopBounds = new Rect(bounds);
90 mSlopBounds.inset(-mSlop, -mSlop);
91 mDelegateView = delegateView;
92 }
93
94 /**
95 * Will forward touch events to the delegate view if the event is within the bounds
96 * specified in the constructor.
97 *
98 * @param event The touch event to forward
99 * @return True if the event was forwarded to the delegate, false otherwise.
100 */
101 public boolean onTouchEvent(MotionEvent event) {
102 int x = (int)event.getX();
103 int y = (int)event.getY();
104 boolean sendToDelegate = false;
105 boolean hit = true;
106 boolean handled = false;
107
108 switch (event.getAction()) {
Justin Klaassen46c77c22017-10-30 17:25:37 -0400109 case MotionEvent.ACTION_DOWN:
110 mDelegateTargeted = mBounds.contains(x, y);
111 sendToDelegate = mDelegateTargeted;
112 break;
113 case MotionEvent.ACTION_UP:
114 case MotionEvent.ACTION_MOVE:
115 sendToDelegate = mDelegateTargeted;
116 if (sendToDelegate) {
117 Rect slopBounds = mSlopBounds;
118 if (!slopBounds.contains(x, y)) {
119 hit = false;
120 }
Justin Klaassen10d07c82017-09-15 17:58:39 -0400121 }
Justin Klaassen46c77c22017-10-30 17:25:37 -0400122 break;
123 case MotionEvent.ACTION_CANCEL:
124 sendToDelegate = mDelegateTargeted;
125 mDelegateTargeted = false;
126 break;
Justin Klaassen10d07c82017-09-15 17:58:39 -0400127 }
128 if (sendToDelegate) {
129 final View delegateView = mDelegateView;
130
131 if (hit) {
132 // Offset event coordinates to be inside the target view
133 event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
134 } else {
135 // Offset event coordinates to be outside the target view (in case it does
136 // something like tracking pressed state)
137 int slop = mSlop;
138 event.setLocation(-(slop * 2), -(slop * 2));
139 }
140 handled = delegateView.dispatchTouchEvent(event);
141 }
142 return handled;
143 }
144}