1 /*
2  * Copyright (C) 2016 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 package com.android.launcher3.util;
17 
18 import android.graphics.Rect;
19 import android.graphics.RectF;
20 import android.view.MotionEvent;
21 import android.view.TouchDelegate;
22 import android.view.View;
23 
24 /**
25  * This class differs from the framework {@link TouchDelegate} in that it transforms the
26  * coordinates of the motion event to the provided bounds.
27  *
28  * You can also modify the bounds post construction. Since the bounds are available during layout,
29  * this avoids new object creation during every layout.
30  */
31 public class TransformingTouchDelegate extends TouchDelegate {
32     private static final Rect sTempRect = new Rect();
33 
34     private final RectF mBounds;
35 
36     private final RectF mTouchCheckBounds;
37     private float mTouchExtension;
38     private boolean mWasTouchOutsideBounds;
39 
40     private View mDelegateView;
41     private boolean mDelegateTargeted;
42 
TransformingTouchDelegate(View delegateView)43     public TransformingTouchDelegate(View delegateView) {
44         super(sTempRect, delegateView);
45 
46         mDelegateView = delegateView;
47         mBounds = new RectF();
48         mTouchCheckBounds = new RectF();
49     }
50 
setBounds(int left, int top, int right, int bottom)51     public void setBounds(int left, int top, int right, int bottom) {
52         mBounds.set(left, top, right, bottom);
53         updateTouchBounds();
54     }
55 
extendTouchBounds(float extension)56     public void extendTouchBounds(float extension) {
57         mTouchExtension = extension;
58         updateTouchBounds();
59     }
60 
updateTouchBounds()61     private void updateTouchBounds() {
62         mTouchCheckBounds.set(mBounds);
63         mTouchCheckBounds.inset(-mTouchExtension, -mTouchExtension);
64     }
65 
setDelegateView(View view)66     public void setDelegateView(View view) {
67         mDelegateView = view;
68     }
69 
70     /**
71      * Will forward touch events to the delegate view if the event is within the bounds
72      * specified in the constructor.
73      *
74      * @param event The touch event to forward
75      * @return True if the event was forwarded to the delegate, false otherwise.
76      */
onTouchEvent(MotionEvent event)77     public boolean onTouchEvent(MotionEvent event) {
78         boolean sendToDelegate = false;
79         switch (event.getAction()) {
80             case MotionEvent.ACTION_DOWN:
81                 mDelegateTargeted = mTouchCheckBounds.contains(event.getX(), event.getY());
82                 if (mDelegateTargeted) {
83                     mWasTouchOutsideBounds = !mBounds.contains(event.getX(), event.getY());
84                     sendToDelegate = true;
85                 }
86                 break;
87             case MotionEvent.ACTION_MOVE:
88                 sendToDelegate = mDelegateTargeted;
89                 break;
90             case MotionEvent.ACTION_UP:
91             case MotionEvent.ACTION_CANCEL:
92                 sendToDelegate = mDelegateTargeted;
93                 mDelegateTargeted = false;
94                 break;
95         }
96         boolean handled = false;
97         if (sendToDelegate) {
98             float x = event.getX();
99             float y = event.getY();
100             if (mWasTouchOutsideBounds) {
101                 event.setLocation(mBounds.centerX(), mBounds.centerY());
102             } else {
103                 event.offsetLocation(-mBounds.left, -mBounds.top);
104             }
105             handled = mDelegateView.dispatchTouchEvent(event);
106             event.setLocation(x, y);
107         }
108         return handled;
109     }
110 }
111