Android嵌套滑动组件相关的文章资料

来源:互联网 发布:微信交友源码 编辑:程序博客网 时间:2024/06/06 01:16

原博客:android滑动组件嵌套一般思路,多任务手势思路,触摸传递思路,【例】listview嵌套viewpager

在android UI开发中,我们经常会遇到这种需求:

两个支持滑动的组件,比如listview嵌套多个listview,listview的item是一个viewpager或gallary?亦或是scrollview嵌套scrollview等等。

一般情况下,你还可能需要支持如下几种功能:

¤ 两层组件都可以滑动

¤ 不让两个组件同时滑动,或者让两个组件同时滑动并可以自己调节

¤ 不影响底层view的子view和嵌套view的子view的点击事件


实现上述功能时,我们也经常遇到一些问题:

¤ 点击事件被屏蔽

¤ view的滑动不流畅(这里不讨论)

¤ view的滑动条件逻辑不符合设计

¤ 不知道怎么重写函数满足逻辑


这里笔者介绍一下解决这类需求的一般思路,以及一个支持多任务手势的listview和viewpager的需求案例。


触摸传递思路:

如果说,需求要把两个可以滑动的view嵌套在一起,那么要注意一些问题

¤ 像listview和scrollview,viewpager等可以滑动的组件,都是有自己的滑动规则的,我们最好不去重写怎么滑动它们(即最好不要去监听触摸的坐标用代码去滑动它们)。我们只要把我们需要的非滑动业务写好就可以了,当然我们也不能阻断默认滑动规则的执行


¤ viewgroup、view的事件分发传递机制需要特别清楚,你要知道,listview继承自viewgroup,当一串触摸事件发生时,当前activity收到这个事件,dispatch给顶层viewgroup,顶层viewgroup先是调用dispatchTouchEvent,该函数内部先在onInterceptTouchEvent函数决定是否要截断,如果选择截断,执行自己的onTouchEvent,子view不会接受到这个TouchEvent;如果不截断,该viewGroup会分发给所有点击范围内的子view(如果你不想分发给点击范围内的子view,你需要重写更多dispatchEvent的部分),即调用子view的dispatchTouchEvent,只要子view中有一个返回true(代表子View消费了这个事件),则该viewGroup不会执行这个TouchEvent,如果没有返回true,则调用该viewGroup的OnTouchEvent(),如果它返回false,说明没有消费掉这个事件,接着调用onClick,如果还是没有消费,则该viewgroup的dispatch函数会返回false。

这一套逻辑可能会很复杂,除却view没有onInterceptTouch以外,可以这么总结:

每个viewGroup收到事件,如果通过判断不决定阻断该事件,判断是否有一个子view要消费这个事件,如果没有,则执行onTouchEvent,即尝试自己消费,如果自己不消费,该viewGroup的dispatch就返回了false,表示该view包括该view的分支下没有消费该事件。


¤ 要非常明确业务需求,因为它要明确地写成代码形式,还要写在正确的地方(有时你可能会犹豫为了执行父view的一个多任务手势,应该在父view截断还是子view的dispatch返回一个false)

¤ 子view垄断父view事件:this.getParent().requestDisallowInterceptTouchEvent(true);该方法禁止父view阻断事件,即一定可以接受到事件,记得完成一套触摸时关闭这个。


多任务手势思路:

相信对读者来说,设计一个view的多任务手势并不是非常困难(重写onTouchEvent,记录按下、移动、抬起的坐标做一些相应的运算),但是这个问题放到viewq嵌套上,你可以考量了。你可能会遇到这样一些问题

¤ 如果子view消费了touchEvent,父view的任何行为不会被调用。

解决:

1.如果你希望父子view同时消费这个事件,你需要重写父view的dispatch并强行调用onTouchEvent。

2.如果这种情形,你不希望子view消费这个事件,有两个方案:重写父view的intercept,检查手势,阻断这个事件;重写子view的dispatch,检查手势,返回false。一个是父view强制阻断,一个是子view强制不消费。

3.如果你当前状态还并不明确应该由哪个view来消费这个事件,你大可以放任不管,直到判断出需要阻断或者消费(因为你业务需求对这种状态没有明确定义,就不需要去定义怎么处理了)。


一个简单的案例,附部分源码:

需求描述:

listview嵌套viewpager,listview的每一个item由xml布局定义,布局中包括一个viewpager和其他部分。

支持的操作:

¤ viewpager可以左右滑动,listview可以上下滑动,不会同时在滑动中,自然地,只有一开始点到的那个viewpager可以滑动,不会滑动其他viewpager。

¤ listview支持双指缩小操作。

¤ listview支持itemClick操作(不点击到viewpager中的图片)。

¤ viewpager中的图片支持点击操作。


先是listview的部分:

onItemClick定义了 我自己的业务事件,当点击每一个item并且没有被viewpager的图片view消费时触发。

在dispatchTouchEvent()中判断如果当前手指数大于等于2,即双指操作时,强行调用onTouchEvent,并返回,此时不会调用super.dispatchTouchEvent(),即事件不会走到子view里面去。

在onTouchEvent()中我就可以简单地实现多任务手势的业务需求啦。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Override  
  2.     public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {  
  3.         // TODO Auto-generated method stub  
  4.         if (mSet.get(arg2).size() > 1)  
  5.             this.downGranularity(arg2);  
  6.     }  
  7.   
  8.     /** 
  9.      * 自定义listview 用于事件分发的处理。 
  10.      * @author ipip 
  11.      *  2014年8月4日上午10:46:53 
  12.      */  
  13.     private class MyListView extends ListView {  
  14.         public MyListView(Context context) {  
  15.             super(context);  
  16.             this.setDivider(null);  
  17.             // TODO Auto-generated constructor stub  
  18.         }  
  19.   
  20.         /** 
  21.          * 处理listView的触摸事件 
  22.          */  
  23.         @Override  
  24.         public boolean onTouchEvent(MotionEvent ev) {  
  25.             switch (ev.getAction() & MotionEvent.ACTION_MASK) {  
  26.             case MotionEvent.ACTION_DOWN:  
  27.             case MotionEvent.ACTION_POINTER_DOWN:  
  28.                 if (ev.getPointerCount() == 2)  
  29.                     dst = measureFingers(ev);  
  30.                 break;  
  31.             case MotionEvent.ACTION_UP:  
  32.             case MotionEvent.ACTION_POINTER_UP:  
  33.   
  34.                 if (ev.getPointerCount() == 2 && ndst < dst) {  
  35.                     upGranularity();  
  36.                     dst = -1;  
  37.                 }  
  38.                 break;  
  39.   
  40.             case MotionEvent.ACTION_MOVE:  
  41.                 if (ev.getPointerCount() >= 2) {  
  42.                     ndst = measureFingers(ev);  
  43.                 }  
  44.                 break;  
  45.             }  
  46.             if (ev.getPointerCount() >= 2)  
  47.                 return true;  
  48.             return super.onTouchEvent(ev);  
  49.         }  
  50.         /** 
  51.          *  
  52.          */  
  53.         @Override  
  54.         public boolean dispatchTouchEvent(MotionEvent ev) {  
  55.             if (ev.getPointerCount() >= 2) {  
  56.                 return onTouchEvent(ev);  
  57.             }  
  58.             return super.dispatchTouchEvent(ev);  
  59.         }  
  60.     }  

接下来是viewpager的代码:

先解释一下我的onTouchEvent(),由于我的viewpager一行可以容纳4张图片(在适配器中重写getPageWidth()),所以当图片数小于4时,我不处理滑动事件,否则会出现图片瞬移闪烁的现象(其实这个挺有趣的,还不清楚什么原理)。

在dispatch中,首先判断是否双指操作。接着是记录第一次操作的位置以及每次移动时的判断。

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public class MyViewPager extends ViewPager {  
  2.   
  3.     public MyViewPager(Context context) {  
  4.         super(context);  
  5.         // TODO Auto-generated constructor stub  
  6.     }  
  7.   
  8.     public MyViewPager(Context context, AttributeSet attrs) {  
  9.         super(context, attrs);  
  10.     }  
  11.   
  12.     private float xDown;// 记录手指按下时的横坐标。  
  13.     private float xMove;// 记录手指移动时的横坐标。  
  14.     private float yDown;// 记录手指按下时的纵坐标。  
  15.     private float yMove;// 记录手指移动时的纵坐标。  
  16.     private boolean viewPagerScrolling = false;  
  17.     private boolean fatherScrolling = false;  
  18.   
  19.     @Override  
  20.     public boolean onTouchEvent(MotionEvent ev) {  
  21.         if (this.getChildCount() < 4)  
  22.             return false;  
  23.         return super.onTouchEvent(ev);  
  24.     }  
  25.   
  26.     @Override  
  27.     public boolean dispatchTouchEvent(MotionEvent ev) {  
  28.         // TODO Auto-generated method stub  
  29.         if (ev.getPointerCount() >= 2)  
  30.             return false;  
  31.   
  32.         switch (ev.getAction() & MotionEvent.ACTION_MASK) {  
  33.         case MotionEvent.ACTION_DOWN:  
  34.         case MotionEvent.ACTION_POINTER_DOWN:  
  35.             xDown = ev.getRawX();  
  36.             yDown = ev.getRawY();  
  37.             fatherScrolling = false;  
  38.             break;  
  39.         case MotionEvent.ACTION_MOVE:  
  40.             xMove = ev.getRawX();  
  41.             yMove = ev.getRawY();  
  42.             if (fatherScrolling) {  
  43.                 return false;  
  44.             }  
  45.             if (viewPagerScrolling) {  
  46.                 return super.dispatchTouchEvent(ev);  
  47.             }  
  48.   
  49.             if (Math.abs(yMove - yDown) < 10 && Math.abs(xMove - xDown) > 3) {  
  50.                 this.getParent().requestDisallowInterceptTouchEvent(true);  
  51.                 viewPagerScrolling = true;  
  52.             } else if (Math.abs(yMove - yDown) >= 10) {  
  53.                 fatherScrolling = true;  
  54.                 return false;  
  55.             } else  
  56.                 return false;  
  57.             break;  
  58.         case MotionEvent.ACTION_UP:  
  59.         case MotionEvent.ACTION_POINTER_UP:  
  60.             viewPagerScrolling = false;  
  61.             if (ev.getPointerCount() == 1)  
  62.                 this.getParent().requestDisallowInterceptTouchEvent(false);  
  63.             break;  
  64.         }  
  65.         return super.dispatchTouchEvent(ev);  
  66.     }  
  67. }  

上面的关键句:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (fatherScrolling) {  
  2.     return false;  
  3. }  
  4. if (viewPagerScrolling) {  
  5.     return super.dispatchTouchEvent(ev);  
  6. }  
  7.   
  8. if (Math.abs(yMove - yDown) < 10 && Math.abs(xMove - xDown) > 3) {  
  9.     this.getParent().requestDisallowInterceptTouchEvent(true);  
  10.     viewPagerScrolling = true;  
  11. else if (Math.abs(yMove - yDown) >= 10) {  
  12.     fatherScrolling = true;  
  13.     return false;  
  14. else  
  15.     return false;  

如果在该点有明显的横向移动,并且竖直方向一定在给定数值内,我判定我要执行viewpager的横向滑动操作,此时,我赋值viewPagerScrolling为true,那么接下来的所有move都会默认调用super.dispatchTouchEvent(),并且拒绝父view的阻断,即会应用默认的滑动方式直到一串事件的结束。如果竖直方向移动超过范围,并且之前横向移动不明显,那么我判定父view的listview要滑动,此时赋值fatherScrolling为ture,那么之后每次move都会返回false,即不消费之后所有事件。

当然了,在所有手指抬起后,这些状态被重置了。

之后,viewpager的每一个imageView都可以简单地设置一个onClickListener(),父view和爷view不会阻断它的点击事件。

简单叙述一下为什么:
¤ itemClick和imageView的click为什么没有被阻断?

前提是祖辈view们没有阻断它的事件,即祖辈view不会消费down-up,不会消费down-move-up。简单地说就是,像按下立即抬起这样的click的事件,两层view都不会将它消费掉,可以以默认方式顺利地传给子view。

¤ 为什么要在viewpager中判断是否有一定的横向移动?

如果直接让viewpager消费这个事件,父view便没有机会消费该事件了,我只有以一定的移动为基础,才能判断到底是竖着滑还是横着滑。

你会说我可以先让子view消费,当竖直方向移动太多就转交给listview消费。那么其实我还是要判断横向移动的距离,如果横向移动了100px,然后因为竖直移动了15px就不让viewpager消费跟业务需求不同,既然同样要计算横向距离,最好的方法应该就是先等待,时机成熟时锁定消费该事件的view直到事件链结束。

¤ 为什么在listview在dispatch中判断是否双指而不是在intercept?

呀,其实都可以的。。。

以下是效果图,用新版adt的Screen Recording录屏,好像从kitkat开始的adt都支持录屏了:


---------------------------------------------------------------------

2014/8/6,修改补充:

原文中的关键句代码有所修改:

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. if (fatherScrolling) {  
  2.                 return false;  
  3.             }  
  4.             if (viewPagerScrolling) {  
  5.                 return super.dispatchTouchEvent(ev);  
  6.             }  
  7.             float dx = Math.abs(xMove - xDown), dy = Math.abs(yMove - yDown);  
  8.             if (dx > 3 && dx > dy && this.getChildCount() >= 4) {  
  9.                 this.getParent().requestDisallowInterceptTouchEvent(true);  
  10.                 viewPagerScrolling = true;  
  11.             } else if (dy > 3 && dy > dx) {  
  12.                 fatherScrolling = true;  
  13.                 return false;  
  14.             } else  
  15.                 return false;  
在经过大量尝试和Log观察后,发现原有的逻辑很可能影响我们的用户体验。

原来的方法有可能造成不响应滑动的情况。

新的方法,当我们检测到有一个方向的偏移大于3,且大于另一者偏移,就确定要竖直滑动还是横向滑动了,完全取决用户在两个方向的偏移,即更倾向于哪种滑动方式。


====================================================================================

近来发觉如果搞不定Android View和ViewGroup的绘制原理,及事件传递机制很难说你彻底理解和掌握了Android App 开发。也就很难驾驭自定义View...

2 0
原创粉丝点击