Android 事件分发机制

先看下Activity,View,Window的区别

activity是四大组件之一,也是我们的界面载体,展示界面,可以理解为控制单元;View是一个个的视图,这些视图可以搭载在一个layout文件上,通过activity的setContentView()方法传递给activity;window是一个窗体,每个activity对应一个window,通常我们在代码中用getWindow()来获取,可以理解为承载模型。

注1,

可以看到实际上是getwindow做的处理,getWindow返回的是mWindow,这个mWindow实际上就是PhoneWindow(Window是一个抽象类,PhoneWindow实际上就是Window的实现继承类)

接下来看看PhoneWindow的setContentView()方法:

先判断mContentParent是否为空(这个是ViewGroup对象,Window中的内容就放在这里)

如果是空的,就执行installDecor():这个方法就是实例化mContentParent,具体逻辑是先判断mDecor(是一个DecorView)是否为空,空的话就初始化,然后判断mContentView是否为空,为空就直接通过mDecor初始化:

mContentParent = generateLayout(mDecor);

 

 

 

 

 

 

 

 

总结:每个Activity包含一个Window对象,这个对象是由PhoneWindow做的实现。而PhoneWindow将DecorView作为了一个应用窗口的根View,这个DecorView又把屏幕划分为两个区域:一个是TitleView,一个是ContentView。我们平时写的布局正好是展示在ContentView中。

事件分发机制:https://www.jianshu.com/p/38015afcdb58

基本会遵从Activity=>ViewGroup=>View的顺序进行事件分发,onTouch()执行优先于onClick()

一般来说事件列都是在用户按下(ACTION_DOWN)那一刻产生的,与事件分发机制相关联的3个方法:

  • dispatchTouchEvent():负责事件分发,当事件产生后,事件首先会传递给当前的activity,这会调用activity的dispatchTouchEvent方法

先看第一个if语句:由于事件都是MotionEvent.ACTION_DOWN产生的,所以一般都会调用onUserInteraction()方法,但这个方法是空方法。不过看注释得知:该方法主要作用是实现屏保功能,并且当该activity在栈顶时,触屏点击Home,Back,Recent键等都会触发该方法。

在看第二个if语句:getWindow()明显是获取Window,但Window是个抽象类,所以我们能拿到子类PhoneWindow

可以看到直接调用了DecorView的superDispatchTrackballEvent()方法。DecorView继承于FrameLayout,作为顶层的View,是所有界面的父类,而FrameLayout作为ViewGroup的子类,所以直接调用了ViewGroup的dispatchTouchEvent().

查看ViewGroup的dispatchTouchEvent()可以发现:

定义了一个boolean值变量intercept来表示是否要拦截事件。其中采用到了onInterceptTouchEvent(ev)对intercepted进行赋值,大多数情况下,是返回false,但我们完全可以重写onInterceptTouchEvent来改变它的返回值。继续往下看:

可以发现,里面做了一个for循环,通过倒序遍历ViewGroup下面的所有子View,然后一个一个判断点击位置是否在该子View的布局区域。

ViewGroup也是View,所以接下来看View的dispatchTouchEvent():

第二个条件是判断当前点击的空间是否为enable,但由于基本View都是enable的,所以这个条件基本返回true;

第三个条件表示我们调用setOnTouchListener时必须覆盖onTouch()的返回值。

可见onTouch()方法的优先级高于onTouchEvent()。

  • onTouchEvent()

可以看到只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么onTouchEvent()就会返回true消耗这个事件。我们通常用setOnClickListener和setOnLongClickListener来设置被点击和长按。

接着看下performClick()方法:

可以看到,如果MOnClickListener不为空,那么它的onClick()方法就会调用。

 

  • onInterceptTouchEvent()

view绘制流程和原理:

窗口和view和activity的区别:https://www.jianshu.com/p/28d396a0f05f

GestureDetector:在onTouchEvent(MotionEvent event) 中回调scaleGestureDetector.onTouchEvent(event),判断滑动还是缩放区分手指就好

SurfaceView:

demo3:使用的ViewPager,并设置PagerAdapter。

 

demo5:
ScaleGestureDetector:缩放   
mGesture = new ScaleGestureDetector(mContext, new ScaleGestureDetector.OnScaleGestureListener() {
    //随着手势操作,回调的方法,
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        float previousSpan = detector.getPreviousSpan();//缩放发生前的两点距离
        float currentSpan = detector.getCurrentSpan();//缩放发生时的两点距离
        if (previousSpan < currentSpan)//放大
        {
            mZoomScale = mZoomScale + (currentSpan - previousSpan) / previousSpan;
        } else {
            mZoomScale = mZoomScale - (previousSpan - currentSpan) / previousSpan;
        }
        //确保放大最多为2倍,最少不能小于原图
        if (mZoomScale > 2) {
            mZoomScale = 2;
        } else if (mZoomScale < 1) {
            mZoomScale = 1;
        }
        setScaleX(mZoomScale);
        setScaleY(mZoomScale);
        //这里调用的是本自定义View的方法,是对本自定义view进行的缩放
        /*在这里调用getChildView(index)的进行缩放,虽然控件显示大小改变了,
        但是在ViewDragHelper的回调方法中获得的View child的getWidth()和getHeigit()是原来的大小,不会发生改变*/
        return true;
    }
    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }
    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
    }
});
ViewDragHelper控制子view的移动:
mViewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelper.Callback() {
    @Override
    public boolean tryCaptureView(View child, int pointerId) {
        //这里的return true表示自view可以滑动,false表示不处理滑动
        return true;

    }

    /*这里是控制子view左右滑动的回调,child为本自定义view的子控件,left表示意图从手指操作子view从左边界滑动的距离,大于0表示向右移动,小于0表示向左移动,是从手指的移动测出的理论值。方法的返回值表示实际上控件移动的距离,可以用返回值控制边界*/
    @Override
    public int clampViewPositionHorizontal(View child, int left, int dx) {
        //放大后控件的宽度-主布局的宽度 因为View chlid的控件使用的是match_parent,所以直接用父布局的宽取值
        if (left < (getWidth() - getWidth() * mZoomScale) / 2) {
            return (int) ((getWidth() - getWidth() * mZoomScale) / 2);
        }
        if (left > ((mZoomScale * getWidth() - getWidth()) / 2)) {
            return (int) ((mZoomScale * getWidth() - getWidth()) / 2);
        }
        return left;
        /** 这里进行边界处理,因为这里实际最大能放大2倍,那么实际会有3/4的左右滑动空间才会把子View(child)完全划出可见范围,这里为了保证view的可见性,使用1/2确保子view始终在可见范围之内*/
    }

    /*这个方法是控制上下滑动的,跟上面的方法意义一样,只是方向上不一样,top表示从顶部边界计算的距离,向下为正,向上为负*/
    @Override
    public int clampViewPositionVertical(View child, int top, int dy) {

        if (top < (getHeight() - getHeight() * mZoomScale) / 2) {
            return ((int) (getHeight() - getHeight() * mZoomScale) / 2);
        }
        if (top > (mZoomScale * getHeight() - getHeight()) / 2) {
            return (int) ((mZoomScale * getHeight() - getHeight()) / 2);
        }
        return top;
    }
});

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值