自定义view系列(3)--给自定义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内部,则回调点击监听接口,否则不予理会。

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

自定义view系列(3)--给自定义View添加点击事件_第1张图片

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

接着,我们需要重写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信息:

自定义view系列(3)--给自定义View添加点击事件_第2张图片

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

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);
    }
}



你可能感兴趣的:(自定义view系列)