给自定义View添加点击事件
这几天一直在看《Android开发艺术探索》和《android群英传》中关于自定义view的章节,结合着网上大神的一些心得分享,感觉自定义view这一块受益匪浅,这里做个心得笔记,主要记录一下view与用户交互的一些知识。
自定义view与用户交互用的最多的就是单击事件,其次的还有双击事件、长按事件、滑动事件等,所以就需要做好view的事件监听。
如果我们继承了View,也绘制好了控件,但是不重写onTouchEvent()方法的话,设置点击事件一般也是没用的,但也不是一定没用,下面先介绍一种比较简单的点击事件实现方式,也是从张鸿洋大神的一篇博客中看到的。
设置view的点击事件实现方式一:
在View的构造方法中直接setOnClickListener,如下代码:
- public TouchEventTest(Context context) {
- this(context, null);
- }
- public TouchEventTest(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public TouchEventTest(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- this.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- Logger.e("view内部设置OnClickListener","");
- }
- });
- }
如上代码即可实现该自定义view的点击监听,但是有很大的局限性,首先这样设置监听很死板,不能供外部调用,其次经过测试,如果你这样写了代码,并且又重写的该View的OnTouchEvent()方法,那么不管你的onTouchEvent方法中是直接return super.onTouchEvent()还是做了任何其他逻辑,都不能触发这个监听事件,也就是说在这种情况下这种方式设置监听根本没用了,而这直接影响到了我们自定义View的扩展性,所以建议别这么用,虽然简单,但是麻烦事也多,不过这种方式也有一种适用情况,就是如果自定义View真的只需要自己内部处理逻辑,不需要外部的参与,那么这样设置也行,不过真的不推荐大家使用这种方式除非业务逻辑允许。
下面就介绍第二种给自定义view设置监听的方式,也是个人认为比较正常且通用的一种方式。
即重写onTouchEvent方法,然后判断所点击的坐标是否处于自定义view内部。如果处于自定义view内部,则回调点击监听接口,否则不予理会。
在说这种方式之前,先普及一下各种坐标知识,毕竟一会要用到。
如上面的图示:清楚的体现了各个坐标的含义。
接着,我们需要重写setOnClickListener()方法,代码如下:
- @Override
- public void setOnClickListener(OnClickListener l) {
- mListener = l;
- }
就是把OnClickListener赋值,当然我们也可以不使用Override注解,又或者我们可以自定义接口和方法,并提供一个设置自定义监听的方法,代码如下:
- public void setOnViewClick(onViewClick click) {
- this.mViewClick = click;
- }
- public interface onViewClick {
- /**
- * @param scrollX 从按下到抬起,X轴方向移动的距离
- * @param scrollY 从按下到抬起,Y轴方向移动的距离
- */
- void onClick(float scrollX, float scrollY);
- }
然后我们重写onTouchEvent方法,如下是一个通用的设置点击事件模板方法,
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- int x = (int) event.getX();
- int y = (int) event.getY();
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- break;
- case MotionEvent.ACTION_MOVE:
- break;
- case MotionEvent.ACTION_UP:
- if (x + getLeft() < getRight() && y + getTop() < getBottom()) {
- mListener.onClick(this);
- mViewClick.onClick(x - startX, y - startY);
- }
- break;
- }
- return true;
- }
注意这里一定要返回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时进行!!!
下面我们在代码中验证一下是否有效,代码如下(布局文件就不贴了):
- public class TouchEventTestActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_touch_event_test);
- TouchEventTest testView = (TouchEventTest) findViewById(R.id.TouchEventTest);
- testView.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Logger.e("setOnClickListener,view.getClass():" + v.getClass() + "");
- }
- });
- testView.setOnViewClick(new TouchEventTest.onViewClick() {
- @Override
- public void onClick(float scrollX, float scrollY) {
- Logger.e("x轴移动了:" + scrollX + " px,y轴移动了:" + scrollY + " px", "");
- }
- });
- }
- }
我们看下打印出的Log信息:
最后贴出完整代码,让大家更好理解:
- package com.lanma.customviewproject.views;
- import android.content.Context;
- import android.util.AttributeSet;
- import android.view.MotionEvent;
- import android.view.View;
- /**
- * 作者 qiang_xi on 2016/8/23 15:05.
- */
- public class TouchEventTest extends View {
- private OnClickListener mListener;
- private onViewClick mViewClick;
- private int startRawX;
- private int startRawY;
- public TouchEventTest(Context context) {
- this(context, null);
- }
- public TouchEventTest(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public TouchEventTest(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- // this.setOnClickListener(new OnClickListener() {
- // @Override
- // public void onClick(View v) {
- // Logger.e("view内部设置OnClickListener", "");
- // }
- // });
- }
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- int x = (int) event.getX();
- int y = (int) event.getY();
- int rawX = (int) event.getRawX();
- int rawY = (int) event.getRawY();
- int action = event.getAction();
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- startRawX = rawX;
- startRawY = rawY;
- break;
- case MotionEvent.ACTION_MOVE:
- break;
- case MotionEvent.ACTION_UP:
- if (x + getLeft() < getRight() && y + getTop() < getBottom()) {
- mListener.onClick(this);
- mViewClick.onClick(rawX - startRawX, rawY - startRawY);
- }
- break;
- }
- return true;
- }
- @Override
- public void setOnClickListener(OnClickListener l) {
- mListener = l;
- }
- public void setOnViewClick(onViewClick click) {
- this.mViewClick = click;
- }
- public interface onViewClick {
- /**
- * @param scrollX 从按下到抬起,X轴方向移动的距离
- * @param scrollY 从按下到抬起,Y轴方向移动的距离
- */
- void onClick(float scrollX, float scrollY);
- }
- }

浙公网安备 33010602011771号