android之view的TouchDelegate

实话实说,之前并不知道有TouchDelegate,直到最近查看view的源码时候才发现这个新大陆.

在view中有一个私有的TouchDelegate变量:

private TouchDelegate mTouchDelegate = null;

在view中的公共方法setTouchDelegate可以给这个view设置一个TouchDelegate对象,源码如下:

    public void setTouchDelegate(TouchDelegate delegate) {
        mTouchDelegate = delegate;
    }

到了这里,你可能会问:view中的mTouchDelegate到底可以做什么?为了回答这个问题,下面分三步来解释.

第一步:首先来看看TouchDelegate类的一段原文说明,在文件TouchDelegate.java有如下说明:

Helper class to handle situations where you want a view to have a larger touch area than its

actual view bounds. The view whose touch area is changed is called the delegate view. This

class should be used by an ancestor of the delegate.

其大意是: TouchDelegate是一个工具类,其目的是让一个view在一个特定的位置拥有比自己实际的触摸区域更大的可触摸的区域.触摸区域被更改的view被称作"delegate view".这个工具类应该被"delegate view"的父view使用.

通过上面的意思,我们可以明白其实TouchDelegate的主要目的就是来扩大一个view的触摸区域的.

第二步:要深刻理解,我觉得需要查看源码,看看他是如何实现的.

查看TouchDelegate可以知道,TouchDelegate是一个很简单的类,主要有四个变量和两个方法:

 /**
     * View that should receive forwarded touch events
     */
    private View mDelegateView;//需要扩大触摸区域的view

    /**
     * Bounds in local coordinates of the containing view that should be mapped to the delegate
     * view. This rect is used for initial hit testing.
     */
    private Rect mBounds;//定义了这个扩大的触目区域

    /**
     * mBounds inflated to include some slop. This rect is to track whether the motion events
     * should be considered to be be within the delegate view.
     */
    private Rect mSlopBounds;//这是一个相对于mBounds溢出的触摸区域:实际上就是比mBounds大一点的区域(宽高分别大8),其目的时消除触摸误差.

    public TouchDelegate(Rect bounds, View delegateView) {//构造函数:bounds 表示触摸的区域;delegateView 需要扩大触摸区域的view
        mBounds = bounds;

        mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();//这里获取的是触摸滑动距离的判断:就是触摸滑动距离为mSlop时候裁判为是在触摸滑动move.其默认值为8
        mSlopBounds = new Rect(bounds);
        mSlopBounds.inset(-mSlop, -mSlop);//这里扩大触摸滑动区域(宽高扩大8),其目的时消除触摸时候的误差.达到一个触摸安全处理.
        mDelegateView = delegateView; //需要扩大触摸区域的view(需要改变触摸区域的view)
    }

    public boolean onTouchEvent(MotionEvent event) {//这是核心代码:判断当前触摸是否在这个区域:mBounds.如果在是则会让这次的触摸事件真的在mDelegateView真实的区域内.
        int x = (int)event.getX();
        int y = (int)event.getY();//记录这次触摸点位置
        boolean sendToDelegate = false;//标记这次触摸是否有效(应该传递给mDelegateView)
        boolean hit = true; //标记这次触摸是否在这个区域内(mBounds)
        boolean handled = false;

        switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Rect bounds = mBounds;

            if (bounds.contains(x, y)) {//ACTION_DOWN是否在这个区域内
                mDelegateTargeted = true;//标记这一次触摸事件在这个区域(在mDelegateView内)
                sendToDelegate = true;
            }
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_MOVE:
            sendToDelegate = mDelegateTargeted;
            if (sendToDelegate) {
                Rect slopBounds = mSlopBounds;
                if (!slopBounds.contains(x, y)) {
                    hit = false;
                }
            }
            break;
        case MotionEvent.ACTION_CANCEL:
            sendToDelegate = mDelegateTargeted;
            mDelegateTargeted = false;
            break;
        }
        if (sendToDelegate) {//这次触摸有效:则把触摸事件传递给 mDelegateView
            final View delegateView = mDelegateView;
            //模拟这次触摸真的在mDelegateView区域内:从新计算event的触摸点,以保证这次的event事件的触摸点在mDelegateView真实的区域内
            if (hit) {
                // Offset event coordinates to be inside the target view
                event.setLocation(delegateView.getWidth() / 2, delegateView.getHeight() / 2);
            } else {
                // Offset event coordinates to be outside the target view (in case it does
                // something like tracking pressed state)
                int slop = mSlop;
                event.setLocation(-(slop * 2), -(slop * 2));
            }
            handled = delegateView.dispatchTouchEvent(event);//把计算后的触摸事件传递给mDelegateView的dispatchTouchEvent使其相应触摸事件.
        }
        return handled;
    }

第三步:最后来看看view里面是何时使用mTouchDelegate.

上面说了,在view里面定义来一个变量mTouchDelegate来保存当前这个view的TouchDelegate对象,其目的是确保view有这样的功能:原本在自己区域的触摸事件实际上相应的却是别的地方(别的区域)的view.

那view是如何实现的呢? 查看源码可以知道,在view的onTouchEvent方法里面,首先就会去判断自己是否有可用的TouchDelegate对象,如果有,那么onTouchEvent方法里面首先会去执行mTouchDelegate的

onTouchEvent方法.下面是源码:

   public boolean onTouchEvent(MotionEvent event) {
        final int viewFlags = mViewFlags;

        if (DBG_MOTION) {
            Xlog.d(VIEW_LOG_TAG, "(View)onTouchEvent 1: event = " + event + ",mTouchDelegate = "
                    + mTouchDelegate + ",enable = " + isEnabled() + ",clickable = " + isClickable()
                    + ",isLongClickable = " + isLongClickable() + ",this = " + this);
        }

        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            /// M: we need to reset the pressed state or remove prepressed callback either up or cancel event happens.
            final int action = event.getAction();
            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
                if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                    setPressed(false);
                } else if ((mPrivateFlags & PFLAG_PREPRESSED) != 0) {
                    Xlog.d(VIEW_LOG_TAG, "View onTouch event, if view is DISABLED & PFLAG_PREPRESSED, remove callback mPrivateFlags = "
                                + mPrivateFlags + ", this = " + this);
                    removeTapCallback();
                }
            }
            // A disabled view that is clickable still consumes the touch
            // events, it just doesn't respond to them.
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }

        if (mTouchDelegate != null) {//判断是否有TouchDelegate对象
            if (mTouchDelegate.onTouchEvent(event)) {执行onTouchEvent
                return true;//如果这个触摸事件在TouchDelegate设置的区域内,这返回.不会执行其他
            }
        }
        .....

最后,来看看一个实例.下面是一个父view夸大自己view触摸区域的方法:

    public static void enlargeBtnTouchScope(View parent, Button btn, Rect rect, float width, float height){
		rect.top = btn.getTop();
		rect.bottom = btn.getBottom();
		rect.left = btn.getLeft();
		rect.right = btn.getRight();

		rect.top -= height;
		rect.bottom += height;
		rect.left -= width;
		rect.right += width;

		parent.setTouchDelegate(new TouchDelegate(rect, btn));
    }

上面的代码可以使得在parent_view的button可以更好地被触摸到(被点击到).

时间: 2024-10-08 08:48:43

android之view的TouchDelegate的相关文章

Android 扩大view点击范围

Android4.0设计规定的有效可触摸的UI元素标准是48dp,转化为一个物理尺寸约为9毫米.7~10毫米,这是一个用户手指能准确并且舒适触摸的区域. 如下图所示,你的UI元素可能小于48dp,图标仅有32dp,按钮仅有40dp,但是他们的实际可操作焦点区域最好都应达到48dp的大小. 为使小的UI区域获得良好的触摸交互,根据View的特性,目前碰到了两种情况: 1.如ImageView,设置其padding值,可触摸区域将向外扩展: 2.如Button,设置其padding值,可触摸区域不变

Android中View绘制流程以及invalidate()等相关方法分析

前言: 本文是我读<Android内核剖析>第13章----View工作原理总结而成的,在此膜拜下作者 .同时真挚地向渴望了解 Android 框架层的网友,推荐这本书,希望你们能够在Android开发里学到更多的知识 . 整个View树的绘图流程是在ViewRoot.java类的performTraversals()函数展开的,该函数做的执行过程可简单概况为 根据之前设置的状态,判断是否需要重新计算视图大小(measure).是否重新需要安置视图的位置(layout).以及是否需要重绘 (d

Android中View的绘制过程 onMeasure方法简述

Android中View的绘制过程 当Activity获得焦点时,它将被要求绘制自己的布局,Android framework将会处理绘制过程,Activity只需提供它的布局的根节点. 绘制过程从布局的根节点开始,从根节点开始测量和绘制整个layout tree. 每一个ViewGroup 负责要求它的每一个孩子被绘制,每一个View负责绘制自己. 因为整个树是按顺序遍历的,所以父节点会先被绘制,而兄弟节点会按照它们在树中出现的顺序被绘制. 绘制是一个两遍(two pass)的过程:一个mea

Android自定义View探索(一)—生命周期

Activity代码: public class FiveActivity extends AppCompatActivity { private MyView myView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.e("log", "Activity生命周期:onCreate"); setConte

Android 中View的绘制机制源码分析 一

尊重原创: http://blog.csdn.net/yuanzeyao/article/details/46765113 差不多半年没有写博客了,一是因为工作比较忙,二是觉得没有什么内容值得写,三是因为自己越来越懒了吧,不过最近我对Android中View的绘制机制有了一些新的认识,所以想记录下来并分享给大家.在之后的几篇博客中,我会给大家分享如下的内容: 1.View中measure(),layout(),draw()函数执行过程分析,带领大家详细分析View的尺寸测量过程,位置计算,并最终

Android的View和ViewGroup分析

1. 概念 Android中的View与我们以前理解的"视图"不同.在Android中,View比视图具有更广的含义,它包含了用户交互和显示,更像Windows操作系统中的window. ViewGroup是View的子类,所以它也具有View的特性,但它主要用来充当View的容器,将其中的View视作自己的孩子,对它的子View进行管理,当然它的孩子也可以是ViewGroup类型. ViewGroup(树根)和它的孩子们(View和ViewGroup)以树形结构形成了一个层次结构,V

android 中View, Window, Activity, WindowManager,ViewRoot几者之间的关系

(1)View:最基本的UI组件,表示屏幕上的一个矩形区域. (2)Window: 表示一个窗口,不一定有屏幕那么大,可以很大也可以很小: 它包含一个View tree和窗口的layout 参数. View tree的root View可以通过getDecorView得到.还可以设置Window的Content View. (3)Activity:Activity包含一个Window,该Window在Activity的attach方法中通过调用 PolicyManager.makeNewWind

Android 自定义View视图

创建全新的视图将满足我们独特的UI需求. 本文介绍在指南针开发中会用到的罗盘的界面UI,通过继承View类实现的自定义视图,以此来深刻了解自定义视图. 实现效果图: 源代码: 布局文件activity_main(其中CompassView继承View类): <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.

Android 计算view 的高度

上午在做一个QuickAction里嵌套一个ListView,在Demo运行没事,结果引入到我的项目里,发现我先让它在Button上面,结果是无视那个Button的高度,这很明显,就是那个Button的高度计算不正确. 看了下别人的建议,大概分为三类: 参数设置: ? mRootView.measure(0, 0); 在draw之前回调: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ViewTreeObserver vto =view.getViewTreeObs