Android自定义View

View的Touch事件分发

2018-03-29  本文已影响7人  世道无情

1. 说明


这节课我们就来分析系View的Touch事件分发的顺序。

2. 分析

public class MainActivity extends AppCompatActivity {

    private TouchView touch_view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        touch_view = (TouchView) findViewById(R.id.touch_view);

        touch_view.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("TAG" , "触摸onTouch() -> " + event.getAction()) ;
                return true;
            }
        });


        touch_view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG" , "点击onClick -> ") ;
            }
        });
    }
}
public class TouchView extends View {

    public TouchView(Context context) {
        super(context);
    }

    public TouchView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public TouchView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("TAG" , "onTouchEvent -> " + event.getAction()) ;
        return super.onTouchEvent(event);
    }
}

现象一:onTouchListener、onTouch、onClick三个都有的情况下,并且onTouchListener返回false,表示自己不处理事件,直接把事件分发下去给子控件,所以3个方法都会执行;

03-25 08:18:36.589 3344-3344/com.jackchen.view_day010_2 E/TAG: 触摸onTouch() -> 0
03-25 08:18:36.589 3344-3344/com.jackchen.view_day010_2 E/TAG: onTouchEvent -> 0
03-25 08:18:36.672 3344-3344/com.jackchen.view_day010_2 E/TAG: 触摸onTouch() -> 2
03-25 08:18:36.672 3344-3344/com.jackchen.view_day010_2 E/TAG: onTouchEvent -> 2
03-25 08:18:36.851 3344-3344/com.jackchen.view_day010_2 E/TAG: 触摸onTouch() -> 1
03-25 08:18:36.851 3344-3344/com.jackchen.view_day010_2 E/TAG: onTouchEvent -> 1
03-25 08:18:36.852 3344-3344/com.jackchen.view_day010_2 E/TAG: 点击onClick -> 

由以上可知:0代表DOWN、2代表MOVE、1代表UP,执行顺序如下:
onTouchListener的DOMN -> onTouch的DOWN ->
onTouchListener的MOVE -> onTouch的MOVE ->
onTouchListener的UP -> onTouch的UP ->
onClick

现象二:onTouchListener、onTouch、onClick三个都有的情况下,并且onTouchListener返回true,表示自己处理事件,就不会把事件分发给子控件,所以就只会执行自己而方法;

03-25 08:25:30.493 23991-23991/? E/TAG: 触摸onTouch() -> 0
03-25 08:25:30.625 23991-23991/? E/TAG: 触摸onTouch() -> 2
03-25 08:25:30.625 23991-23991/? E/TAG: 触摸onTouch() -> 1

执行顺序如下:
onTouchListener的DOWN -> onTouchListener的MOVE -> onTouchListener的UP

现象三:只有 onTouchEvent()、onClickListener(),前提是onTouchEvent返回true,表示自己处理事件,就不会把事件分发下去,所以就只执行自己的方法,不会执行下边的方法;

03-25 09:19:11.648 3005-3005/? E/TAG: onTouchEvent -> 0
03-25 09:19:11.752 3005-3005/? E/TAG: onTouchEvent -> 2
03-25 09:19:11.752 3005-3005/? E/TAG: onTouchEvent -> 1

执行顺序如下:
onTouchEvent的DOWN -> onTouchEvent的MOVE -> onTouchEvent的UP,不会执行onClick事件

onClick不执行的原因:
因为onClick事件是在 View中的 onTouchEvent中的 case MotionEvent.ACTION_UP:里边调用了performClick(),而这里onTouchEvent返回的是true,而不是super.onTouchEvent(event),所以就不会执行View中的方法,所以就不会执行 View中的 onTouchEvent中的 case MotionEvent.ACTION_UP,所以onClick不会调用

现象四: onTouchListener onTouchEvent onClick dispatchTouchEvent 都有,前提是dispatchTouchEvent返回true,那么一个方法都不会执行;如果dispatchTouchEvent返回true,并且添加super.dispatchTouchEvent(event) ;那么现象和现象一是一样的。

public class MainActivity extends AppCompatActivity {

    private TouchView touchView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        touchView = (TouchView) findViewById(R.id.touch_view);
        touchView.setEnabled(false);  //touchView不可用


        touchView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                Log.e("TAG" , "onTouch -> "+event.getAction()) ;
                return false;
            }
        });
        touchView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e("TAG" , "onClick") ;
            }
        });

    }
}
public class TouchView extends View {
    public TouchView(Context context) {
        this(context, null);
    }

    public TouchView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public TouchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("TAG" , "onTouchEvent -> "+event.getAction()) ;
        return super.onTouchEvent(event);
    }


    /**
     *  复写 dispatchTouchEvent作用就是,在onTouchEvent()方法中可以返回任何值,return false、return true、return super.onTouchEvent(event)都是可以的
     *  但是在 dispatchTouchEvent必须返回true,并且添加super.dispatchTouchEvent(event) ,这样的话现象就是现象一
     * @param event
     * @return
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        super.dispatchTouchEvent(event) ;
        Log.e("TAG" , "dispatchTouchEvent -> "+event.getAction()) ;
        return true;
    }
}


3. View和Touch相关的有2个非常重要的方法

3.1>:dispatchTouchEvent()事件分发:

源码分析:
在View源码中的 dispatchTouchEvent()方法中:

    public boolean dispatchTouchEvent(MotionEvent event) {        

        // result:默认是false
        boolean result = false;

            // ListenerInfo:里边存放了 View的所有的 Listener信息
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }
        return result;
    }

以上中:
boolean result = false;
ListenerInfo 很重要,它里边存放了View的所有的Listener信息,比如:onTouchListener、onClickListener

ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null               
   && (mViewFlags & ENABLED_MASK) == ENABLED
   && li.mOnTouchListener.onTouch(this, event)) {
       result = true;
}
分析以上代码可知:
li为true,li.mOnTouchListener != null 为true
(mViewFlags & ENABLED_MASK) == ENABLED 是否是ENABLE可用的
li.mOnTouchListener.onTouch(this, event):

如果返回false,说明进不去if语句,那么就直接用初始化的result = false的值;
如果返回true,那么 result = true ;

if (!result && onTouchEvent(event)) {
      result = true;
}
分析以上代码可知:

对于if (!result && onTouchEvent(event)) :
如果上边的result为false,那么 !result为true,那么就会执行后边的 onTouchEvent()方法了;
如果 result为true,那么 !result为false,那么就不会执行后边的 onTouchEvent()方法了;

到目前来讲,我们都还没有看到点击事件?
在View的onTouchEvent中的 case MotionEvent.ACTION_UP里边调用了 performClick()方法,点击 performClick()进入里边方法后,具体代码是:

public boolean performClick() {
        final boolean result;
        final ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
        return result;
    }

分析:因为onTouchEvent返回true,即return true ,而没有return super.onTouchEvent(event),所以就不会执行View,更不会执行View中的onTouchEvent中的case MotionEvent.ACTION_UP,所以就只会执行 onTouchEvent()的Down、Move、Up事件,不会执行onClick()方法了;

3.2>:onTouchEvent(),这个方法一般都被我们复写,并且直接return true即可;

注意:

注意:自定义View是没有 onInterceptTouchEvent() 拦截方法的

4. 总结如下:


上一篇 下一篇

猜你喜欢

热点阅读