Android 控件ListView 条目响应深入探索
2016-11-02 12:40
330 查看
前言
之前在项目中遇到这么个问题,对ListView进行如下设置,发现点击事件并未响应,由于项目进度紧,所以采用另一方法来解决这一问题,另一方法在文章最后面会贴上,觉得文章篇幅太长的话,可直接看文章最后面的总结ListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // } });
前几天一朋友也遇到这问题,趁着有空赶忙探究下,可能现在大多项目都用recyclerview控件,在项目中我也用到recyclerview控件,但ListView还是有必要探讨下
开始
那么首先通过重写ListView控件,重写其事件分发、事件拦截,事件处理等方法,打印返回值,下面是重写的代码:/** * Created by WYK on 2016/10/31. */ public class MyListViewview extends ListView{ public MyListViewview(Context context) { super(context); } public MyListViewview(Context context, AttributeSet attrs) { super(context, attrs); } public MyListViewview(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { LogUtil.logi("------dispatchTouchEvent------------->"); boolean b = super.dispatchTouchEvent(ev); LogUtil.logi("------dispatchTouchEvent------------->" + b); return b; } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { LogUtil.logi("------onInterceptTouchEvent------------->"); boolean b = super.onInterceptTouchEvent(ev); LogUtil.logi("------onInterceptTouchEvent------------->" + b); return b; } @Override public boolean onTouchEvent(MotionEvent ev) { LogUtil.logi("------onTouchEvent----------------------->"); boolean b = super.onTouchEvent(ev); LogUtil.logi("------onTouchEvent----------------------->" + b); return super.onTouchEvent(ev); } }
重写ListView之后,将xml布局中的ListView控件替换成重写的MyListViewview,下面是简化后的布局代码:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/color_white"> <com.wyk.kk.view.SideBar android:id="@+id/sidebar" android:layout_width="30dp" android:layout_height="wrap_content" android:visibility="gone" android:layout_alignParentRight="true"/> <com.wyk.kk.version.five.view.MyListViewview android:id="@+id/listview" android:layout_toLeftOf="@id/sidebar" android:listSelector="@android:color/transparent" android:layout_width="match_parent" android:layout_height="match_parent" android:scrollbars="none" android:divider="@null"/> <TextView android:id="@+id/tv_peoplehub_myfriend_sidebar_dialog" android:layout_width="64dp" android:layout_height="64dp" android:background="@drawable/shape_dialog_sidebar" android:layout_centerInParent="true" android:gravity="center" android:text="A" android:textColor="@android:color/white" android:textSize="@dimen/textsize_36" android:visibility="gone" /> </RelativeLayout>
那就运行起来后点击条目,日志打印如下:
328 13413-13413/com.wyk.kk I/info======>: ---dispatchTouchEvent---> 328 13413-13413/com.wyk.kk I/info======>: ---onInterceptTouchEvent---> 329 13413-13413/com.wyk.kk I/info======>: ---onInterceptTouchEvent--->false 329 13413-13413/com.wyk.kk I/info======>: ---dispatchTouchEvent--->true 408 13413-13413/com.wyk.kk I/info======>: ---dispatchTouchEvent---> 408 13413-13413/com.wyk.kk I/info======>: ---onInterceptTouchEvent---> 408 13413-13413/com.wyk.kk I/info======>: ---onInterceptTouchEvent--->false 409 13413-13413/com.wyk.kk I/info======>: ---dispatchTouchEvent--->true
点击事件并未响应,可以看到dispatchTouchEvent的返回值为true(备注:文章最后面会贴上关于事件分发机制的牛逼文章),则说明事件已经被消费了,那么到底是谁消费事件呢?回头来看看ListView条目的布局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content"> <RelativeLayout android:id="@+id/rl_people_hub_myfriend_tag" android:layout_width="match_parent" android:layout_height="28dp" android:visibility="gone"> <TextView android:id="@+id/tv_item_people_hub_myfriend_tag" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:gravity="center_vertical" android:paddingLeft="10dip" android:textColor="@color/color_blacker" android:textSize="14sp" /> <View android:layout_width="match_parent" android:layout_height="0.3dp" android:layout_alignParentBottom="true" android:background="@color/item_line_cover" /> </RelativeLayout> <RelativeLayout android:id="@+id/rl_my_friend_item" android:layout_width="match_parent" android:layout_height="60dp" android:layout_below="@id/rl_people_hub_myfriend_tag" android:background="@drawable/selector_header_back" android:clickable="true"> //这里设置了clickable <de.hdodenhof.circleimageview.CircleImageView android:id="@+id/iv_people_hub_myfriend_ico" android:layout_width="48dp" android:layout_height="48dp" android:layout_centerVertical="true" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:src="@drawable/yh_usercenter_editdata_photo" /> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginLeft="4dp" android:layout_toRightOf="@id/iv_people_hub_myfriend_ico"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:gravity="center_vertical" android:orientation="horizontal"> <TextView android:id="@+id/tv_people_hub_myfriend_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="4dip" android:width="100dp" android:singleLine="true" android:textColor="@color/text_gray" android:textSize="14sp" /> <TextView android:id="@+id/tv_friend_phone" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_marginLeft="4dip" android:width="100dp" android:singleLine="true" android:textColor="@color/text_gray_01" android:textSize="14sp" /> </LinearLayout> <CheckBox android:id="@+id/checkbox_select" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:layout_marginRight="30dp" android:visibility="gone"/> //note <View android:layout_width="match_parent" android:layout_height="0.3dp" android:layout_alignParentBottom="true" android:background="@color/item_line_cover" /> </RelativeLayout> </RelativeLayout> </RelativeLayout>
上面ListView的条目布局中出现CheckBox这控件和RelativeLayout布局中存在属性android:clickable=”true” ,那么会不会是这两者抢走了事件?排除法对其进行探究之后发现:
1.当将CheckBox控件从布局移除之后,ListView事件仍无响应;
2.将CheckBox移除,从RelativeLayout中删除属性clickable,ListView事件有响应;
3.从RelativeLayout删除属性clickable,并将CheckBox设置为显示状态,ListView事件仍无响应。
4.从RelativeLayout中删除属性clickable,而CheckBox是处于Gone状态的,ListView事件有响应;
上面简单描述了可能影响ListView事件无响应测试的结果,那么接下来我们就总结所分析的:
1.在上面的条目布局中,CheckBox并未抢了ListView的事件响应,主要由于在布局中将其设置为android:visibility=”gone”;
2.如果我们将CheckBox设置为显示状态,并删除RelativeLayout里的属性clickable,则ListView事件仍无响应,那么改下MyListView里重写的onTouchEvent方法实现:
@Override public boolean onTouchEvent(MotionEvent ev) { boolean b = false; switch(ev.getAction()){ case MotionEvent.ACTION_DOWN: LogUtil.logi("onTouchEvent---down->"); b = super.onTouchEvent(ev); LogUtil.logi("onTouchEvent---down->" + b); break; case MotionEvent.ACTION_MOVE: LogUtil.logi("onTouchEvent---move->"); b = super.onTouchEvent(ev); LogUtil.logi("onTouchEvent---move->" + b); break; case MotionEvent.ACTION_UP: LogUtil.logi("onTouchEvent---up->"); b = super.onTouchEvent(ev); LogUtil.logi("onTouchEvent---up->" + b); break; } return b; }
日志打印如下(ListView事件是无响应的情况),可以看到下面的onTouchEvent结果是返回true,分别对应事件的按下/触摸/抬起这三个动作;
280 25188-25188/com.wyk.kk I/info======>: dispatchTouchEvent----> 280 25188-25188/com.wyk.kk I/info======>: onInterceptTouchEvent----> 281 25188-25188/com.wyk.kk I/info======>: onInterceptTouchEvent---->false 285 25188-25188/com.wyk.kk I/info======>: onTouchEvent---down-> 286 25188-25188/com.wyk.kk I/info======>: onTouchEvent---down->true 286 25188-25188/com.wyk.kk I/info======>: dispatchTouchEvent---->true 325 25188-25188/com.wyk.kk I/info======>: dispatchTouchEvent----> 325 25188-25188/com.wyk.kk I/info======>: onTouchEvent---move-> 326 25188-25188/com.wyk.kk I/info======>: onTouchEvent---move->true 326 25188-25188/com.wyk.kk I/info======>: dispatchTouchEvent---->true 341 25188-25188/com.wyk.kk I/info======>: dispatchTouchEvent----> 342 25188-25188/com.wyk.kk I/info======>: onTouchEvent---move-> 342 25188-25188/com.wyk.kk I/info======>: onTouchEvent---move->true 342 25188-25188/com.wyk.kk I/info======>: dispatchTouchEvent---->true 352 25188-25188/com.wyk.kk I/info======>: dispatchTouchEvent----> 352 25188-25188/com.wyk.kk I/info======>: onTouchEvent---up-> 353 25188-25188/com.wyk.kk I/info======>: onTouchEvent---up->true 353 25188-25188/com.wyk.kk I/info======>: dispatchTouchEvent---->true
如果在CheckBox加属性”android:focusable=”false”,运行之后点击是有响应事件的,日志打印结果跟上面的一样。之所以刚才条目无响应,可能是由于CheckBox捕获了焦点;当checkBox设置为隐藏的状态(Gone、invisible),则点击事件还是交给listView去处理的,这又是为何? 个人猜测的原因是:”控件都处于隐藏的状态,那捕获焦点还能做什么? “(补充:接下来的分析中会看到在AbsListView的onTouchUp方法会进行条目的子View焦点判断,最终会调用到View的hasFocusable方法,可以在hasFocusable方法里看到,只要View的状态不是VISIBLE,View的hasFocusable方法则返回false),看看View源码的hasFocusable方法的实现如下:
public boolean hasFocusable() { if (!isFocusableInTouchMode()) { for (ViewParent p = mParent; p instanceof ViewGroup; p = p.getParent()) { final ViewGroup g = (ViewGroup) p; if (g.shouldBlockFocusForTouchscreen()) { return false; } } } return (mViewFlags & VISIBILITY_MASK) == VISIBLE && isFocusable(); }
可以看到是先遍历View的父节点,只要有一父节点阻塞了焦点,该子View就获取不到焦点,返回false;如果父节点并没有阻塞焦点,最后还要根据View的显示状态和焦点状态进行判断;上面的CheckBox控件捕获焦点和这里View的焦点是否存在的判断 貌似没有关联,其实不是的!ListView继承自AbsListView,再想想事件响应一般都是在onTouchEvent方法里,那么看看AbsListView的onTouchEvent方法 (为什么不看ListView的onTouchEvent方法? 因为ListView并没有重写onTouchEvent方法),而条目响应是在手指抬起的时候,那么就可以看看onTouchUp这个方法,因为onTouchUp是在onTouchEvent响应手指抬起事件的子方法,可以看到在这里面有这么一段代码:
private void onTouchUp(MotionEvent ev) { //这里的child == ListView某条条目 final View child = getChildAt(motionPosition - mFirstPosition); //省略 final float x = ev.getX(); final boolean inList = x > mListPadding.left && x < getWidth() - mListPadding.right; //条目进行范围判断以及该条目的子View焦点的判断 if (inList && !child.hasFocusable()) { //note if(mPerformClick == null) { mPerformClick = new PerformClick();//条目事件响应逻辑 } final AbsListView.PerformClick performClick = mPerformClick; performClick.mClickMotionPosition = motionPosition; performClick.rememberWindowAttachCount(); //省略
在上面的小段源码中,在注释”note”的该行代码,可以看到需要对条目进行范围判断以及item的子View焦点的判断,而这里的焦点判断就显得很重要了,只有当条目的全部子View当前的焦点都为false时,才会进行条目事件的响应(看下面ViewGroup的hasFocusable的实现),所以item里的任意子View的焦点必须为false,才能进入该方法,条目事件才可以得到响应。再回来看CheckBox,由于加属性”android:focusable=”false”之后,ListView是可以响应事件的,这样就解决 “ListView由于条目的布局存在CheckBox控件导致条目点击无响应的问题”;
第二种解决方法:
将ListView条目的根布局(必须为ViewGroup)加上属性android:descendantFocusability=”blocksDescendants”
顺便学习下descendantFocusability属性
该属性主要用于解决ViewGroup和其子控件焦点优先级的问题,这个属性的存在,就很方便解决上面ListView与CheckBox事件的问题。属性的值有三种:
beforeDescendants:viewgroup会优先其子类控件而获取到焦点 afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点 blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点
探究下android:descendantFocusability=”blocksDescendants”的作用点,先看看下面ViewGroup的hasFocusable的方法:
//ViewGroup的hasFocusable @Override public boolean hasFocusable() { if ((mViewFlags & VISIBILITY_MASK) != VISIBLE) { return false; } if (isFocusable()) { return true; } final int descendantFocusability = getDescendantFocusability(); if (descendantFocusability != FOCUS_BLOCK_DESCENDANTS) { //note final int count = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < count; i++) { final View child = children[i]; if (child.hasFocusable()) { return true; } } } return false; }
在上面 标注 “note”的那行代码,可以看到如果ViewGroup有”blocksDescendants”属性值作用的时候,ViewGroup的hasFocusable直接就返回false,而AbsListView类的onTouchUp方法里的mPerformClick就可以被执行,条目事件也就可以得到响应咯。这里还可以看到关于ViewGroup和View关于焦点的判断流程,从ViewGroup的hasFocusable方法看到当显示状态和焦点存在满足的条件下,会进行ViewGroup的子View的焦点的判断,只要有一个子View焦点返回true,则ViewGroup的焦点直接返回true;
3.探究完CheckBox的问题,接着看看 “布局由于控件的clickable属性的存在 而导致事件并未响应的问题”
其实布局设置里有控件设置clickable属性值为true,则表示其能处理Touch事件,看下面
View的onTouchEvent方法的部分源码
public boolean onTouchEvent(MotionEvent event) { //省略 if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); } //省略 }
看了上面源码,是不是可以理解咯,当设置clickable/long_clickable/context_clickable 任一属性,则View在抬起的事件直接返回true,表示其能消费,则事件响应在该View,导致条目点击是无响应的;
在这里记录另外一种解决ListView条目点击未响应的解决方法
其实ListView的条目布局不都是有一个根控件,比如上面的RelativeLayout根布局,在ListView的适配器BaseAdapter的getView方法不是要将布局inflate之后再寻找布局里的控件,为其子View设置数据以及交互,那么拿到RelativeLayout根布局也是可以的,只要为它设置id,find到之后只要设置点击事件及实现交互逻辑,这样即可。
感觉思路是不是挺乱的,总结下
1.ListView的条目布局如果有CheckBox、Button这些控件,可以设置其焦点为false,也可以为根布局设置属性descendantFocusability,属性值为blocksDescendants,这样子条目点击事件就可以得到响应咯
2.ListView的条目布局里的控件不可设置Clickable为true的属性,否则事件会被该控件以do nothing的方式消费。
3.简单总结下ListView条目响应的大致回调流程
类 方法 AbsListView onTouchEvent onTouchUp ------重点在:child.hasFocusable() AbsListView内部类PerformClick performItemClick AdapterView performItemClick ----- mOnItemClickListener.onItemClick(this, view, position, id);事件响应 //---------------------------------------------------------------- 顺便看下ListView的继承关系 ListView ----》 AbsListView ----》AdapterView ----》ViewGroup ----》View
参考博文
事件分发机制:http://www.jianshu.com/p/e99b5e8bd67b
ListView问题探讨:http://www.jb51.net/article/77792.htm
Touch事件处理 :http://www.cnblogs.com/xiaoweiz/p/3838682.html
相关文章推荐
- Android深入探究笔记之二 -- 打开一个新的 Activity 并传递参数与如何响应控件的点击事件 .
- Android中listview中条目及控件点击事件position位置不对
- Android中ListView(gridview)的item中有button等子点击控件时不能响应点击事件的原因
- Android如何让ListView的子组件按钮响应单击事件并修改该项子控件内容
- Android控件listView条目不能点击问题
- 解决Android中Listview条目里面有checkbox、radiobutton无法响应条目点击事件
- [转]Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件
- Android深入探究笔记之二 -- 打开一个新的 Activity 并传递参数与如何响应控件的点击事件
- Android ListView最佳处理方式,ListView拖动防重复数据显示,单击响应子控件
- Android中自定义ListView无法响应OnItemClickListener中的onItemClick方法问题解决方案<转>
- Android ListView 控件
- Android中自定义ListView无法响应OnItemClickListener中的onItemClick方法问题解决方案
- android 自定义listview无法响应点击事件OnItemClickListener的原因
- Android ListView 控件
- 【Android游戏开发二十三】自定义ListView【通用】适配器并实现监听控件!
- 解决Android的ListView控件滚动时背景变黑
- 【Android游戏开发二十三】自定义ListView【通用】适配器并实现监听控件!
- 【Android游戏开发二十三】自定义ListView【通用】适配器并实现监听控件!
- Android ListView控件基本用法 (转载)
- Android控件之ListView探究二