Android应用开发之自定义View触摸相关工具类全解

背景

最近有些乱,各种事情,各种交叉。好在还有一点上进心,于是继续将自定义这个系列的核心知识再梳理一下吧。关于自定义控件前面博文说过了,这里不会教你拿来主义,只授之以渔,如果你喜欢拿来主义,不好意思,请绕行,如果你喜欢得渔,那请继续。

前面我们已经叙述过了几篇关于自定义View涉及的东西,大家可以自己回过头去看我之前的博客,譬如事件处理、坐标系、工具类等。下面我们还是继续补充一些常用的自定义控件工具类。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

ViewConfiguration基础参数工具类

ViewConfiguration这个类主要提供了一些自定义控件用到的标准常量,譬如尺寸、滑动距离、敏感度等,当我们自定义控件时就可以直接使用他来避免自己做一些测试。下面是该类的源码注视,使用时直接可以参考,没啥特殊的逻辑东西,所以不再进行源码分析。如下:

public class ViewConfiguration {
    ......
    //不推荐使用,推荐ViewConfiguration.get(Context)获取实例
    public ViewConfiguration() {}
    public static ViewConfiguration get(Context context) {}
    //不推荐使用,推荐getScaledScrollBarSize()代替;获取水平滚动条的宽或垂直滚动条的高
    public static int getScrollBarSize() {}
    public int getScaledScrollBarSize() {}
    //滚动条褪去消失的持续时间
    public static int getScrollBarFadeDuration() {}
    //滚动条消失的延迟时间
    public static int getScrollDefaultDelay() {}
    //不推荐使用,推荐getScaledFadingEdgeLength()代替;褪去边缘的长度
    public static int getFadingEdgeLength() {}
    public int getScaledFadingEdgeLength() {}
    //按下的持续时间长度
    public static int getPressedStateDuration() {}
    //按住状态转变为长按状态需要的时间
    public static int getLongPressTimeout() {}
    //重新按键判断时间
    public static int getKeyRepeatTimeout() {}
    //重复按键延迟的时间
    public static int getKeyRepeatDelay() {}
    //判断是单击还是滚动的时间,在这个时间内没有移动则是单击,否则是滚动
    public static int getTapTimeout() {}
    //在这个时间内没有完成这个点击,那么就认为是一个点击事件
    public static int getJumpTapTimeout() {}
    //得到双击间隔时间,在这个时间内是双击,否则是单击
    public static int getDoubleTapTimeout() {}
    //不推荐使用,推荐getScaledEdgeSlop()代替;判断是否滑动事件
    public static int getEdgeSlop() {}
    public int getScaledEdgeSlop() {}
    //不推荐使用,推荐getScaledTouchSlop()代替;滑动的时候,手的移动要大于这个距离才算移动
    public static int getTouchSlop() {}
    public int getScaledTouchSlop() {}
    //触摸边沿padding区域的判断
    public int getScaledPagingTouchSlop() {}
    //不推荐使用,推荐getScaledDoubleTapSlop()代替;判断是否双击的阈值
    public static int getDoubleTapSlop() {}
    public int getScaledDoubleTapSlop() {}
    //不推荐使用,推荐getScaledWindowTouchSlop()代替;触摸窗体边沿区域判断
    public static int getWindowTouchSlop() {}
    public int getScaledWindowTouchSlop() {}
    //不推荐使用,推荐getScaledMinimumFlingVelocity()代替;得到滑动的最小速度, 以像素/每秒来进行计算
    public static int getMinimumFlingVelocity() {}
    public int getScaledMinimumFlingVelocity() {}
    //不推荐使用,推荐getScaledMaximumFlingVelocity()代替;得到滑动的最大速度, 以像素/每秒来进行计算
    public static int getMaximumFlingVelocity() {}
    public int getScaledMaximumFlingVelocity() {}
    //不推荐使用,推荐getScaledMaximumDrawingCacheSize()代替;获取最大的图形可缓存大小,单位bytes
    public static int getMaximumDrawingCacheSize() {}
    public int getScaledMaximumDrawingCacheSize() {}
    ......
}

有了上面这个工具类,我们在自定义控件处理滑动手势等判断时就可以很方便的判断出临界值等问题,不用我们再去自己测试定义一个近似的值来代替。

特别注意: ViewConfiguration还有一个在support包中的兼容类ViewConfigurationCompat,使用时请注意一下。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

Scroller加强版OverScroller回弹工具类

之前有篇博客说到了Scroller的源码浅析,其实Scroller在API 1就出现了,而这里要说的Scroller加强版OverScroller在API 9才出现,所以功能指定比之前的Scroller强大,支持了回弹效果(关于不同的回弹效果我们可以自定义不同的动画插值器即可),不过原理基本和之前分析的Scroller源码一样,所以这里我们不会再对OverScroller源码分析,只对他和Scroller的差异进行说明,下面我们来看看。

OverScroller在Scroller类基础上多出来的方法:

方法 含义
isOverScrolled() 返回当前的位置是否有效或者是否超出滚动边界。
springBack(int startX, int startY, int minX, int maxX, int minY, int maxY) 当你想回滚的时候调用这个方法,回滚的范围在有效的坐标范围内。
fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY, int overX, int overY) 同Scroller的,只是最后两个参数含义为fling滚动超过有效值的范围。
notifyHorizontalEdgeReached(int startX, int finalX, int overX) 通知水平滚动是否到达边界。
notifyVerticalEdgeReached(int startY, int finalY, int overY) 同上。

关于Scroller的基本使用流程可以参见我之前博客Scroller源码浅析和ViewDragHelper源码浅析两篇文章,如果需要深入理解可以看看官方ScrollView的实现,其就完全使用了OverScroller。

特别注意: Scroller(OverScroller)这货也有一个在support兼容包的兼容类ScrollerCompat,使用时请留意一下。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

VelocityTracker手势速率工具类

VelocityTracker主要用跟踪触摸屏事件(Flinging及其他Gestures手势事件等)的速率。我们在拿到实例后可以通过computeCurrentVelocity(int)来初始化速率的单位,然后接着通过addMovement(MotionEvent)方法将MotionEvent加入VelocityTracker实例中,然后在需要的地方通过getXVelocity() 或getXVelocity()获得横向和竖向的速率即可。

下面给出相关的API说明(VelocityTracker许多方法都是native实现的):

public final class VelocityTracker {
    //获取VelocityTracker实例
    static public VelocityTracker obtain() {}
    public static VelocityTracker obtain(String strategy) {}
    //回收后代表你不需要使用了,系统将此对象在此分配到其他请求者
    public void recycle() {}
    //清空回到初始状态,computeCurrentVelocity都被reset了
    public void clear() {}
        //将事件加入到VelocityTracker类实例中
    public void addMovement(MotionEvent event) {}
    //unitis表示速率的基本时间单位,1表示一毫秒时间单位内运动了多少个像素
    public void computeCurrentVelocity(int units) {}
    //同上,floatVelocity表示速率的最大值,超过最大值的都返回最大值
    public void computeCurrentVelocity(int units, float maxVelocity) {}
        //获取xy方向速率
    public float getXVelocity() {}
    public float getYVelocity() {}
        //获取xy速率,id为event的pointid
    public float getXVelocity(int id) {}
    public float getYVelocity(int id) {}
}

有了上面这些手势速率的检测工具类,下面我们来看下他的一些通用模板:

VelocityTracker mVelocityTracker = null;
@Override
public boolean onTouchEvent(MotionEvent event){
    int action = event.getAction();
    switch(action){
    case MotionEvent.ACTION_DOWN:
        if(mVelocityTracker == null){
        mVelocityTracker = VelocityTracker.obtain();
        }else{
        mVelocityTracker.clear();
        }
        mVelocityTracker.addMovement(event);
        break;
    case MotionEvent.ACTION_MOVE:
        mVelocityTracker.addMovement(event);
        mVelocityTracker.computeCurrentVelocity(1000); 

        Log.i("X = "+mVelocityTracker.getXVelocity());
        Log.i("Y = "+mVelocityTracker.getYVelocity());
        break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_CANCEL:
        mVelocityTracker.recycle();
        break;
    }
    return true;
} 

关于速率检测类的知识就介绍到这里,没啥新鲜的。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

GestureDetector手势工具类

除了我们通过onTouchEvent()自己处理一堆复杂的手势以外,其实Android给我们提供了现成的便捷方式,那就是GestureDetector手势监听类,如下:

public class GestureDetector {
    public interface OnGestureListener {
    //ACTION_DOWN时触发
        boolean onDown(MotionEvent e);
    //ACTION_DOWN了过一会还没有滑动时触发,onDown->onShowPress->onLongPress
        void onShowPress(MotionEvent e);
    //ACTION_DOWN后没有滑动(onScroll)且没有长按(onLongPress)接着ACTION_UP时触发
        boolean onSingleTapUp(MotionEvent e);
    //滑动时实时触发
        boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
    //ACTION_DOWN长按时触发
        void onLongPress(MotionEvent e);
    //触摸滑动一定距离后松手ACTION_UP时触发,后参数为速率
        boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
    }

    public interface OnDoubleTapListener {
    //ACTION_DOWN后没有滑动(onScroll)且没有长按(onLongPress)接着ACTION_UP时触发
        boolean onSingleTapConfirmed(MotionEvent e);
    //双击的第二下ACTION_DOWN时触发
        boolean onDoubleTap(MotionEvent e);
    //双击的第二下ACTION_DOWN和ACTION_UP都会触发,e.getAction()区别
        boolean onDoubleTapEvent(MotionEvent e);
    }

    public interface OnContextClickListener {
    //context点击触发,与View#onGenericMotionEvent(MotionEvent)相关,不常用
        boolean onContextClick(MotionEvent e);
    }

    public static class SimpleOnGestureListener implements OnGestureListener, OnDoubleTapListener,
            OnContextClickListener {
    ......
    //OnGestureListener、OnDoubleTapListener、OnContextClickListener所有接口的默认实现
    ......
    }

    //各种推荐的不推荐的构造方法
    @Deprecated
    public GestureDetector(OnGestureListener listener, Handler handler) {}
    @Deprecated
    public GestureDetector(OnGestureListener listener) {}
    public GestureDetector(Context context, OnGestureListener listener) {}
    public GestureDetector(Context context, OnGestureListener listener, Handler handler) {}
    public GestureDetector(Context context, OnGestureListener listener, Handler handler,
            boolean unused) {}
    //其他两类回调接口的设置,OnGestureListener必须在构造中就处理掉
    public void setOnDoubleTapListener(OnDoubleTapListener onDoubleTapListener) {}
    public void setContextClickListener(OnContextClickListener onContextClickListener) {}
    //一些处理判断方法
    public void setIsLongpressEnabled(boolean isLongpressEnabled) {}
    public boolean isLongpressEnabled() {}
    public boolean onTouchEvent(MotionEvent ev) {}
    public boolean onGenericMotionEvent(MotionEvent ev) {}
}

有了上面这些GestureDetector手势工具类的基本API介绍之后我们就可以各种使用了,没啥特殊的介绍。

特别注意: 其实手势相关的东西还有Gesture类等GestureOverlayView手势创建识别类的,这里不作介绍,作为拓展。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

View及ViewGroup触摸事件总结

关于View触摸屏事件的传递机制源码分析其实可以参考我前面写的博客,这篇文章既然是总结,那就是只给出结论,相关分析请看前面的博文。

触摸事件传递源Activity:

Activity的dispatchTouchEvent()方法将事件传递给它的根布局ViewGroup(即调用根布局ViewGroup的dispatchTouchEvent()方法,该方法会对事件进行如下情况处理:

  • 如果根布局ViewGroup及其内部子布局控件均没处理(此时根布局ViewGroup的dispatchTouchEvent()方法返回false)则调用Activity自己的onTouchEvent()方法;如果Activity自己的onTouchEvent()方法仍然没有处理(返回false)则该事件处理宣告结束。
  • 如果根布局ViewGroup的dispatchTouchEvent()方法返回true则表明根布局中处理了这一次事件,此时就不会再调用Activity的onTouchEvent()方法了(因为Activity没有父控件且不能设置触摸监听OnTouchListener,所以没有onInterceptTouchEvent()方法)。

触摸事件传递View级别处理:

这里所谓的View级别泛指其内部不包含子控件(已经为最小控件单位)的View,当该View的父级ViewGroup触发该View的dispatchTouchEvent()方法时,由于该View没有子控件可以被继续派发,所以事件只能自己调度自己相关方法。测试的调度如下:

  • 如果该View注册了OnTouchListener,则优先调用OnTouchListener的onTouch()方法,如果onTouch()方法返回false则继续调运该View的onTouchEvent()方法,如果onTouch()方法返回true则该View的dispatchTouchEvent()方法直接返回true。
  • 如果该View没有注册OnTouchListener则直接调用该View的onTouchEvent()方法,该方法返回true、false决定了该View的dispatchTouchEvent()方法返回值。

触摸事件传递ViewGroup级别处理:

ViewGroup的dispatchTouchEvent()方法被其父布局 (父ViewGroup或者Activity)调用,当前ViewGroup的dispatchTouchEvent()方法主要任务就是为子控件派发事件(调运子控件的dispatchTouchEvent()),同时向父级布局返回事件处理情况。

不过在当前ViewGroup的dispatchTouchEvent()方法向子控件派发事件之前我们在当前ViewGroup里是可以通过自己的onInterceptTouchEvent()方法来决定触摸事件是否拦截(当前ViewGroup的onInterceptTouchEvent()返回true则不再传递给自己的子控件,而是当前ViewGroup自己处理,接着将处理结果告诉父控件;返回false则不拦截(继续传递给子控件,如果子控件的dispatchTouchEvent()方法都返回false则ViewGroup就尝试自己处理事件,然后告诉父布局自己处理的结果)。

一次完整的事件流程处理:

综合从Activity到根ViewGroup到中间ViewGroup,再到View的事件处理流程,我们要注意其传递过程中的下面几点:

  • 一次完整的事件触发可以分为ACTION_DOWN->[ACTION_MOVE]->ACTION_UP。当我们手指按下派发ACTION_DOWN事件时,如果我们当前层级的View或者ViewGroup的onTouch()或onTouchEvent()方法返回false,则当前层级的View或者ViewGroup的onTouch()或onTouchEvent()方法就再也接收不到其他事件了,直到下次新的触摸事件(ACTION_DOWN)开始。
  • 在一次完整的事件传递(ACTION_DOWN->[ACTION_MOVE]->ACTION_UP)过程中只要当前ViewGroup的onInterceptTouchEvent()方法有一次返回true则当前ViewGroup将会拦截这次事件传递的全部后续触发事件,同时这些后续触发事件都不会再触发当前ViewGroup的onInterceptTouchEvent()方法(直到下次ACTION_DOWN来临),同时向之前处理事件的子布局传递一个ACTION_CANCEL事件。如果当前ViewGroup的onInterceptTouchEvent()方法返回false,则本次传递的每个事件来临时都会触发当前ViewGroup的onInterceptTouchEvent()方法。
  • 只有ViewGroup才有onInterceptTouchEvent()方法,因为最小单位的View不具备再往下派发事件的能力,它只会直接调用自己的onTouch()和onTouchEvent()方法。
  • 当父ViewGroup截获了当前传递事件,常理来说其内部的子布局View或者ViewGroup就不能够再收到派发事件了;但是我们有一种方法可以阻止父ViewGroup截获传递的事件(getParent().requestDisallowInterceptTouchEvent(true);),一旦子布局View或者ViewGroup收到触摸事件后调用这个方法则父ViewGroup就不会再调用她自己的onInterceptTouchEvent()方法了,直到事件处理完毕再getParent().requestDisallowInterceptTouchEvent(false);即可。

到此View的触摸事件传递也就总结完成了,使用中牢记这些准则即可,当然也推荐查看源码。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

总结

可以看见,关于自定义控件的基础触摸相关的东西其实差不多也就这么多了,有了上面这些玩意你也基本上就能够玩转Android自定义控件触摸相关的蛋疼处理了,不用再苦苦思索了。

这一篇博文没有附带任何例子,因为是一个总结性的文章,相关总结到的东西在我前面的博文中基本都有深入的分析文章,所以如果你感兴趣可以翻翻我之前的博文,谢谢喏。

【工匠若水 http://blog.csdn.net/yanbober 未经允许严禁转载,请尊重作者劳动成果。私信联系我

时间: 2024-10-14 00:09:58

Android应用开发之自定义View触摸相关工具类全解的相关文章

【Android 应用开发】 自定义 View 组件 -- 圆形进度条

转载著名出处 : http://blog.csdn.net/shulianghan/article/details/40351487 代码下载 : -- CSDN 下载地址 : http://download.csdn.net/detail/han1202012/8069497 ; -- GitHub 地址 : https://github.com/han1202012/CircleProcess.git ; -- 工程示例 : 一. 相关知识点解析 1. 自定义 View 组件构造方法 构造方

Android开发之自定义View专题(二):自定义饼图

在图表里面,常用的图标一般为折线图.柱形图和饼图,上周,博主已经将柱形图分享.在博主的项目里面其实还用到了饼图,但没用到折线图.其实学会了其中一个,再去写其他的,应该都是知道该怎么写的,原理都是自己绘制图形,然后获取触摸位置判定点击事件.好了,废话不多说,直接上今天的饼图的效果图 这次也是博主从项目里面抽离出来的,这次的代码注释会比上次的柱形图更加的详细,更加便于有兴趣的朋友一起学习.图中的那个圆形指向箭头不属于饼图的部分,是在布局文件中为了美化另外添加进去的,有兴趣的朋友可以下载完整的项目下来

详解iOS开发之自定义View

iOS开发之自定义View是本文要将介绍的内容,iOS SDK中的View是UIView,我们可以很方便的自定义一个View.创建一个 Window-based Application程序,在其中添加一个Hypnosister的类,这个类选择继承UIObject.修改这个类,使他继承:UIView @interface HypnosisView : UIView 自定义View的关键是定义drawRect: 方法,因为主要是通过重载这个方法,来改变view的外观.例如,可以使用下面代码绘制一个很

Android自定义View——实现理财类APP七日年化收益折线图效果

这段时间的自定义View学习,学会了绘制柱状图.绘制折线图.绘制进度控件,那我们今天就来聊聊另外一种自定义的View,这就是我们常见的七日年化收益折线图效果.先看看长什么样. 这就是效果图了,元素相对而言还是比较多的,这里有线.柱状图.文字.折线.点等等.看起来好像很复杂,但是呢,只要一步一步的实现,那还是可以达到这种效果的,之前我们说过的, 自定义View,就像是在photo shop里面画图,想要什么就画什么,我们可以有很多的画笔工具,也可以有很多的图层. 先看看我们这一次用到哪些变量. p

转: Android 软件开发之如何使用Eclipse Debug调试程序详解(七)

转自: http://www.uml.org.cn/mobiledev/201110092.asp Android 软件开发之如何使用Eclipse Debug调试程序详解(七)   发布于2011-10-09   1.在程序中添加一个断点 如果所示:在Eclipse中添加了一个程序断点 在Eclipse中一共有三种添加断点的方法 第一种: 在红框区域右键出现菜单后点击第一项 Toggle Breakpoint 将会在你右键代码的哪一行添加一个程序断点 (同样的操作方可取消程序断点) 第二种:

Android开发之自定义View专题(三):自定义GridView

gridview作为android开发中常用的组件,其功能十分强大.但是,我们有时候有很多特殊的需求,需要在其基础上进行改造.有时候会有移动gridView中item位置的需求,这个网上已经有很多例子,博主就不在描述.今天博主讲的是移动gridView中item中的内容.博主没看过网上那些移动item位置的demo,不知道其原理是不是和博主想的一样.博主思考过,似乎博主的这种实现原理似乎也可以用作实现移动item位置.而之前博主百思不得其解的小米手机的桌面的自定义乱序排放,似乎也可以用这个原理去

Android开发之自定义View专题(四):自定义ViewGroup

有时候,我们会有这样的需求,一个activity里面需要有两个或者多个界面切换,就像Viewpager那样.但是在这些界面里面又需要能够有listView,gridview等组件.如果是纵向的,似乎还好,没什么影响,那么如果是横向的,那么就会出事情.因为Viewpager会拦截触摸事件.而如果将Viewpager的触摸事件拦截掉给里面的子控件,那么Viewpager又不能响应滑动事件了.那么如何又能让界面之间能够来回切换,又能让里面的子控件的触摸事件也能毫无影响的响应呢,这个时候,我们需要自定义

Android实现随机验证码——自定义View

一.问题描述 熟悉web开发中童鞋们都知道为了防止恶意破解.恶意提交.刷票等我们在提交表单数据时,都会使用随机验证码功能.在Android应用中我们同样需要这一功能,该如何实现呢,下面我们就自定义一个随机验证码View控件实现这一需求,并且具备通用性,需要的时候在界面中直接加入这个View组件即可. 二.案例介绍 案例运行效果 案例所涉及组件 1.CheckView 自定义的验证码控件,主要重写onDraw方法实现图形绘制 2.Config:用于对验证码控件参数的配置,像画点点数.划线数.背景颜

Android知识梳理之自定义View

虽然android本身给我们提供了形形色色的控件,基本能够满足日常开发的需求,但是面对日益同质化的app界面,和不同的业务需求.我们可能就需要自定义一些View来获得比较好的效果.自定义View是android开发者走向高级开发工程师必须要走的一关. 转载请标明出处:http://blog.csdn.net/unreliable_narrator/article/details/51274264 一,构造函数: 当我们创建一个类去继承View的时候,会要求我们至少去实现一个构造函数. publi