• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
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  */
32 public 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
47      * should be considered to be be within the delegate view.
48      */
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     /**
67      * The touchable region of the View extends to the left of its
68      * actual extent.
69      */
70     public static final int TO_LEFT = 4;
71 
72     /**
73      * The touchable region of the View extends to the right of its
74      * actual extent.
75      */
76     public static final int TO_RIGHT = 8;
77 
78     private int mSlop;
79 
80     /**
81      * Constructor
82      *
83      * @param bounds Bounds in local coordinates of the containing view that should be mapped to
84      *        the delegate view
85      * @param delegateView The view that should receive motion events
86      */
TouchDelegate(Rect bounds, View delegateView)87     public TouchDelegate(Rect bounds, View delegateView) {
88         mBounds = bounds;
89 
90         mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
91         mSlopBounds = new Rect(bounds);
92         mSlopBounds.inset(-mSlop, -mSlop);
93         mDelegateView = delegateView;
94     }
95 
96     /**
97      * Will forward touch events to the delegate view if the event is within the bounds
98      * specified in the constructor.
99      *
100      * @param event The touch event to forward
101      * @return True if the event was forwarded to the delegate, false otherwise.
102      */
onTouchEvent(MotionEvent event)103     public boolean onTouchEvent(MotionEvent event) {
104         int x = (int)event.getX();
105         int y = (int)event.getY();
106         boolean sendToDelegate = false;
107         boolean hit = true;
108         boolean handled = false;
109 
110         switch (event.getAction()) {
111         case MotionEvent.ACTION_DOWN:
112             Rect bounds = mBounds;
113 
114             if (bounds.contains(x, y)) {
115                 mDelegateTargeted = true;
116                 sendToDelegate = true;
117             }
118             break;
119         case MotionEvent.ACTION_UP:
120         case MotionEvent.ACTION_MOVE:
121             sendToDelegate = mDelegateTargeted;
122             if (sendToDelegate) {
123                 Rect slopBounds = mSlopBounds;
124                 if (!slopBounds.contains(x, y)) {
125                     hit = false;
126                 }
127             }
128             break;
129         case MotionEvent.ACTION_CANCEL:
130             sendToDelegate = mDelegateTargeted;
131             mDelegateTargeted = false;
132             break;
133         }
134         if (sendToDelegate) {
135             final View delegateView = mDelegateView;
136 
137             if (hit) {
138                 // Offset event coordinates to be inside the target view
139                 event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
140             } else {
141                 // Offset event coordinates to be outside the target view (in case it does
142                 // something like tracking pressed state)
143                 int slop = mSlop;
144                 event.setLocation(-(slop * 2), -(slop * 2));
145             }
146             handled = delegateView.dispatchTouchEvent(event);
147         }
148         return handled;
149     }
150 }
151