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以外,可以这么总结:

每个view收到事件,如果通过判断不决定阻断该事件,判断是否有一个子view要消费这个事件,如果没有,则执行onTouchEvent,即尝试自己消费,如果自己不消费,该view的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()中我就可以简单地实现多任务手势的业务需求啦。

@Override
	public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
		// TODO Auto-generated method stub
		if (mSet.get(arg2).size() > 1)
			this.downGranularity(arg2);
	}

	/**
	 * 自定义listview 用于事件分发的处理。
	 * @author ipip
	 *  2014年8月4日上午10:46:53
	 */
	private class MyListView extends ListView {
		public MyListView(Context context) {
			super(context);
			this.setDivider(null);
			// TODO Auto-generated constructor stub
		}

		/**
		 * 处理listView的触摸事件
		 */
		@Override
		public boolean onTouchEvent(MotionEvent ev) {
			switch (ev.getAction() & MotionEvent.ACTION_MASK) {
			case MotionEvent.ACTION_DOWN:
			case MotionEvent.ACTION_POINTER_DOWN:
				if (ev.getPointerCount() == 2)
					dst = measureFingers(ev);
				break;
			case MotionEvent.ACTION_UP:
			case MotionEvent.ACTION_POINTER_UP:

				if (ev.getPointerCount() == 2 && ndst < dst) {
					upGranularity();
					dst = -1;
				}
				break;

			case MotionEvent.ACTION_MOVE:
				if (ev.getPointerCount() >= 2) {
					ndst = measureFingers(ev);
				}
				break;
			}
			if (ev.getPointerCount() >= 2)
				return true;
			return super.onTouchEvent(ev);
		}
		/**
		 *
		 */
		@Override
		public boolean dispatchTouchEvent(MotionEvent ev) {
			if (ev.getPointerCount() >= 2) {
				return onTouchEvent(ev);
			}
			return super.dispatchTouchEvent(ev);
		}
	}

接下来是viewpager的代码:

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

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

public class MyViewPager extends ViewPager {

	public MyViewPager(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
	}

	public MyViewPager(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	private float xDown;// 记录手指按下时的横坐标。
	private float xMove;// 记录手指移动时的横坐标。
	private float yDown;// 记录手指按下时的纵坐标。
	private float yMove;// 记录手指移动时的纵坐标。
	private boolean viewPagerScrolling = false;
	private boolean fatherScrolling = false;

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		if (this.getChildCount() < 4)
			return false;
		return super.onTouchEvent(ev);
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		// TODO Auto-generated method stub
		if (ev.getPointerCount() >= 2)
			return false;

		switch (ev.getAction() & MotionEvent.ACTION_MASK) {
		case MotionEvent.ACTION_DOWN:
		case MotionEvent.ACTION_POINTER_DOWN:
			xDown = ev.getRawX();
			yDown = ev.getRawY();
			fatherScrolling = false;
			break;
		case MotionEvent.ACTION_MOVE:
			xMove = ev.getRawX();
			yMove = ev.getRawY();
			if (fatherScrolling) {
				return false;
			}
			if (viewPagerScrolling) {
				return super.dispatchTouchEvent(ev);
			}

			if (Math.abs(yMove - yDown) < 10 && Math.abs(xMove - xDown) > 3) {
				this.getParent().requestDisallowInterceptTouchEvent(true);
				viewPagerScrolling = true;
			} else if (Math.abs(yMove - yDown) >= 10) {
				fatherScrolling = true;
				return false;
			} else
				return false;
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_POINTER_UP:
			viewPagerScrolling = false;
			if (ev.getPointerCount() == 1)
				this.getParent().requestDisallowInterceptTouchEvent(false);
			break;
		}
		return super.dispatchTouchEvent(ev);
	}
}

上面的关键句:

			if (fatherScrolling) {
				return false;
			}
			if (viewPagerScrolling) {
				return super.dispatchTouchEvent(ev);
			}

			if (Math.abs(yMove - yDown) < 10 && Math.abs(xMove - xDown) > 3) {
				this.getParent().requestDisallowInterceptTouchEvent(true);
				viewPagerScrolling = true;
			} else if (Math.abs(yMove - yDown) >= 10) {
				fatherScrolling = true;
				return false;
			} else
				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都支持录屏了:

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

时间: 2024-08-27 02:09:58

android滑动组件嵌套一般思路,多任务手势思路,触摸传递思路,【例】listview嵌套viewpager的相关文章

Android学习之路——Android四大组件之activity(二)数据的传递

上一篇讲了activity的创建和启动,这一篇,我们来讲讲activity的数据传递 activity之间的数据传递,这里主要介绍的是activity之间简单数据的传递,直接用bundle传递基本数据类型的数据.另一种数据类型是parcelable和serialable 用bundle 传递数据有两种情况,这篇文章就分别从两个方面说明一下. 一.利用bundle传递基本数据类型 1.启动时传递数据,使用intent的put方法,将数据写入bundle中,然后startActivity(inten

从零开始学android&lt;SeekBar滑动组件.二十二.&gt;

拖动条可以由用户自己进行手工的调节,例如:当用户需要调整播放器音量或者是电影的播放进度时都会使用到拖动条,SeekBar类的定义结构如下所示: java.lang.Object ? android.view.View ? android.widget.ProgressBar ? android.widget.AbsSeekBar ? android.widget.SeekBar 常用方法 public SeekBar(Context context) 构造 创建SeekBar类的对象 publi

Android自定义组件系列【10】——随ViewPager滑动的导航条

昨天在用到ViewPager实现滑动导航的时候发现微信的导航条效果是跟随ViewPager的滑动而动的,刚开始想了一下,感觉可以使用动画实现,但是这个滑动是随手指时时变化的,貌似不可行,后来再网上搜了一下,找到一个开源代码,结果打开一看大吃一惊,这么简单的效果代码居然大概有300多行,太占手机存储空间了!后来自己干脆重写ViewGroup使用scrollTo方法实现了一下,具体实现过程如下: package com.example.slideupdownviewpage; import andr

android listView嵌套gridview的使用心得

在开发的过程中可能需要用到listview嵌套gridview的场景,但是在Android中,不能在一个拥有Scrollbar的组件中嵌入另一个拥有Scrollbar的组件,因为这不科学,会混淆滑动事件,导致只显示一到两行数据.那么就换一种思路,首先让子控件的内容全部显示出来,禁用了它的滚动.如果超过了父控件的范围则显示父控件的scrollbar滚动显示内容,思路是这样,一下是代码.具体的方法是自定义GridView组件,继承自GridView.重载onMeasure方法: public cla

Android中ListView嵌套GridView的简单消息流UI(解决宽高问题)

最近搞一个项目,需要用到类似于新浪微博的消息流,即每一项有文字.有九宫格图片,因此这就涉及到ListView或者ScrollView嵌套GridView的问题.其中GridView的高度问题在网上都很容易找到答案,即覆写onMeasure方法,然后设置高度的MeasureSpec.但是宽度问题确实没有什么资料,这里所说的宽度问题是比如GridView的列数为3,那么即使只有一张图片,gridview的宽度也是match_parent的,导致用户点击在图片范围外但是在gridview范围内时Lis

关于Android滑动冲突的解决方法(二)

之前的一遍学习笔记主要就Android滑动冲突中,在不同方向的滑动所造成冲突进行了了解,这样的冲突非常easy理解,当然也非常easy解决.今天,就同方向的滑动所造成的冲突进行一下了解,这里就先以垂直方向的滑动冲突为背景,这也是日常开发中最常见的一种情况. 这里先看一张效果图 由于GIF 图片大小的限制.截图效果不是非常好 上图是在购物软件上常见的上拉查看图文详情,关于这中动画效果的实现.事实上实现总体的效果,办法是有非常多的,网上有非常多相关的样例,可是对某些细节的处理不是非常清晰.比方,下拉

Android基础新手教程——3.8 Gestures(手势)

Android基础新手教程--3.8 Gesture(手势) 标签(空格分隔): Android基础新手教程 本节引言: 周六不歇息,刚剪完了个大平头回来.继续码字~ 好的,本节给大家带来点的是第三章的最后一节--Gesture(手势), 用过魅族手机的朋友相信对手势肯定是不陌生的.在home键两側像屏幕内滑动, 能够打开后台任务列表等等~在应用中通过手势来操作会大大提升用户体验. 比方Scroll手势在浏览器中个滚屏,Fling在浏览器中的换页等! 当然,有利也有弊,比方不当的手势操作引起AP

ViewPage嵌套ListView,嵌套Gallery 滑动冲突

首先项目需求是, 1.有左滑出现点击登录界面,用sliderMenu框架实现 2.新闻内容用ViewPage实现 3.在ViewPage中加载新闻片段Fragment 4.Fragment布局是一个带有上拉刷新,下拉下载的PullToRefresh ListView 5.ListView 里面有一个特殊第一项 拉取新闻广告位,用Gallery控件实现 问题:这么多滑动控件,在自定义Gallery控件,左右滑动,并未调用Gallery的OnFling()方法? 问题一:当Gallery第一个图,左

Android ListView嵌套ListView的实现方式

首先刚到北京一个月,产品经理让做一个类似于商城的东东,起初感觉没什么难度,(不就一个电子商务app嘛,以前也做过啊),但是当看到有需求是这样的 然后就开始做,起初太懒了,就在网上找,找到了一个ListView嵌套ListView的一哥们的讲解的大致思路的,然后根据那哥们的思路自己写了一个demo,感觉效果还挺好,不卡, 第一种实现方式:这种方式有个问题就像我项目中的问题,子列中的值如果是加减变化的,对应的每个父类的item的总价格会动态变化的话用此方式就会出现一定的问题,如果不需要实现像我项目图