安卓学习笔记之View的事件体系

4 篇文章 0 订阅
3 篇文章 0 订阅

一、 关于View所需要掌握的基本功

1、 View的概念

- 在android中View为所有控件的基类,简单的控件和VIewGroup都继承自View。
- 一个View占用了屏幕上的一个矩形区域并且负责界面绘制和事件处理。
- View是一个宽泛的概念,既可以指复杂的ViewGroup(控件组,诸如RelativeLayout,LinearLayout),也可以是简单的控件(诸如TextView,Button)。
- View树,由于View既可以做简单控件,也可为ViewGroup,故而View体系组成了View树,View的显示和事件处理,都是依赖于这个View树。绘制和事件处理的起始点,都是从根View开始一级一级的往下传递。我们从任意一层发起绘制,都将反馈到根View,然后再从上往下传递。

这里写图片描述

2、 View坐标参数

android坐标系中的view  

这里写图片描述

四个参数
View的四个顶点决定了其显示的位置,分别为left、top、right、bottom(分别对应着view的左上顶点的横坐标、左上顶点的纵坐标、右下顶点的横坐标、右下顶点的纵坐标),
获取方式
分别通过getLeft()、getTop()、getRight()、getBottom()方法获取(相对于ViewGroup)
View的宽高计算
width=getRight()-getLeft();
height=getBottom()-getTop();

3、 MotionEvent触摸事件

常见触摸事件
从手指触摸屏幕开始到手指离开屏幕,触发了一系列事件,常见动作及触发时机

MotionEvent.ACTION_DOWN ,触摸屏幕时触发
MotionEvent.ACTION_MOVE,滑动时触发
MotionEvent.ACTION_UP,离开屏幕时触发
MotionEvent.ACTION_CANCEL,当焦点滑动到控件之外时触发
MotionEvent对象获取事件发生时的坐标

getX() / getY() , 获取相对于View自身的左上角的x/y坐标
getRawX() / getRawY() , 获取相对于屏幕左上角的x/y坐标

4、 TouchSlop – 触摸or滑动

TouchSlop含义
Distance in pixels a touch can wander before we think the user is scrolling,即可以视为触摸而非滑动事件的最大距离,大于这个值一般可视为滑动。其值为常量且与设备有关,不同设备值可能不同。
获取TouchSlop
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();

5、 GestureDetector与ScaleGestureDetector 手势识别

GestureDetector
GestureDetector的使用

作用:手势检测,如单击,滑动,长按,双击等
使用:

实现OnGesturerListener接口,注册事件监听
接管目标View滑动事件onTouchEvent方法。在onTouchEvent方法中

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

在对应的方法中执行相应的操作
OnGestureListener接口中的方法
  • onDown Notified when a tap occurs with the down MotionEvent that triggered it.

  • onFling Notified of a fling event when it occurs with the initial on down MotionEvent and the matching up MotionEvent.//手指以某个速率滑离屏幕时

  • onLongPress Notified when a long press occurs with the initial on down MotionEvent that trigged it.

  • onScroll Notified when a scroll occurs with the initial on down MotionEvent and the current move MotionEvent.

  • onShowPress The user has performed a down MotionEvent and not performed a move or up yet.

  • onSingleTapUp Notified when a tap occurs with the up MotionEvent that triggered it.

  • onSingleTapConfirmed(MotionEvent e); Notified when a single-tap occurs. Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this will only be called after the detector is confident that the user’s first tap is not followed by a second tap leading to a double-tap gesture.// 严格的单击事件,不可能为双击事件的一击

ScaleGestureDetector
ScaleGestureDetector的使用

作用:识别缩放手势
使用:

实现OnScaleGestureListener接口,注册事件监听
接管目标View滑动事件onTouchEvent方法。在onTouchEvent方法中
boolean isConsume = mScaleGestureDetector.onTouchEvent(event);
return isConsume; 

在对应的方法中执行相应的操作

// 获取缩放因子
float scaleFactor = detector.getScaleFactor();
mMatrix.postScale(scaleFactor,scaleFactor,detector.getFocusX(), detector.getFocusY()); //手势中点
setImageMatrix(mMatrix);

onScaleBegin,onScale返回值为true时才会有缩放效果

OnScaleGestureListener接口方法
  • onScale Responds to scaling events for a gesture in progress.缩放时

  • onScaleBegin Responds to the beginning of a scaling gesture. 缩放开始

  • onScaleEnd Responds to the end of a scale gesture. 缩放结束

6、 Scroller - 弹性滑动承载者

弹性滑动对象,本身并不能产生弹性滑动,需要配合computeScroll () 与scrollTo()方法才能实现view的弹性滑动,示例、
/**
 * 弹性滑动
 * @param startX
 * @param startY
 * @param dX ,x方向的偏移量,>0往左滑 ,<0往右滑
 * @param dY ,y方向的偏移量,>0往上滑 ,<0往下滑
 * @param duration , 持续时长
 */
private void smoothScroll(int startX, int startY, int dX, int dY, int duration) {
    if (Math.abs(duration) > 800) duration = 800;
    mScroller.startScroll(startX, startY, dX, dY, Math.abs(duration));
    invalidate();
}

@Override
public void computeScroll() {
    super.computeScroll();
    if (mScroller.computeScrollOffset()) {  // computeScrollOffset返回值为true则滑动还没结束
        scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
        invalidate();
    }
} 

7、 VelocityTracker - 速度追踪利器

作用:滑动速度追踪,分为x方向(getXVelocity)与y方向(getYVelocity)
计算:( 终点位置横坐标(纵坐标)-起点位置横坐标(纵坐标)) / 耗时
当从左往右滑getXVelocity>0 , 反之getXVelocity<0
当从上往下滑getXVelocity>0 , 反之getXVelocity<0
使用步骤
  • 获取VelocityTracker

      if (velocityTracker == null) {
          	velocityTracker = VelocityTracker.obtain();
      }
    
  • 添加用户的movement到tracker

      velocityTracker.addMovement(ev); // ACTION_DOWN与ACTION_MOVE都要
    
  • 设置时间间隔计算速度

      velocityTracker.computeCurrentVelocity(50); // 计算当前速率,按每50毫秒
    
  • 获取x与y方向速度

      final float yVelocity = velocityTracker.getYVelocity();
      final float xVelocity = velocityTracker.getXVelocity();
    
  • 回收资源

     velocityTracker.clear();  // 重置到初始状态
     当velocityTracker不再使用时,调用velocityTracker.recycle();回收资源,以便其他使用
    

二、View滑动那些事

1. View的几种常见滑动

使用scrollTo/scrollBy实现
特点:瞬间完成,scrollTo绝对移动,scrollBy相对移动scrollTo是滑动到指定位置,scrollBy是相对自身的位置滑动指定距离
scrollBy基于scrollTo实现
/**
 * @param x the amount of pixels to scroll by horizontally
 * @param y the amount of pixels to scroll by vertically
 */
public void scrollBy(int x, int y) {
    scrollTo(mScrollX + x, mScrollY + y);
}
getScrollX与getScrollY的意义
getScrollX获取的是view内容左边缘相对于view边界左边缘的偏移量,值=view边界左边缘 x- view内容左边缘x
getScrollY获取的是view内容上边缘相对于view边界上边缘的偏移量,值=view边界上边缘y - view内容上边缘y
由上可总结如下,
	view内容左边缘滑动到view左边缘左边时,getScrollX>0,反之,右边时,getScrollX<0
	view内容上边缘滑动到view上边缘上边时,getScrollY>0,反之,下边时,getScrollY<0
使用动画实现
-	补间动画
	包括AlphaAnimation、TranslateAnimation、ScaleAnimation、RotateAnimation
	特点:不改变view的位置属性,移动的只是view的副本
	设置setFillAfter为true,保持最后状态
	
-	属性动画
	包括ObjectAnimator,ValueAnimator
	特点:真实改变View的属性
	Android2.3以下使用nineoldandroids兼容库
示例
ObjectAnimator.ofFloat(targetView,"alpha",0.0f,1.0f);
改变布局参数策略
通过改变View的自身的位置参数实现滑动效果
在目标view旁置一个空view,通过改变此空view的位置参数来间接改变目标view位置,
获取并修改位置参数
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
mlp.leftMargin += 10;
mlp.width += 10;
textView.setLayoutParams(mlp);  // 或textView.requestLayout();
layout方法
int contentLeft = centerContent.getLeft() + dX;
centerContent.layout(contentLeft,centerContent.getTop(),contentLeft+centerContent.getMeasuredWidth(), centerContent.getBottom());
offsetLeftAndRight与offsetTopAndBottom
offsetLeftAndRight : 通过特定的pixels对view进行水平方向偏移
	Offset this view's horizontal location by the specified amount of pixels. 
offsetTopAndBottom : 通过特定的pixels对view进行竖直方向偏移
	Offset this view's vertical location by the specified number of pixels.
ViewDragHelper
常用方法
  • create(forParent, callBack) —创建

      forParent - Parent view to monitor 要监控的父view
      callBack - Callback to provide information and receive events 回调
    
  • tryCaptureView (View child, int pointerId) —决定哪个view可以被捕获

      child - Child the user is attempting to capture  要捕获的view
      pointerId - ID of the pointer attempting the capture  touch的id
    
  • clampViewPositionHorizontal (View child, int left, int dx) —限制被拖拽view的水平方向滑动

     child - Child view being dragged  被拖拽的view
     left - Attempted motion along the X axis  x方向尝试滑动的left
     dx - Proposed change in position for left  x方向的增量
    
  • clampViewPositionVertical —限制被拖拽view的水竖直向滑动

  • onViewPositionChanged(View changedView, int left, int top, int dx, int dy) —当view位置改变时

      param left        此view的左边界的x
      param top        此view的上边界的y
      param dx         与上次相比的x位置增量
      param dy         与上次相比的y位置增量
    
  • onViewReleased(View releasedChild, float xvel, float yvel) — 释放拖动后调用

      param releasedChild  被释放的view
      param xvel          x方向的速度
      param yvel          y方向的速度
    
  • getViewHorizontalDragRange与getViewVerticalDragRange 返回view的水平与竖直拖动范围,设置为0则不可拖动

  • onEdgeDragStarted 在边界拖动时回调

实现弹性滑动
mViewDragHelper.smoothSlideViewTo(View child,int finalLeft, int finalTop)  开启滑动
ViewCompat. postInvalidateOnAnimation(View view)  引导重绘
computeScroll中continueSettling判断是否完成
简单总结
  • scrollTo/scrollBy : 操作简单,适用于对view内容的滑动
  • 动画 : 适用于没有交互性的操作及实现复杂的动画
  • 改变布局参数 : 操作繁琐,适用于有交互性的滑动
  • layout方法:对ViewGroup内部View重新布局
  • 使用ViewDragHelper:功能强大,可以用于制作复杂的滑动效果

2. View的弹性滑动

Scroller实现弹性滑动(示例见view的基本功6)
注意事项:
1.	滑动的是view的内容而不是view本身位置改变
2.	mScroller.getCurrX() / mScroller.getCurrY()获得的值是根据时间流逝比例计算所得,为scrollTo将要滑动到的位置
3.	mScroller.computeScrollOffset() 返回值为true表示滑动还未结束
4.	Scroller内部只是保存了我们传递的参数,没有实现滑动
5.	mScroller.startScroll并不会开始滑动,调用invalidate()才会导致view重绘,并开始滑动
6.	Scroller本身需要配合computeScroll方法和scrollTo方法才能实现弹性滑动,通过不断的view重绘和小步位移完成view的滑动效果
7.	如果滑动过程执行在非UI线程,在computeScroll方法中应调用postInvalidate()进行view重绘,否则会导致异常产生
使用动画实现
/**
 * @param targetView 目标view
 * @param dx , 偏移量
 * @param duration
 */
private void smoothScroll(final View targetView, final int dx, int duration) {
    final int left = targetView.getLeft();
    final int right = targetView.getRight();
    final ValueAnimator animator = ObjectAnimator.ofFloat(0, 1);
    animator.setInterpolator(new AccelerateDecelerateInterpolator(mContext,null));
    animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            final float fraction = animation.getAnimatedFraction();
            targetView.layout((int) (left + fraction * dx), targetView.getTop(), (int) (right + fraction * dx), targetView.getBottom());
        }
    });
    animator.start();
}

使用延时
  • 使用handler的postDelayed实现
  • 使用view自身的postDelayed实现
  • 使用线程
/**
 * @param targetView 目标view
 * @param dx   , 偏移量
 */
private void smoothScrollRunnable(final View targetView, final int dx) {
     targetView.postDelayed(new ScrollRunnable(targetView, dx),10);
}

class ScrollRunnable implements Runnable {
    private View targetView;
    private int dx;
    private int left, right; // targetView初始的左右位置
    private float fraction; //位移比率

    public ScrollRunnable(View targetView, int dx) {
        this.targetView = targetView;
        this.dx = dx;
        left = targetView.getLeft();
        right = targetView.getRight();
    }

    @Override
    public void run() {
        fraction += 0.04;
        if (fraction < 1.0f) {
            int delta = (int) (fraction * dx);
            Log.e("", delta+"-------------- ");
            targetView.layout(left + delta, targetView.getTop(), right + delta, targetView.getBottom());
            postDelayed(this, 5);
        }else {
            targetView.layout(left + dx, targetView.getTop(), right + dx, targetView.getBottom());
        }
    }
}

三、 事件的分发机制浅析

1. 点击事件的传递规则

事件分发的三个重要方法及作用
public boolean dispatchTouchEvent(MotionEvent ev)   事件分发
public boolean onInterceptTouchEvent(MotionEvent ev)(ViewGroup) 事件拦截
public boolean onTouchEvent (MotionEvent ev) 事件处理
分发细节
  • 如果view能够接受到点击事件,则dispatchTouchEvent一定会调用
  • 在dispatchTouchEvent方法中onInterceptTouchEvent会调用
  • 如果onInterceptTouchEvent返回值为true,则表示此view将消耗此事件,其onTouchEvent会被调用
  • 如果onInterceptTouchEvent返回值为false,则此view不消耗此事件,其子view的dispatchTouchEvent方法将被调用,重复上述过程直到事件被处理

注意:只有ViewGroup才有onInterceptTouchEvent方法,简单的view不具备onInterceptTouchEvent方法

这里写图片描述

2. View事件的传递顺序

  • 事件总是先传给Activity ,然后会走Activity -> Window(实现类PhoneWindow) -> View(从顶级decorview开始) -> ViewGroup -> View
  • 一个view的onTouchEvent方法返回false,则其父view的onTouchEvent方法会被调用
  • 若所有的view的onTouchEvent都返回false,则事件会往回传递,最终Activity的onTouchEvent方法会被调用

3. 优先级

onTouch > onTouchEvent > onClick

#四、 滑动冲突解决办法
###1. 滑动冲突处理规则
根据滑动的方向来判断该由谁来拦截事件

2. 两种解决方式

外部拦截法
  • 点击事件都先由父容器进行拦截处理,父容器进行选择性拦截
  • 如果父容器需要则拦截事件,在onInterceptTouchEvent方法中返回true,否则返回false
  • 父容器的onInterceptTouchEvent方法的ACTION_DOWN必须返回false,否则子view收不到点击事件,因为如果返回true,则后续事件都会交由父容器处理
  • 若父容器将事件交由子view处理,如果父容器ACTION_UP返回为true,则使得子view的ACTION_UP无法触发,导致子view的onClick无法触发
  • 父容器一旦决定拦截事件,则后续事件都交由它处理,即使其onInterceptTouchEvent方法的ACTION_UP返回为false

在父容器中onInterceptTouchEvent的处理细节

boolean intercepted=false;
	if(父容器要消耗的事件){
		intercepted=true;
	}else{
		intercepted=false;
	}
	return intercepted;
内部拦截法
  • 父容器不拦截任何事件,所有事件都交由子元素处理,如果子元素需要则消耗事件,否则交由父容器处理
  • 需要配合requestDisallowInterceptTouchEvent()方法,父容器如果需要处理事件,则给此方法传递false
  • 父元素默认拦截除了ACTION_DOWN的所有事件

注:此篇为总结篇,在前人的基础上,增加部分内容,作了自我梳理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值