高级UI--事件处理(六)

本节内容包括

  • 事件分发机制
  • ListView和ScrollView的冲突处理
  • viewPager简易实现

事件分发机制

一、View的事件分发传递
测试结果:
1.控件的Listener事件触发的顺序是先onTouch,再onClick。】
2.控件的onTouch返回true,将会onClick事件没有了---阻止了事件的传递。返回false,才会传递onClick事件(才会传递up事件)

源码分析:
    1.dispatchTouchEvent();
    2.onTouchListener-->onTouch方法
    3.onTouchEvent
    4.onClickListener-->onClick方法

源码设计(View的dispatchTouchEvent源码):
    1.如果onTouchListener的onTouch方法返回了true,那么view里面的onTouchEvent就不会调用了。
    调用顺序:
    dispatchTouchEvent-->onTouchListener-->onTouch方法(return false)-->onTouchEvent
    2.如果view为disenable,则:onTouchListener里面不会执行,但会执行onTouchEvent
    3.onTouchEvent方法中的ACTION_UP分支中触发onClick事件监听

二、ViewGroup+View的事件分发传递
ViewGroup-->View
    1.dispatchTouchEvent();
    2.onTouchEvent();
    3.onInterceptTouchEvent();拦截触摸事件
  先接触到事件的是父容器。
  顺序:
    dispatchTouchEvent-->onInterceptTouchEvent-->onTouchListener-->onTouch(return false)-->onTouchEvent

ViewGroup源码分析

//源码2520行
dispatchTransformedTouchEvent(){
     if(child == null){//如果viewGroup里面没有子控件就交给自己处理
            handled = super.dispatchTouchEvent(event);
      }else{
            handled = child.dispatchTouchEvent(event);
      }
}

测试DemoEventDeliver事件分发顺序

dispatchTouchEvent====0====MyRelativeLayout
onInterceptTouchEvent====0====MyRelativeLayout
dispatchTouchEvent====0====MyButton
onTouchListener====0====2131427423
onTouchEvent====0====MyButton
dispatchTouchEvent====1====MyRelativeLayout
onInterceptTouchEvent====1====MyRelativeLayout
dispatchTouchEvent====1====MyButton
onTouchListener====1====2131427423
onTouchEvent====1====MyButton
onClickListener====2131427423

参考文献
1.图解 Android 事件分发机制

ListView和ScrollView的冲突处理

(1). 在ScrollView控件中设置固定高度ListView控件,并用其他控件(如TextView)占据超过屏幕剩余空间。这样滑动界面的时候,只会触发ScrollView的滚动,而不能触发ListView的滑动。
解决方法:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //不要拦截
        requestDisallowInterceptTouchEvent(true);
        return super.dispatchTouchEvent(ev);
    }

调用requestDisallowInterceptTouchEvent(true)不拦截子控件的事件
(2). ScrollView嵌套ListView,ListView完全展开
布局如下:




    

        

        
        
        
        ....

无论ListView的高度怎么设置,都会只显示一行的高度,那是由于ListView的父容器测量模式为UNSPECIFIED的时候,ListView的高度默认为一个item的高度。ListView中源码如下:

if (heightMode == MeasureSpec.UNSPECIFIED) {
    heightSize = mListPadding.top + mListPadding.bottom + childHeight +
                   getVerticalFadingEdgeLength() * 2;
}

解决方法:重写ListView的onMeasure方法

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //int size = MeasureSpec.getSize(heightMeasureSpec);
        //int mode = MeasureSpec.getMode(heightMeasureSpec);
        //>>右移运算符
        int expandedHeight  = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2, MeasureSpec.AT_MOST);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  }

原理分析:
我们把高度写成了一个固定值expandSpec ,这个值是这样计算出来的

int expandedHeight  = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE>>2, MeasureSpec.AT_MOST);

这和android的实体测量机制有关了,android中规定,测量的值(高度或宽度)为一个int类型,但不是普通的int,而是一个进过处理的int,在view视图中我们制定一个高度需要2个参数,1个是具体的值,一个是测量模式,测量模式就是我们在布局中经常用到的MATCH_PARENT 、WRAP_CONTENT。他们是一个int型的常量,对应的值分别是:

LayoutParams.MATCH_PARENT  对应 MeasureSpec.EXACTLY
LayoutParams.WRAP_CONTENT  对应 MeasureSpec.AT_MOST

而EXACTLY和AT_MOST的值是:

private static final int MODE_SHIFT = 30;      

public static final int EXACTLY     = 1 << MODE_SHIFT;    //填满父控件高度
public static final int AT_MOST     = 2 << MODE_SHIFT;    //自适应当前控件高度

android中把测量出的int做了处理,int的长度时32位,把前2位作为标志位标示了测量模式,如EXACTLY、AT_MOST ,把后30位作为测量的具体高度或宽度。 也就是说,把一个int分成了2部分,使一个int值同时拥有了模式和具体数值的2部分信息!

EXACTLY的值是1向左进位30,就是01 00000000000…(01后跟30个0) 
AT_MOST的值是2向左进位30,就是10 00000000000…(10后跟30个0)

所以我们在调用MeasureSpec.makeMeasureSpec(size,mode)方法时,传入的size参数要把Integer.MAX_VALUE右移2位,因为前两位会被认为是标志,而不是值。这样我们传入的参数才会被认为是最大的int类型的值,同时传入AT_MOST作为模式,那么前两位就会被赋值为10。

参考文献
1.android开发游记:ScrollView嵌套ListView,ListView完全展开及makeMeasureSpec测量机制原理分析

ViewPager简易实现

你可能感兴趣的:(高级UI--事件处理(六))