scrollerView与侧滑菜单滑动冲突的问题(自定义view结合事件传递机制实例分析)

来源:互联网 发布:linux更改文件权限777 编辑:程序博客网 时间:2024/06/13 19:42

写在前面的话:

为了更深刻的理解Android事件传递机制,看了很多前辈博客,也写过一些小demo,觉得侧滑菜单嵌套scrollerView导致滑动冲突,可以形象的说明事件传递机制。

事件传递机制说明文字版:

1. 一个完整的touch事件,由一个down事件,多个move事件,一个up事件组成。

2.  touch事件的传递流程:activity --> window --> decorView --> viewGroup --> view.

3. 监听touch事件有两种方式:setonTouchListener和重写dispach,Intercept和touch三个方法。

4. setOnTouchListener: 该方法监听Touch事件,优先级高,如果return true,OnTouchEvent方法是接收不到Touch事件的。而    onClickListener  的onclick方法实际上是在onTouchEvent中调用的,所以点击事件不会生效,也表明触摸事件是要先于点击事件的。

5. dispatchTouchEvent: 该方法对事件进行分发。

6. onInterceptTouchEvent: 该方法表示对Touch事件进行拦截,viewGroup(容器)特有,而view没有(孩子已经是最后一层view了),如果reture true,表示拦截,父容器处理事件,return false 表示不拦截,让子类去分发处理这个事件。

7. onTouchEvent : 该方法表示对Touch事件进行消费,返回true表示自己来处理,返回false表示自己处理不了,重新返回父类容器去处理。



对于初学者来说,看完上面的文字说明,可能仍然会有疑惑:什么时候需要自己处理,什么时候需要拦截,或者,我连在一个界面中谁是viewgroup和谁是子view都不太明白,更别说事件传递机制了,

没有关系,我们用一个例子:侧滑菜单,手指向右滑动拉出菜单,向左滑动菜单收起。结合代码来一步一步展示。>首先在布局文件中,把左侧的菜单布局写出来。结构是:scrollerView里面包含一个孩子linearlayout, linearlayout包含多个孩子textview。







<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="180dp"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
<ScrollView 
   android:layout_width="180dp"
   android:layout_height="wrap_content"
   android:background="@drawable/menu_bg"
   > 
   <LinearLayout 
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:orientation="vertical"
       android:padding="5dp"
       >
       <TextView 
           android:layout_marginLeft="15dp"
           android:layout_marginTop="10dp"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:gravity="center_vertical"
           android:textColor="#ffffff"
           android:textSize="20sp"
           android:text="text"
           />
       
            <TextView 
           android:layout_marginLeft="15dp"
           android:layout_marginTop="20dp"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:gravity="center_vertical"
           android:textColor="#ffffff"
           android:textSize="20sp"
           android:text="text"
           />
            
           <TextView 
           android:layout_marginLeft="15dp"
           android:layout_marginTop="20dp"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:gravity="center_vertical"
           android:textColor="#ffffff"
           android:textSize="20sp"
           android:text="text"
           />
           
               <TextView 
           android:layout_marginLeft="15dp"
           android:layout_marginTop="20dp"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:gravity="center_vertical"
           android:textColor="#ffffff"
           android:textSize="20sp"
           android:text="text"
           />
                   <TextView 
           android:layout_marginLeft="15dp"
           android:layout_marginTop="20dp"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:gravity="center_vertical"
           android:textColor="#ffffff"
           android:textSize="20sp"
           android:text="text"
           />
                   
               <TextView 
           android:layout_marginLeft="15dp"
           android:layout_marginTop="20dp"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:gravity="center_vertical"
           android:textColor="#ffffff"
           android:textSize="20sp"
           android:text="text"
           />
               
         <TextView 
           android:layout_marginLeft="15dp"
           android:layout_marginTop="20dp"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:gravity="center_vertical"
           android:textColor="#ffffff"
           android:textSize="20sp"
           android:text="text"
           />
         
           <TextView 
           android:layout_marginLeft="15dp"
           android:layout_marginTop="20dp"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:gravity="center_vertical"
           android:textColor="#ffffff"
           android:textSize="20sp"
           android:text="text"
           />
   </LinearLayout>
   
  </ScrollView>
    
</LinearLayout>




再把右侧界面布局搭出来:
右侧布局很简单,中间显示一个textview:



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    <LinearLayout 
   android:layout_width="match_parent"
   android:layout_height="50dp"
   android:background="#00ff00"
   >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="侧滑菜单"
        />
</LinearLayout>
<TextView 
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:background="#33000000"
   android:text="text"
   android:gravity="center"
   />


</LinearLayout>


搭出侧滑和主界面两个界面后,将他们组合起来放到一个布局中:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"


    tools:context=".MainActivity" >


好,布局就算是搭起来了,现在要自定义一个myView的控件去继承viewGroup. 重写里面的 measure , layout,draw方法,对其进行布局。


完整代码如下:

package com.csdn.cehua;import android.content.Context;import android.graphics.Canvas;import android.provider.ContactsContract.CommonDataKinds.Event;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.Scroller;public class myView extends ViewGroup {private Scroller mScroller;private View mMainView;private View mMenuView;private boolean isOpen;public myView(Context context, AttributeSet attrs) {super(context, attrs);mScroller = new Scroller(context);}float downX,downY,moveX,moveY,upX,upY;@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_MOVE://触摸移动时调用的方法moveX=event.getX();System.out.println(moveX+"1111111");performMove(); break;case MotionEvent.ACTION_DOWN:downX=event.getX();                                                                                System.out.println(downX);break;case MotionEvent.ACTION_UP:upX=event.getX();performUp();break;default:break;}//return true表示消费,返回false则继续上传至父容器的ontouchevent去处理return true;}float dx,dy,ux,uy;@Override//写这个方法是因为scrollerView会抢占事件,当发生抢占事件的时候,全部交给父容器去处理//scrollerview和右侧主页均在一个view中,当发生事件,对于左侧和右侧不管有没有抢占事件的,都要交给//父容器处理,对于父容器来说,左右滑动时它的事件需求,上下滑动不是,//拦截方法是在viewgroup中特有的,在view中并不存在,返回true时,表示将事件拦截,该事件传给父容器viewgroup// ontouchevent 处理 ,如果false表示不拦截,传给子类的view去分发//public boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_MOVE:ux=ev.getX();uy=ev.getY();float mx=Math.abs(dx-ux);float my=Math.abs(dy-uy);if(mx>my){//return false;左右滑动左边时,无法将侧边栏收回,上下滑动正常return true; //左右滑动时,可以将侧边栏收回,上下滑动正常 }break;case MotionEvent.ACTION_DOWN:dx=ev.getX();dy=ev.getY();break;case MotionEvent.ACTION_UP:break;default:break;}return super.onInterceptTouchEvent(ev);}private void performUp() {int middleMenu=mMenuView.getWidth()/2;getScrollX();if(-getScrollX()>middleMenu){if(!isOpen){mOnslidelistener.open();}doOpen();}else{if(isOpen){mOnslidelistener.close();}doClose();}}private void doClose() {isOpen=false;int dx=-getScrollX();int duration;if(dx*10<1000){duration=dx*10;}else{duration=1000;}mScroller.startScroll(getScrollX(), 0, dx, 0,duration);invalidate();}private void doOpen() {isOpen=true;int dx=getScrollX()+mMenuView.getWidth();int duration;if(dx*10<1000){duration=dx*10;}else{duration=1000;}mScroller.startScroll(getScrollX(), 0, -dx, 0,duration);invalidate();}private void performMove() {int dx=Math.round(downX-moveX);int finalX=dx+getScrollX();System.out.println("getscrollerx"+getScrollX());if(finalX>0){scrollTo(0, 0);}else if(finalX<-mMenuView.getWidth()){scrollTo(-mMenuView.getWidth(), 0);}else{scrollBy(dx, 0);downX=moveX;invalidate();}}@Overridepublic void computeScroll() {// TODO Auto-generated method stubif(mScroller.computeScrollOffset()){int currX = mScroller.getCurrX();scrollTo(currX, 0);invalidate();}}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {measureChildren(widthMeasureSpec, heightMeasureSpec);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {mMainView = getChildAt(0);mMainView.layout(0, 0, mMainView.getMeasuredWidth(), mMainView.getMeasuredHeight());mMenuView = getChildAt(1);mMenuView.layout(-mMenuView.getMeasuredWidth(), 0, 0, mMenuView.getMeasuredHeight());}@Overrideprotected void onDraw(Canvas canvas) {// TODO Auto-generated method stubsuper.onDraw(canvas);}


下面先来分析这段代码做了什么事,了解谁是view谁是viewGroup,如何布局,复习一下自定义控件,再来引出view的事件传递机制:


1.  在布局文件中,我们看到myView这个自定义控件include了两个布局:一个main布局一个menu布局,所以myView必须继承viewGroup,首先执行onMeasure方法:将子类布局测量出来。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {measureChildren(widthMeasureSpec, heightMeasureSpec);super.onMeasure(widthMeasureSpec, heightMeasureSpec);}


2.  measure完之后执行layout方法,确定两个子view的位置,把menu菜单隐藏显示

protected void onLayout(boolean changed, int l, int t, int r, int b) {mMainView = getChildAt(0);mMainView.layout(0, 0, mMainView.getMeasuredWidth(), mMainView.getMeasuredHeight());mMenuView = getChildAt(1);mMenuView.layout(-mMenuView.getMeasuredWidth(), 0, 0, mMenuView.getMeasuredHeight());}

3. 没有需要画出特殊控件的需求,所以 执行draw方法只需要super调用父类的方法:

protected void onDraw(Canvas canvas) {// TODO Auto-generated method stubsuper.onDraw(canvas);}



经过前面三个步骤,我们就画出了一个自定义控件。接下来我们就需要对这个控件定义更多方法了。

首先确定一点:menu和main界面都是子view,包含这个两个控件就是viewGroup, 也就是说,在myView里面写的所有事件处理方法,都是站在父容器的立场的。明白这个,理解下面代码就好多了。



1. 首先重写ontouch事件,return true表示自己处理,不分发出去。

float downX,downY,moveX,moveY,upX,upY;@Overridepublic boolean onTouchEvent(MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_MOVE://触摸移动时调用的方法moveX=event.getX();System.out.println(moveX+"1111111");performMove(); break;case MotionEvent.ACTION_DOWN:downX=event.getX();                                                                                System.out.println(downX);break;case MotionEvent.ACTION_UP:upX=event.getX();performUp();break;default:break;}//return true表示消费,返回false则继续上传至父容器的ontouchevent去处理return true;}

2.  根据performMove(),和performUp() 去控件侧滑菜单,由于我们的重点是讲解事件传递机制,这里就贴出代码,不详细讲解:

private void performMove() {int dx=Math.round(downX-moveX);int finalX=dx+getScrollX();System.out.println("getscrollerx"+getScrollX());if(finalX>0){scrollTo(0, 0);}else if(finalX<-mMenuView.getWidth()){scrollTo(-mMenuView.getWidth(), 0);}else{scrollBy(dx, 0);downX=moveX;invalidate();}}

private void performUp() {int middleMenu=mMenuView.getWidth()/2;getScrollX();if(-getScrollX()>middleMenu){doOpen();}else{doClose();}}

3. doOpen()是执行打开菜单,doClose()执行关闭菜单。
private void doClose() {isOpen=false;int dx=-getScrollX();int duration;if(dx*10<1000){duration=dx*10;}else{duration=1000;}mScroller.startScroll(getScrollX(), 0, dx, 0,duration);invalidate();}private void doOpen() {isOpen=true;int dx=getScrollX()+mMenuView.getWidth();int duration;if(dx*10<1000){duration=dx*10;}else{duration=1000;}mScroller.startScroll(getScrollX(), 0, -dx, 0,duration);invalidate();}



做完以上步骤,运行代码,发现菜单完全滑出来的时候,左侧菜单由于包含一个scrollerView,所以上下滑动正常,但是在左侧菜单区域左右滑动时,菜单却无法收起。

这时候就可以体现事件传递机制的好处了:

 因为市场rollerView会抢占事件,表示它会从父容器拿走事件来自己处理。这时候需要在父容器里写一个拦截方法,就可以解决这个问题了:

public boolean onInterceptTouchEvent(MotionEvent ev) {switch (ev.getAction()) {case MotionEvent.ACTION_MOVE:ux=ev.getX();uy=ev.getY();float mx=Math.abs(dx-ux);float my=Math.abs(dy-uy);if(mx>my){//return false;左右滑动左边时,无法将侧边栏收回,上下滑动正常return true; //左右滑动时,可以将侧边栏收回,上下滑动正常 }break;case MotionEvent.ACTION_DOWN:dx=ev.getX();dy=ev.getY();break;case MotionEvent.ACTION_UP:break;default:break;}return super.onInterceptTouchEvent(ev);}

这样对事件传递机制是不是更清楚了呢?




  
1 0
原创粉丝点击