美文网首页
高级UI--事件处理(六)

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

作者: AndryYu | 来源:发表于2017-09-29 22:39 被阅读0次

    本节内容包括

    • 事件分发机制
    • 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完全展开
    布局如下:

    <?xml version="1.0" encoding="utf-8"?>
    <com.andryyu.eventdeliver.test2.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.andryyu.eventdeliver.test2.Test2Activity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
    
            <com.andryyu.eventdeliver.test2.MyListView
                android:id="@+id/lv_test2"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>
    
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="200dp"
                android:text="周末愉快,各位大宝贝!" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="200dp"
                android:text="周末愉快,各位大宝贝!" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="200dp"
                android:text="周末愉快,各位大宝贝!" />
            ....
    

    无论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--事件处理(六)

          本文链接:https://www.haomeiwen.com/subject/ixlflxtx.html