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”。

  • 相关阅读:
    Could A New Linux Base For Tablets/Smartphones Succeed In 2017?
    使用libhybris,glibc和bionic共存时的TLS冲突的问题
    6 Open Source Mobile OS Alternatives To Android in 2018
    Using MultiROM
    GPU drivers are written by the GPU IP vendors and they only provide Android drivers
    Jolla Brings Wayland Atop Android GPU Drivers
    How to Use Libhybris and Android GPU Libraries with Mer (Linux) on the Cubieboard
    闲聊Libhybris
    【ARM-Linux开发】wayland和weston的介绍
    Wayland and X.org problem : Why not following the Android Solution ?
  • 原文地址:https://www.cnblogs.com/jianglijs/p/9392156.html
Copyright © 2011-2022 走看看