Home | History | Annotate | Download | only in view
      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 import android.view.MotionEvent;
     21 import android.view.View;
     22 import 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  */
     35 public 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 }
    154