学习笔记 | Android开发艺术之View(一)

一、View事件体系

1、概念: View是Android中所有控件的基类,一种界面层的控件的一种抽象,它代表了一个控件。

2、View的位置参数

  • View的四个属性:top、left、right、bottom,坐标都是相对于View的父容器来说的。
  • Android3.0开始,View增加了额外的几个参数:x、y、translationX和translationY,其中x和y是View左上角的坐标,而translationX和translationY是View左上角相对于父容器的偏移量。这几个参数也是相对于父容器的坐标,并且translationX和translationY的默认值是0
    注意: View在平移的过程中,top和left表示的是原始左上角的位置信息,其值并不会发生改变,此时发生改变的是x、y、translationX和translationY这四个参数

3、MotionEvent和TouchSlop

  • MotionEvent:手指接触屏幕后所产生的一系列事件
    典型的事件类型有如下几种:
    ACTION_DOWN——手指刚接触屏幕
    ACTION_MOVE——手指在屏幕上移动
    ACTION_UP——手机从屏幕上松开的一瞬间
  • TouchSlop :系统所能识别出的被认为是滑动的最小距离
    获取方式:ViewConfiguration. get(getContext()).getScaledTouchSlop()

4、VelocityTracker和GestureDetector

  • VelocityTracker :速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度
    使用过程:
     a. 在View的onTouchEvent方法中追踪当前单击事件的速度
VelocityTracker velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);

  b. 获取手指在content界面滑动的速度

//设置移动速度单位为:像素/10000ms,即1000毫秒内移动的像素
velocityTracker.computeCurrentVelocity(1000);
//获取手指在界面滑动的速度
int xVelocity = (int) velocityTracker.getXVelocity();
int yVelocity = (int) velocityTracker.getYVelocity();

  c. 调用clear方法来重置并回收内存

velocityTracker.clear();
velocityTracker.recycle();
  • GestureDetector :手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为
    使用过程:
     a. 创建一个GestureDetector对象并实现OnGestureListener接口
GestureDetector mGestureDetector = new GestureDetector(this);//实例化一个GestureDetector对象
mGestureDetector.setIsLongpressEnabled(false);// 解决长按屏幕后无法拖动的现象

  b. 接管目标view的onTouchEvent方法,在待监听view的onTouchEvent方法中添加如下实现:

boolean consume = mGestureDetector.onTouchEvent(event);
return consume;

  c. 选择地实现OnGestureListener和OnDoubleTapListener中的方法
   常用的有:onSingleTapUp(单击)、onFling(快速滑动)、onScroll(拖动)、onLongPress(长按)和onDoubleTap(双击)

5、实现View的滑动

 第一种是通过View本身提供的scrollTo/scrollBy方法来实现滑动;第二种是通过动画给View施加平移效果来实现滑动;第三种是通过改变View的LayoutParams使得View重新布局从而实现滑动。

方式说明缺点
scrollTo/scrollBy操作简单,适合对View内容的滑动只能滑动View的内容,并不能滑动View本身
动画操作简单,主要适用于没有交互的View和实现复杂的动画效果在Android 3.0以下使用属性动画,均不能改变View本身的属性。会出现通过View动画将一个Button向右移动100px,并为其设置单击事件,单击新位置无法触发onClick事件,而单击原始位置仍然可以触发onClick事件的问题
改变布局参数适用于有交互的View操作稍微复杂

6、实现View的弹性滑动

(1)Scroller :弹性滑动对象,用于实现View的弹性滑动

a. 典型代码:

Scroller scroller = new Scroller(mContext); //实例化一个Scroller对象

private void smoothScrollTo(int dstX, int dstY) {
  int scrollX = getScrollX();//View的左边缘到其内容左边缘的距离
  int scrollY = getScrollY();//View的上边缘到其内容上边缘的距离
  int deltaX = dstX - scrollX;//x方向滑动的位移量
  int deltaY = dstY - scrollY;//y方向滑动的位移量
  scroller.startScroll(scrollX, scrollY, deltaX, deltaY, 1000); //开始滑动
  invalidate(); //刷新界面
}

@Override//计算一段时间间隔内偏移的距离,并返回是否滚动结束的标记
public void computeScroll() {
  if (scroller.computeScrollOffset()) { 
    scrollTo(scroller.getCurrX(), scroller.getCurY());
    postInvalidate();//通过不断的重绘不断的调用computeScroll方法
  }
}

startScroll源码如下:

public void startScroll(int startX,int startY,int dx,int dy,int duration){
  mMode = SCROLL_MODE;
  mFinished = false;
  mDuration = duration;//滑动时间
  mStartTime = AnimationUtils.currentAminationTimeMills();//开始时间
  mStartX = startX;//滑动起点
  mStartY = startY;//滑动起点
  mFinalX = startX + dx;//滑动终点
  mFinalY = startY + dy;//滑动终点
  mDeltaX = dx;//滑动距离
  mDeltaY = dy;//滑动距离
  mDurationReciprocal = 1.0f / (float)mDuration;
 }

仅仅调用startScroll方法是无法让View滑动的,因为它内部并没有做滑动相关的事,而是通过后续invalidate()方法,它会导致View重绘,当View重绘后会在draw方法中调用computeScroll,而computeScroll又会去向Scroller获取当前的scrollX和scrollY;然后通过scrollTo方法实现滑动;接着又调用postInvalidate方法来进行第二次重绘,这一次重绘的过程和第一次重绘一样,还是会导致computeScroll方法被调用;然后继续向Scroller获取当前的scrollX和scrollY,并通过scrollTo方法滑动到新的位置,如此反复,直到整个滑动过程结束。

 b. Scroller的工作机制
 Scroller本身并不能实现View的滑动,它需要配合View的computeScroll方法才能完成弹性滑动的效果,它不断地让View重绘,而每一次重绘距滑动起始时间会有一个时间间隔,通过这个时间间隔Scroller就可以得出View当前的滑动位置,知道了滑动位置就可以通过scrollTo方法来完成View的滑动。就这样,View的每一次重绘都会导致View进行小幅度的滑动,而多次的小幅度滑动就组成了弹性滑动.

(2)使用动画:动画本身就是一种渐近的过程,因此通过它来实现的滑动天然就具有弹性效果
(3)使用延时策略

  a. 核心思想:通过发送一系列延时消息从而达到一种渐近式的效果。
  b. 具体来说可以使用Handler或View的postDelayed方法,也可以使用线程的sleep方法。

二、View的事件分发机制

a. 点击事件的传递顺序:Activity(Window) -> ViewGroup -> View

b. 三个主要方法

  • dispatchTouchEvent:进行事件的分发(传递)。返回值是 boolean 类型,受当前onTouchEvent和下级view的dispatchTouchEvent影响
  • onInterceptTouchEvent:对事件进行拦截。该方法只在ViewGroup中有,View(不包含 ViewGroup)是没有的。一旦拦截,则执行ViewGroup的onTouchEvent,在ViewGroup中处理事件,而不接着分发给View。且只调用一次,所以后面的事件都会交给ViewGroup处理。
  • onTouchEvent:处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。

事件分发过程图
事件分发过程图

c. 结论

  • 同一个事件序列指的是从手指接触屏幕开始,到手指离开屏幕的过程,也就是DOWN—MOVE…MOVE—UP,这是一个事件序列。
  • 一个事件序列只能被一个View拦截且消耗。
  • 某个View一旦决定拦截,那么这一个事件序列都只能由它来处理(如果事件序列能够传递给它的话),并且它的onInterceptTouchEvent不会再被调用。
  • 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false,表示不消耗事件),那么同一事件序列中的其他事件都不会再交给他处理,即父元素的onTouchEvent会被调用,交给父元素来处理(责任链模式)。
  • ViewGroup的onInterceptTouchEvent方法默认返回false,即不拦截任何事件。
  • View没有onInterceptTouchEvent函数,即不能选择是否拦截,必须拦截,但可以不处理。
  • View的onTouchEvent默认都会消耗事件,返回true,除非它是不可点击的。

d. 事件分发源码解析
Android事件分发机制详解
注:View对点击事件的处理过程 :(1)OnTouchListener的优先级高于onTouchEvent . (2)onTouch()的执行 先于 onClick().

三、View的滑动冲突

1、产生原因: 在一个界面里存在内外两层可同时滑动的情况时,会出现滑动冲突现象。

2、常见的滑动冲突场景

  • 外部滑动方向和内部滑动方向不一致,如:ViewPager嵌套ListView(外层ViewGroup是可以横向滑动的,内层View是可以竖向滑动的),但ViewPager内部处理了这种滑动冲突,无须关注这个问题
  • 外部滑动方向和内部滑动方向一致,如:ScrollView+ListView造成内外两层只能有一层能够滑动
  • 上面两种情况的嵌套(外部有一个SlideMenu效果,然后内部有一个ViewPager,ViewPager的每一个页面中又是一个ListView)

3、滑动冲突的解决方式

  • 外部拦截法
    含义:指点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,否则就不拦截。
    方法:需要重写父容器的onInterceptTouchEvent方法,在内部做出相应的拦截。
  • 内部拦截法
    含义:指父容器不拦截任何事件,而将所有的事件都传递给子容器,如果子容器需要此事件就直接消耗,否则就交由父容器进行处理。
    方法:需要配合requestDisallowInterceptTouchEvent方法,并且重写子元素的dispatchTouchEvent方法,父元素也要默认拦截除了ACTION_DOWN以外的其他事件,这样当子元素调用parent.requestDisal-lowInterceptTouchEvent(false)方法时,父元素才能继续拦截所需的事件。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值