给自定义View添加点击事件

这几天一直在看《Android开发艺术探索》和《android群英传》中关于自定义view的章节,结合着网上大神的一些心得分享,感觉自定义view这一块受益匪浅,这里做个心得笔记,主要记录一下view与用户交互的一些知识。

自定义view与用户交互用的最多的就是单击事件,其次的还有双击事件、长按事件、滑动事件等,所以就需要做好view的事件监听。

如果我们继承了View,也绘制好了控件,但是不重写onTouchEvent()方法的话,设置点击事件一般也是没用的,但也不是一定没用,下面先介绍一种比较简单的点击事件实现方式,也是从张鸿洋大神的一篇博客中看到的。

设置view的点击事件实现方式一:

在View的构造方法中直接setOnClickListener,如下代码:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public TouchEventTest(Context context) {  
  2.         this(context, null);  
  3.     }  
  4.   
  5.     public TouchEventTest(Context context, AttributeSet attrs) {  
  6.         this(context, attrs, 0);  
  7.     }  
  8.   
  9.     public TouchEventTest(Context context, AttributeSet attrs, int defStyleAttr) {  
  10.         super(context, attrs, defStyleAttr);  
  11.         this.setOnClickListener(new OnClickListener() {  
  12.             @Override  
  13.             public void onClick(View v) {  
  14.                 Logger.e("view内部设置OnClickListener","");  
  15.             }  
  16.         });  
  17.     }  

如上代码即可实现该自定义view的点击监听,但是有很大的局限性,首先这样设置监听很死板,不能供外部调用,其次经过测试,如果你这样写了代码,并且又重写的该View的OnTouchEvent()方法,那么不管你的onTouchEvent方法中是直接return super.onTouchEvent()还是做了任何其他逻辑,都不能触发这个监听事件,也就是说在这种情况下这种方式设置监听根本没用了,而这直接影响到了我们自定义View的扩展性,所以建议别这么用,虽然简单,但是麻烦事也多,不过这种方式也有一种适用情况,就是如果自定义View真的只需要自己内部处理逻辑,不需要外部的参与,那么这样设置也行,不过真的不推荐大家使用这种方式除非业务逻辑允许。

 

 

下面就介绍第二种给自定义view设置监听的方式,也是个人认为比较正常且通用的一种方式。

即重写onTouchEvent方法,然后判断所点击的坐标是否处于自定义view内部。如果处于自定义view内部,则回调点击监听接口,否则不予理会。

在说这种方式之前,先普及一下各种坐标知识,毕竟一会要用到。

如上面的图示:清楚的体现了各个坐标的含义。

接着,我们需要重写setOnClickListener()方法,代码如下:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     public void setOnClickListener(OnClickListener l) {  
  3.         mListener = l;  
  4.     }  


就是把OnClickListener赋值,当然我们也可以不使用Override注解,又或者我们可以自定义接口和方法,并提供一个设置自定义监听的方法,代码如下:

 

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public void setOnViewClick(onViewClick click) {  
  2.         this.mViewClick = click;  
  3.     }  
  4.   
  5.   
  6.     public interface onViewClick {  
  7.         /** 
  8.          * @param scrollX 从按下到抬起,X轴方向移动的距离 
  9.          * @param scrollY 从按下到抬起,Y轴方向移动的距离 
  10.          */  
  11.         void onClick(float scrollX, float scrollY);  
  12.     }  



 

然后我们重写onTouchEvent方法,如下是一个通用的设置点击事件模板方法,

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     public boolean onTouchEvent(MotionEvent event) {  
  3.         int x = (int) event.getX();  
  4.         int y = (int) event.getY();  
  5.         int action = event.getAction();  
  6.         switch (action) {  
  7.             case MotionEvent.ACTION_DOWN:  
  8.                 break;  
  9.             case MotionEvent.ACTION_MOVE:   
  10.                 break;  
  11.             case MotionEvent.ACTION_UP:  
  12.                 if (x + getLeft() < getRight() && y + getTop() < getBottom()) {  
  13.                     mListener.onClick(this);  
  14.                     mViewClick.onClick(x - startX, y - startY);  
  15.                 }  
  16.                 break;  
  17.         }  
  18.         return true;  
  19.     }  


注意这里一定要返回true或者根据自己的业务逻辑决定返回true还是false。根据view的事件分发流程,如果该自定义view不处理ACTION_DOWN事件,那么以后的所有事件都不会给他处理,结合代码说一下:当用户按下时,进入到MotionEvent.ACTION_DOWN这个case语句中,然后break掉,之后返回了true,重点就在这里,如果这里你返回了false,说明该view不处理事件,所以以后的各种状态如ACTION_MOVE和ACTION_UP都不会再触发,因为该事件已经交由该view的某个父类处理了,不会再传递给子view了。而如果返回了true,就说明该view接管了以后的所有操作,由该view处理相应的事件。

 

另外再说明一点:处理点击事件一般都是在ACTION_UP时进行,而处理滑动之类的一般都在ACTION_MOVE时进行!!!

下面我们在代码中验证一下是否有效,代码如下(布局文件就不贴了):

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
  1. public class TouchEventTestActivity extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_touch_event_test);  
  7.         TouchEventTest testView = (TouchEventTest) findViewById(R.id.TouchEventTest);  
  8.         testView.setOnClickListener(new View.OnClickListener() {  
  9.             @Override  
  10.             public void onClick(View v) {  
  11.                 Logger.e("setOnClickListener,view.getClass():" + v.getClass() + "");  
  12.             }  
  13.         });  
  14.         testView.setOnViewClick(new TouchEventTest.onViewClick() {  
  15.             @Override  
  16.             public void onClick(float scrollX, float scrollY) {  
  17.                 Logger.e("x轴移动了:" + scrollX + " px,y轴移动了:" + scrollY + " px", "");  
  18.             }  
  19.         });  
  20.   
  21.     }  
  22. }  


我们看下打印出的Log信息:

 

最后贴出完整代码,让大家更好理解:

 

[java] view plain copy
 
 在CODE上查看代码片派生到我的代码片
    1. package com.lanma.customviewproject.views;  
    2.   
    3. import android.content.Context;  
    4. import android.util.AttributeSet;  
    5. import android.view.MotionEvent;  
    6. import android.view.View;  
    7.   
    8. /** 
    9.  * 作者 qiang_xi on 2016/8/23 15:05. 
    10.  */  
    11. public class TouchEventTest extends View {  
    12.     private OnClickListener mListener;  
    13.     private onViewClick mViewClick;  
    14.     private int startRawX;  
    15.     private int startRawY;  
    16.   
    17.     public TouchEventTest(Context context) {  
    18.         this(context, null);  
    19.     }  
    20.   
    21.     public TouchEventTest(Context context, AttributeSet attrs) {  
    22.         this(context, attrs, 0);  
    23.     }  
    24.   
    25.     public TouchEventTest(Context context, AttributeSet attrs, int defStyleAttr) {  
    26.         super(context, attrs, defStyleAttr);  
    27. //        this.setOnClickListener(new OnClickListener() {  
    28. //            @Override  
    29. //            public void onClick(View v) {  
    30. //                Logger.e("view内部设置OnClickListener", "");  
    31. //            }  
    32. //        });  
    33.     }  
    34.   
    35.     @Override  
    36.     public boolean onTouchEvent(MotionEvent event) {  
    37.         int x = (int) event.getX();  
    38.         int y = (int) event.getY();  
    39.         int rawX = (int) event.getRawX();  
    40.         int rawY = (int) event.getRawY();  
    41.         int action = event.getAction();  
    42.         switch (action) {  
    43.             case MotionEvent.ACTION_DOWN:  
    44.                 startRawX = rawX;  
    45.                 startRawY = rawY;  
    46.                 break;  
    47.             case MotionEvent.ACTION_MOVE:  
    48.   
    49.                 break;  
    50.             case MotionEvent.ACTION_UP:  
    51.                 if (x + getLeft() < getRight() && y + getTop() < getBottom()) {  
    52.                     mListener.onClick(this);  
    53.                     mViewClick.onClick(rawX - startRawX, rawY - startRawY);  
    54.                 }  
    55.                 break;  
    56.         }  
    57.         return true;  
    58.     }  
    59.   
    60.     @Override  
    61.     public void setOnClickListener(OnClickListener l) {  
    62.         mListener = l;  
    63.     }  
    64.   
    65.     public void setOnViewClick(onViewClick click) {  
    66.         this.mViewClick = click;  
    67.     }  
    68.   
    69.   
    70.     public interface onViewClick {  
    71.         /** 
    72.          * @param scrollX 从按下到抬起,X轴方向移动的距离 
    73.          * @param scrollY 从按下到抬起,Y轴方向移动的距离 
    74.          */  
    75.         void onClick(float scrollX, float scrollY);  
    76.     }  
    77. }  
posted @ 2016-11-27 12:07  天涯海角路  阅读(472)  评论(0)    收藏  举报