先来几句废话:终于下定决心开始写博客了,Android好奇宝宝系列主要是尝试解释一些开发过程中遇到的一些疑问。特别是一些知道该怎么做,但不知道为什么要这么做的问题。本猿能力有限,有错误处请各路大牛不吝赐教,万分感谢!!
好了,废话完了,接下来进入正题。
Android好奇宝宝_开山篇_解析ListView点击事件冲突原因
问题:
当ListView的item布局中有Button等控件时,itemClick事件会失效。
解决方法:
(1)在item布局的最外层父控件添加属性:
android:descendantFocusability="blocksDescendants"
(2)在item布局中为所有可获得焦点的控件添加属性:
android:focusable="false"
网上挺多帖子都说必须两个都得设置,其实只需要设置其中一个就行了,看完下面就知道为什么了。
开始分析:
首先思考:我们的问题是itemClick事件不生效,那么正常情况下itemClick事件是怎么产生的?
答:点击事件应该包括两个动作:按下(down)和松开(up)。所以点击事件应该是在up触摸动作结束时产生的(其实其它控件包括Button的点击事件都是这样的)。
开始查找ListView对up触摸动作的处理,在其父类AbsListView找到。
AbsListView的onTouchEvent(MotionEvent ev)代码片段:
case MotionEvent.ACTION_UP: {
onTouchUp(ev);
break;
}
转交onTouchUp(MotionEvent ev)处理,onTouchUp会进行一些触摸位置、当前触摸模式等东西的检查和判断,但在正常情况下会执行:
performClick.run();
performClick是一个内部线程类,run方法中执行了:
performItemClick(view, motionPosition, adapter.getItemId(motionPosition));
super.performItemClick(view, position, id);
mOnItemClickListener.onItemClick(this, view, position, id);
这个mOnItemClickListener就是我们调用方法listview.setOnItemClickListener(listener)时赋的值,这样就会去调用我们重写过的onItemClick方法。至此,我们就接受到了itemClick事件并可以执行相应的操作。(注:整个流程是基于普通情况下的)
从事件刚产生时开始检查,好吧,就是刚产生时出了问题,在AbsListView的onTouchUp(MotionEvent ev)方法里有这么一个判断:
if (inList && !child.hasFocusable())
其中inList是前面计算出up的位置是不是在ListView中,这里不用理会。
主要是!child.hasFocusable(),从命名猜测,这个if的作用就是当触摸位置位于ListView中并且该位置对应的child不可以拥有焦点时,itemClick事件才会触发。
child就是listview中触摸位置对应的item的view,当child里有按钮,checkbox等控件时,child.hasFocusable()就会返回true,就不会进入这个判断,performClick.run()也不会被执行,itemClick事件就夭折了。
hasFocusable()是View类的方法,我们来看下它的注释:
* Returns true if this view is focusable or if it contains a reachable View
* for which {@link #hasFocusable()} returns true. A "reachable hasFocusable()"
* is a View whose parents do not block descendants focus.
在这个View为可聚焦或者一个可到达的子View为可聚焦时,返回True。“可到达的子View”指那些没有被父 View阻断了焦点的View。
所以现在只要让child.hasFocusable()返回false,就可以让itemClick事件继续下去,也就是刚开始时说的两个方法。
(1)在item布局的最外层父控件添加属性:
android:descendantFocusability="blocksDescendants"
该方法就是设置父View分派焦点给子View的模式,blocksDescendants表示阻断焦点,不分派焦点给子View。
(2)在item布局中为所有可获得焦点的控件添加属性:
android:focusable="false"
该方法就是子View自己表明自己不需要焦点。当然,当你的item布局中同时有几个默认为focusable的控件时,你必须为每个控件都加上这个属性,相比还是第一种方法比较方便。
好奇延续:
(1)为什么Button和CheckBox等控件会导致itemClick事件失败,而TextView等就不会呢?
答:因为Button和Checkbox等控件focusable默认为true,而TextView等默认为false。就像Button默认为可点击,而TextView默认为不可点击一样。
这是Button控件的默认样式:
<style name="Widget.Button">
<item name="android:background">@android:drawable/btn_default</item>
<item name="android:focusable">true</item>
<item name="android:clickable">true</item>
<item name="android:textAppearance">?android:attr/textAppearanceSmallInverse</item>
<item name="android:textColor">@android:color/primary_text_light</item>
<item name="android:gravity">center_vertical|center_horizontal</item>
</style>
(2)为什么要加上这个if,谷歌工程师闲的没事做吗?
答:每一个if都是为了去适应某个场景。
场景:itemView中有EditText
在没有使用上面所述的两种方法的条件下:
<1>有这个if时,点击EditText,获得焦点,等待用户输入
<2>没有这个if时,点击EditText,触发itemClick事件,EditeText无变化
这个if的存在价值就是为了EditText等有需要获得焦点的控件能正常显示。
有兴趣的可以去试下写一个有EditText的ListView,然后使用上面说的两个方法中的一个,就会知道这个if存在的意义了。
以上所说的对GridView同样适用!
好的,第一篇就到这里了,以后应该会每周一至两篇持续更新,我要坚持!
有问题的、有发现错误的、有好意见的。。。欢迎评论