• 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 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 actual extent.
68      */
69     public static final int TO_LEFT = 4;
70 
71     /**
72      * The touchable region of the View extends to the right of its actual extent.
73      */
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      */
TouchDelegate(Rect bounds, View delegateView)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      */
onTouchEvent(MotionEvent event)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.getActionMasked()) {
109             case MotionEvent.ACTION_DOWN:
110                 mDelegateTargeted = mBounds.contains(x, y);
111                 sendToDelegate = mDelegateTargeted;
112                 break;
113             case MotionEvent.ACTION_POINTER_DOWN:
114             case MotionEvent.ACTION_POINTER_UP:
115             case MotionEvent.ACTION_UP:
116             case MotionEvent.ACTION_MOVE:
117                 sendToDelegate = mDelegateTargeted;
118                 if (sendToDelegate) {
119                     Rect slopBounds = mSlopBounds;
120                     if (!slopBounds.contains(x, y)) {
121                         hit = false;
122                     }
123                 }
124                 break;
125             case MotionEvent.ACTION_CANCEL:
126                 sendToDelegate = mDelegateTargeted;
127                 mDelegateTargeted = false;
128                 break;
129         }
130         if (sendToDelegate) {
131             final View delegateView = mDelegateView;
132 
133             if (hit) {
134                 // Offset event coordinates to be inside the target view
135                 event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
136             } else {
137                 // Offset event coordinates to be outside the target view (in case it does
138                 // something like tracking pressed state)
139                 int slop = mSlop;
140                 event.setLocation(-(slop * 2), -(slop * 2));
141             }
142             handled = delegateView.dispatchTouchEvent(event);
143         }
144         return handled;
145     }
146 }
147