Android好奇宝宝_开山篇_解析ListView点击事件冲突原因

先来几句废话:终于下定决心开始写博客了,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));


AbsListView的 performItemClick()方法会去调用父类(AdapterView)的performItemClick()
super.performItemClick(view, position, id);


最终在AdapterView的performItemClick()方法中:
mOnItemClickListener.onItemClick(this, view, position, id);


这个mOnItemClickListener就是我们调用方法listview.setOnItemClickListener(listener)时赋的值,这样就会去调用我们重写过的onItemClick方法。至此,我们就接受到了itemClick事件并可以执行相应的操作。(注:整个流程是基于普通情况下的)

现在仔细阅读源码,检查是那个环节导致的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同样适用!


好的,第一篇就到这里了,以后应该会每周一至两篇持续更新,我要坚持!

有问题的、有发现错误的、有好意见的。。。欢迎评论

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值