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 
     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      */
     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      */
    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