《Android 开发艺术探索》笔记3--View事件体系

View的事件体系.png

View的事件体系

View的基础知识

View的位置参数

一个View的位置主要由四个顶点构成, 或者可以就是两个点就可以确定. 分别为左上点,右下角每个点都对应x,y两个属性. 因为默认都是矩形, 所以两个点就可以确定.

一个View的大小可以利用四个属性可知. 分别对应getLeft(),getRight(),getTop(),getBottom系统提供的函数.

  • 一个控件的宽: getRight() - getLeft()
  • 一个控件的高: getTop() - getBottom()

在Android3.0中, View增加了几个属性:x , y, translationX, translationY

  • x , y: 表示View的左上角坐标点(最终坐标点).
  • translationX, translationY: 表示View的左上点相对于父容器的偏移量(默认是0).

而这些参数的换算关系为:

x = left + translationX;

y = top + translationY;

MotionEvent和TouchSlop

MotionEvent是指手指在接触屏幕之后产生的一系列事件

最常见事件类型是ACTION_DOWN,ACTION_MOVE,ACTION_UP

一次事件可以有不同的持续时间, 和不同的事件类型. 例如

  • 按下抬起 : DOWN –> UP
  • 按下移动抬起 : DOWN -> MOVE -> MOVE -> … ->UP
  • ….

而在移动时可以根据MotionEvent提供的参数获对应的xy取值.

*getX/getY: 返回相对于当前View左上角的x,y坐标.
getRawX/getRawY: 返回的是针对整个屏幕的左上角的x,y坐标.

TouchSlop是系统可以识别的最小滑动距离单位

只有手指两次滑动大于这个TouchSlop,系统才认为是滑动.

ViewConfiguration.get(getContent).getSealedTouchSlop()可以获得这个系统值默认8dp.

用途: 在自定义的时候, 可以参考系统的默认值, 来作为实际的滑动定义.

VelocityTracker GestureDetector

VelocityTracker 速度追踪

用于追踪手指在滑动过程中的速度,包括水平和数值方向的速度

使用方式: 在View的OnTouchEvent方法中:

//获得速度追踪对象

VelocityTracker velocity = VelocityTracker.obtain();

velocity.addMovement(event);

//计算速度 并获取计算值

velocity.computeCurrentVelocity(1000); //设定一个时间间隔值

float xVelocity = velocity.getXVelocity();

float yVelocity = velocity.getYVelocity();

必须要先计算并设定计算速度的时间单元值,才可以获得速率.

公式: 速度 = (终点位置 - 起点位置) / 时间间隔值

可以看到, 计算的速度是根据我们自己添加的时间间隔值计算的. 并且速度可以为负值,如果向左滑动.

当不需要的时候, 调用clear()重置并回收内存.


velocity.clear();

velocity.recycle();

GestureDetector 手势检测

用于辅助检测用户的单击, 滑动, 长按, 双击等行为.

使用如下

创建GestureDetector对象并实现OnGestureDetector接口.

GestureDetector mGestureDetector = new GestureDetector(this);

// 解决长按屏幕后无法拖动的现象

mGestureDetector.setIsLongpressEnabled(false);

然后接管目标View的onTouchEvent()方法. 在onTouchEvent()方法中


boolean consume = mGestureDetector.onTouchEvent(event);

return consume;

然后根据需求可以选择性的实现OnGestureListenerOnDoubleTapListener接口

接口的方法说明:

方法名 描述 所属接口
onDown 按下 OnGestureListener
onShowPress 按下 但是未松开或者拖动.强调状态 OnGestureListener
onSingleTapUp 抬起 表示单击行为, 双击中也会触发 OnGestureListener
onScroll 按下并拖动 拖动行为 OnGestureListener
onLongPress 长按 OnGestureListener
onFling 按下屏幕病快速滑动后松开 OnGestureListener
onDoubleTap 双击,两次连续单击组成, 与onSingleTapConfirmed无法共存 OnDoubleTapListener
onSingleTapConfirmed 严格意义上的单击 双击中的单击无法触发 OnDoubleTapListener
onDoubleTapEvent 表示发生了行为 OnDoubleTapListener

实际开发中:根据喜好来使用. 即使不使用GestureDetector辅助手势检测类,一样可以实现.

建议: 如果要监听双击这种行为就是用此类.

Scroller 弹性滑动对象

用于实现View的弹性滑动.

在开发中, 当需要把View从一个点移动到另一个点的时候. 如果使用scrollTo/scrollBy进行滑动时, 都是瞬间完成. 没有过度动画, 给用户感觉很生硬. 使用Scroller 可以实现有过渡的滑动.Scroller本身无法让View弹性滑动, 需要和View的computerScroll进行配合使用.

下面会说到

View的滑动

实现滑动的方式有三种:

  • 通过View本身的scrollTo/scrollBy方法实现滑动
  • 通过动画给View施加平移效果来实现动画
  • 通过改变View的LayoutParams使View重新布局实现滑动

scrollTo/scrollBy

首先要明确一点: 这两个方法只能改变View的内容位置,而不能改变View本身在布局中的位置

而且方法中都是以像素值来进行移动的.

  • scrollTo: 针对当前View的绝对位置进行移动.
  • scrollBy: 根据当前View的内容值进行相对位置移动.

看一下scrollBy的源码调用


public void scrollBy(int x, int y) {

   scrollTo(mScrollX + x, mScrollY + y);

}

其实本质上scrollBy调用了scrollTo方法

mScrollX/mScrollY是什么? 这个就是当前View的内容 与这个View实际布局位置(原始位置)的差值.
而当前View内容这个东西就是让用户看到的效果发生改变. 但是如果这个View可以被点击. 那么能触发点击的位置是View的实际所在布局位置. 而不是View的内容显示的位置.

使用动画

使用动画来对View进行移动,主要就是操作View的translationX/translationY属性

可以使用普通动画和属性动画.

普通动画是对View进行影像的移动. 可以通过设置fillAfter=true,来让影像在动画结束时候保留最终结果.而不是还原到起始位置.

而属性动画会对真实位置也进行改变.

ObjectAnimator.ofFloat(tagerView,"translationX",0,100).setDuration(100).start()

改变布局参数"

这个比较简单, 获得View的LayoutParams参数.进行修改,改好之后再赋值回去.

MarginLayoutParams params = (MarginLayoutParams)mTextView.getLayoutParams();

params.width += 100;

params.leftMargin += 100;

mTextView.requestLayout();

//或者mTextview.setLayoutParams(params);

关于这三种方式的简单总结

  • scrollTo/scrollBy: 操作简单, 适合对View内容的滑动
  • 动画: 操作简单,主要适用于没有交互的View和实现复杂的动画效果.
  • 修改布局参数: 操作稍微复杂,适用于有交互的View.

弹性滑动

使用Scroller

一个简单的使用方法如下:


Scroller mScroller = new Scroller(mContent);

// 封装一个方法, 接收要移动到的目标点 x和y

private void smoothScrollTo(int destX, int destY){

    int scrollX = getScrollX();

    int deltaX = destX - scrollX;

    // 1000ms内逐渐滑向destX

    mScroller.startScroll(scrollX, 0, deltaX, 0, 1000);

    invalidate();

}

//复写View的computeScroll方法

public void computeScroll(){

    if(mScroller.computeScrollOffset()){

        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());

        postInvalidate()

    }

}

源码中Scroller类中startScroll()方法,其实没有实际操作什么,只是保存了调用方法时,传递的几个参数. 如: 开始结束点,时间等. 那动画究竟是怎么实现的? 复写的computeScroll()又有什么用?

流程顺序这样的: 当调用了startScroll()系统只是保存了一些信息, 但是下面调用invalidate(). 这个方法都知道是会导致View的重绘, 在View的draw()方法中又会去调用computeScroll()方法,本身computeScroll()是一个空实现,但是这里进行了复写. 而这个方法我们复写的时候调用了scrollTo()方法! ok这样View就会真正的移动了! 但是还有一点这次滚动只是整个滚动事件的一个小部分,后续的怎么触发的? 就是下面又调用了postInvalidate(), 又会重新绘制重新调用computeScroll()这个复写过的空实现方法.

Scroller类中的computeScrollOffset()可以直接返回这个滚动的动作是否全部完成. 源码实现思路就是根据时间的流逝的百分比来计算出当前ScrollX和ScrollY的值.


// 核心代码  x就是时间流逝的百分比

mCurrX = mStartX + Math.round(x * mDeltaX);

mCurrY = mStartY + Math.round(x * mDeltaY);

小结Scroller的工作原理:

Scroller本身不可以实现滑动, 需要和View的ComputeScroll()配合使用来完成弹性滑动. 通过不断的在computeScroll()调用View的重绘方法. 每次绘制时候的当前时间与开始时间的时间差与设定的执行动画时间的百分比,算出每一次需要scroll到的坐标点, 然后通过调用scrollTo()来实现每一次的小滚动效果. 通过一连串的滚动达到了平滑的效果. 这就是Scroller工作机制. 完全实现了解耦操作. 这个过程没有任何一处对View进行引用,甚至连内部计时器都没有.

补充:
1、top、left、right、bottom的值,是在view的onLayout的时候确定。
2、scrollView只在绘制的时候onLayout,在滚动的时候不会再次出发onLayout,所以对于子View的top、left、right、bottom是没有影响的、
3、listView自身有回收机制,所以在滚动的时候需要时刻去检测item是否已经滚动出了屏幕,这样就需要重新测量子view的位置,所以就直接影响了item的top、left、right、bottom。

通过动画

可以直接使用ObjectAnimator.ofFloat(tagerView,"translationX",0,100).setDuration(100).start()

也可以利用动画的特性, 实现与Scroller原理近似的方法.

final int startX = 100;

final int endX = 200;

ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1).setDuration(1000);

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

  @Override

  public void onAnimationUpdate(ValueAnimator animation) {

      int offset = (int) animation.getAnimatedFraction();

      mTextview.scrollTo(startX+offset, 0);

  }

});

让系统算出每个时间片我们需要移动的距离, 并回调给我们.让我们自己实现. 如果是一组动画在相同的时间执行的绝对值相同我们就可以在onAnimationUpdate()一起进行调用.

使用延时策略

核心思想就是通过发送一些列延时消息从而达到一种渐进的效果.

可以使用: Handler, View的postDelayed()方法, 或者线程的sleep()

参看文章

《Android 开发艺术探索》书集
《Android 开发艺术探索》 03-View的事件体系

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336