本节内容包括
- 事件分发机制
- 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测量机制原理分析