zoukankan      html  css  js  c++  java
  • 如何正确的给ViewGroup设置OnClickListener

    http://shymanzhu.com/2017/07/23/%E5%A6%82%E4%BD%95%E6%AD%A3%E7%A1%AE%E7%9A%84%E7%BB%99ViewGroup%E8%AE%BE%E7%BD%AEOnClickListener/

    如何正确的给ViewGroup设置OnClickListener

    在Android的日常开发中,我们总会碰到要给某个LinearLayout、RelativeLayout等设置OnClickListener,以便达到点击其子view能够触发设置的OnClickListener。但是当我们点击子view的时候,对应的Listener并没有触发到,这是为什么呢,接下来我们将结合例子从源码角度去解释它。

    实例

      我们从一个简单的需求出发:有一个Button和一个TextView,当他们被点击后都要响应相同的事件。我们可以同时设置Button和TextView的点击监听,但是这样代码量就比较大了。作为爱偷懒的程序员,又怎么愿意这么干呢,反正我的内心是拒绝的。我们容易想到的方法就是给Button和TextView所在的ViewGroup设置点击事件。可能我们会这样做:

    • 假如布局文件我们是这样写的:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <?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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">
     
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/layout_group"
    android:gravity="center_vertical"
    android:orientation="horizontal">
    <Button
    android:layout_marginLeft="100dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button1"/>
     
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="@string/test"
    android:textSize="30sp"
    android:layout_marginLeft="30dp"
    android:id="@+id/tv"/>
     
    </LinearLayout>
     
    </RelativeLayout>
    • 代码内容也很简单,信手捏来
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package com.zhuzp.test;
     
    import android.os.Bundle;
    import android.app.Activity;
    import android.util.Log;
    import android.view.View;
    import android.widget.LinearLayout;
     
    /**
    * Created by zhuzp on 2016/9/29.
    */
     
    public class TestActivity extends Activity {
    private static final String TAG = "TestActivity";
    private View layoutView;
     
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.test_activity);
    initView();
    }
     
    private void initView(){
    layoutView = findViewById(R.id.layout_group);
    layoutView.setOnClickListener(mClickListener);
    }
     
    private View.OnClickListener mClickListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    Log.d(TAG,"onClick...."+ v.toString());
    }
    };
    }

    我们在代码中给Button和TextView所在的LinearLayout中设置了点击事件监听。Easy,是不是?然后我们看下运行的结果吧。

     点击Button怎么没有打印出来,但是点击TextView的时候有打印,打印的结果如下:

    1
    01-02 14:19:32.220 5891-5891/com.zhuzp.test D/TestActivity: onClick....com.zhuzp.test.widget.MyLinearLayout{41907c58 V.E...C. ...P.... 64,16-339,64 #7f0c0063 app:id/layout_group}

    不和常理啊,按道理应该都有打印出来的,对不对?嗯,有可能你知道问题出在哪,下面我先给出几种解决方法,然后再解释为什么这几种方法是可行的。

    解决方法

    1,重写LinearLayout的setOnClickListener(View.OnClickListener listener)方法,通过给每个子view设置点击事件。

    比如我们定义个MyLinearLayout,继承LinerLayout。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package com.zhuzp.test.widget;
     
    import android.content.Context;
    import android.util.AttributeSet;
    import android.view.MotionEvent;
    import android.view.View;
    import android.widget.Button;
    import android.widget.LinearLayout;
     
    /**
    * Created by zhuzp on 2016/9/28.
    */
     
    public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    }
    @Override
    public void setOnClickListener(OnClickListener l) {
    super.setOnClickListener(l);
    int N = getChildCount();
    for (int i = 0; i < N; i++){
    View view = getChildAt(i);
    view.setOnClickListener(l);
    }
    }
    }

      然后修改布局文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <?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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">
     
    <com.zhuzp.test.widget.MyLinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/layout_group"
    android:gravity="center_vertical"
    android:orientation="horizontal">
    <Button
    android:id="@+id/btn"
    android:layout_marginLeft="100dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button1"/>
     
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Text"
    android:textSize="30sp"
    android:layout_marginLeft="30dp"
    android:id="@+id/tv"/>
     
    </com.zhuzp.test.widget.MyLinearLayout>
     
    </RelativeLayout>

      然后我们看下运行的结果:

    1
    2
    10-08 12:00:12.765 6247-62471/com.zhuzp.test D/TestActivity: onClick....android.widget.Button{4190a4a0 VFED..C. ...P.... 100,0-187,48 #7f0c0064 app:id/btn}
    10-08 12:00:12.765 6247-6247/com.zhuzp.test D/TestActivity: onClick....android.widget.TextView{41911480 V.ED..C. ...P.... 217,3-275,44 #7f0c0066 app:id/tv}

    从结果来看,看样子我们是已经解决了问题,OK,这就是第一种方法。

    2,重写LinearLayout的onInterceptTouchEvent()方法,返回true。

     代码就不重复了,和方法1中基本类似。结果每次点击Button或者TextView时,打印信息为:

    1
    2
    10-08 12:02:12.765 6247-6247/com.zhuzp.test D/TestActivity: onClick....android.widget.LinearLayout{419cd1b0 V.E...C. ...P.... 16,16-291,64 #7f0c0063 app:id/layout_group}
    10-08 12:02:14.355 6247-6247/com.zhuzp.test D/TestActivity: onClick....android.widget.LinearLayout{419cd1b0 V.E...C. ...P.... 16,16-291,64 #7f0c0063 app:id/layout_group}

    从打印信息来看我们的问题好像也得到了解决,但是如果你的Button 和TextView设置了背景selector,是没有相应的效果的。

    3,增加Button 的android:clickable=“false”.

      布局文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    <?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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">
     
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/layout_group"
    android:gravity="center_vertical"
    android:orientation="horizontal">
    <Button
    android:layout_marginLeft="100dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:clickable="false"
    android:text="Button1"/>
     
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Text"
    android:textSize="30sp"
    android:layout_marginLeft="30dp"
    android:id="@+id/tv"/>
     
    </LinearLayout>
     
    </RelativeLayout>

    然后,我们看下点击Button和TextView打印的log 的结果:

    1
    2
    10-08 12:05:12.765 6247-6247/com.zhuzp.test D/TestActivity: onClick....android.widget.LinearLayout{419cd1b0 V.E...C. ...P.... 16,16-291,64 #7f0c0063 app:id/layout_group}
    10-08 12:05:14.355 6247-6247/com.zhuzp.test D/TestActivity: onClick....android.widget.LinearLayout{419cd1b0 V.E...C. ...P.... 16,16-291,64 #7f0c0063 app:id/layout_group}

    从log中我们可以看到,当我们点击Button和TextView的时候,实际上响应点击的是他们所在的ViewGroup——LinearLayout。

    对比两种方式,很明显第三种方法来得简单

    原因分析

    ​ 首先要明确:

    • 当我们给View(ViewGroup)设置了View.OnClickListener后,View (ViewGroup)是否响应点击事件是在其onTouchEvent(MotionEvent event)判断的。
    • View事件分发的流程图,这里我们只关心ViewGroup和View这一块

    image

     明确上面两点后,解下来就比较容易理解了。

    方法1其实是一种比较hack的方法,为每个子view设置了监听,最终由每个子view去响应点击事件。方法二,结合上面的图就很好理解了,子view根本没有收到touch 事件,touch事件由ViewGroup的onTouchEvent()方法处理,在onTouchEvent()方法中会去处理点击事件。方法3为什么可以呢,从上面的图你可能已经猜到在Button设置了android:clickable=”false”属性之后,Button 的onTouchEvent()应该是返回了false。究竟是否是这样的呢,接下来我们一起结合源码调试下,你没听错,就是调试。

    源码解析

     知道上面的结论后,我们开始结合代码验证下:

    • 创建一个类继承Button,方法比如命名为MyButton,重写onTouchEvent()。
    • 在布局文件中引用创建的MyButton。

    MyButton代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package com.zhuzp.test.widget;
     
    import android.content.Context;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.widget.Button;
     
    /**
    * Created by zhuzp on 2016/9/29.
    */
     
    public class MyButton extends Button {
    private static final String TAG = "MyButton";
    public MyButton(Context context, AttributeSet attrs) {
    super(context, attrs);
    }
     
    @Override
    public boolean onTouchEvent(MotionEvent event) {
    boolean result = super.onTouchEvent(event);
    Log.d(TAG,"onTouchEvent = " + result);
    return result;
    }
     
    }

    布局文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <?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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin">
     
    <LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/layout_group"
    android:gravity="center_vertical"
    android:orientation="horizontal">
    <com.zhuzp.test.widget.MyButton
    android:layout_marginLeft="100dp"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Button1"/>
     
    <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Text"
    android:textSize="30sp"
    android:layout_marginLeft="30dp"
    android:id="@+id/tv"/>
     
    </LinearLayout>
     
    </RelativeLayout>

    最后Activity代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    package com.zhuzp.test;
     
    import android.os.Bundle;
    import android.app.Activity;
    import android.util.Log;
    import android.view.View;
    import android.widget.LinearLayout;
     
    /**
    * Created by zhuzp on 2016/9/29.
    */
     
    public class TestActivity extends Activity {
    private static final String TAG = "TestActivity";
    private View layoutView;
     
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.test_activity);
    initView();
    }
     
    private void initView(){
    layoutView = findViewById(R.id.layout_group);
    layoutView.setOnClickListener(mClickListener);
    }
     
    private View.OnClickListener mClickListener = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    Log.d(TAG,"onClick...."+ v.toString());
    }
    };
    }

     整个代码内容都很简单,为什么我们要定义一个继承Button的类,这里主要是为了通过Android Studio结合源码调试。我们在MyButton的”boolean result = super.onTouchEvent(event);”前面打个断点,然后开始debug调试。

    • 在你的Android设备上点击button

    image

    如上图,然后按”F7”进入super.onTouchEvent(),如果你有安装多个sdk版本,选择你Android设备对应的版本,比如我的设备是Android4.4,那么我就选择API 19。

    image

    • 选择对应的的版本后,进入到TextView(Button继承自TextView)的onTouchEvent()方法,

    image

    按“F8”到下一步,然后到“final boolean superResult = super.onTouchEvent(event);”,按“F7”进入View的onTouchEvent()方法。

    image

    然后一直“F8”,然后走到”if (((viewFlags & CLICKABLE) == CLICKABLE ||(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))”中,你会发现进入了这个if语句后,View的onTouchEvent()方法返回的都是true,如果没进if语句,返回的就是false。

    当从View的onTouchEvent()中返回后,回到TextView.onTouchEvent()方法,然后一步步走下去,你会发现最终TextView.onTouchEvent()返回的就是super.onTouchEvent()的返回值。所以要想上面的例子中的LinearLayout响应点击事件,要设置Button的android:clickable=”false”。

  • 相关阅读:
    木马后门入侵与RKHunter,ClamAV检测工具
    Jenkins环境搭建
    Mha-Atlas-MySQL高可用
    JAVA企业级应用服务器之TOMCAT实战
    Keepalived高可用集群
    scp ssh-key连接原理
    jumpserver跳板机搭建
    DNS域名解析服务器
    DHCP服务
    Keepalived高可用集群
  • 原文地址:https://www.cnblogs.com/jianglijs/p/9392156.html
Copyright © 2011-2022 走看看