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);}
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);}
这样对事件传递机制是不是更清楚了呢?
- scrollerView与侧滑菜单滑动冲突的问题(自定义view结合事件传递机制实例分析)
- 自定义View(二)--表层浅析View的事件分发机制和滑动冲突
- Android View的事件分发机制与滑动冲突解决方案
- 解决viewpager与自定义view滑动冲突的问题
- View的事件传递机制-实例分析(2)
- scrollerView 和 slider 的滑动事件冲突的解决方案
- Android自定义View探索(三)—事件分发机制与滑动冲突处理
- 自定义View事件拦截机制(自定义viewGroup和外部法解决滑动冲突)
- Android View事件分发机制及View的滑动冲突
- Android 笔记 ViewPager的滑动与子view内部滑动事件的冲突问题
- Android View的事件分发机制和滑动冲突解决方案
- Android View的事件分发机制和滑动冲突解决方案
- Android View的事件分发机制和滑动冲突解决方案
- Android View的事件分发机制和滑动冲突解决
- View事件传递与绘制机制,自定义View实现理解
- 用ViewDragHelper自定义侧滑菜单——浅析源码解决与ScrollView的滑动冲突
- 【Android View事件分发机制】滑动冲突
- View的事件体系(下)(事件分发,滑动冲突)
- Linux ftp 远程文件传输
- Same Tree (JAVA实现)
- HDU1159poj1458Common Subsequence最长公共子序列
- Markdown 语法说明 (简体中文版)
- Parajumpers Jacka read to born for you at
- scrollerView与侧滑菜单滑动冲突的问题(自定义view结合事件传递机制实例分析)
- Oracle 常用函数实例总结
- JFreeChart的使用
- linux下tar.gz、tar、bz2、zip等解压缩、压缩命令小结
- 对比 Matlab 和 Octave 的运行速度
- altium designer 去掉PCB的所有连线
- 迷宫寻路
- 利用header下载图片等文件
- Mybatis学习总结五