Android自定义控件系列三:自定义开关按钮(二)

原创 2014年11月04日 09:00:04

接上一篇自定义开关按钮(一)的内容继续。上一次实现了一个开关按钮的基本功能,即自定义了一个控件,开关按钮,实现了点击切换开关状态的功能。今天我们想在此基础之上,进一步实现触摸拖拽开关滑块来实现开关的功能,还是一样先来看看效果,这里由于要显示拖拽,我打开了开发者选项中的显示触摸操作,会在屏幕上显示一个圆圈表示触摸位置:



在这里,我们的主要工作就是在原有代码的基础上,增加一个重写的onTouchEvent方法,刚添加上来的时候是这个样子的:


@Override
	public boolean onTouchEvent(MotionEvent event) {
		return super.onTouchEvent(event);
	}


对于触摸事件来说,一般返回值为true的话,那么就代表在这里消费掉本次触摸,而返回false的话,就在当前位置对本次触摸不做处理或者不能完全处理,还需要继续将本次事件分发给后续view或者viewgroup响应,对于这里,我们定义的开关按钮已经是子view了,所以这里返回true就可以,现在看起来我们直接将return super.onTouchEvent(event);这一句删掉,然后加上return true;就可以了,会不会产生什么问题呢?我们暂时先这样处理。

之后的工作就比较简单了,我们需要根据event.getAction()的类型来做相应的处理,即我们基本上每天都在使用的:MotionEvent.ACTION_DOWN、MotionEvent.ACTION_MOVE和MotionEvent.ACTION_UP


由于在ACTION_MOVE的时候,我们想让滑块随着我们手指的位置的移动而移动,这里由于只是水平移动,所以我们只需要记录x方向的位置,需要两个值:int firstXint secondXfirstX负责记录上一次ACTION_MOVE时候的x值,secondX负责记录本次ACTION_MOVE时候的x值,然后这两个值相减,则可以得到手指在两次ACTION_MOVE触发的时间里移动的距离,得到这个距离之后还需要将firstX调整为当前的值,以便下次使用。然后将这个距离差值与我们滑块的位置进行求和,这样就可以调整我们滑块的位置随手指移动了,当然滑块的位置是有一个范围的,这里应该是[0,MAX_LEFT_DISTANCE]MAX_LEFT_DISTANCE = backgroundBitmap.getWidth()- slideButton.getWidth();,所以我们需要做一个判断,来限制滑块,不让其划出边界,好了,基本逻辑到这里,看看代码:


@Override
	public boolean onTouchEvent(MotionEvent event) {

		switch (event.getAction()) {

		case MotionEvent.ACTION_DOWN:
			// 当按下的时候
			firstX = secondX = (int) event.getX();
			break;
		case MotionEvent.ACTION_MOVE:
			// 当移动的时候
			// 计算手指在屏幕上移动的距离
			int disX = (int) (event.getX() - secondX);
			secondX = (int) event.getX();

			// 将本次的位置,设置给lastX
			slideBtn_left = slideBtn_left + disX;

			// 做一个判断,防止滑块划出边界,滑块的范围应该是在[0,MAX_LEFT_DISTANCE];
			if (slideBtn_left < 0) {
				slideBtn_left = 0;
			} else {
				if (slideBtn_left > MAX_LEFT_DISTANCE) {
					slideBtn_left = MAX_LEFT_DISTANCE;
				}
			}

			break;
		case MotionEvent.ACTION_UP:
			// 当抬起的时候

			// 抬起的时候,判断松开的位置是哪里,来由此来决定开关的状态是打开还是关闭
			if (slideBtn_left < MAX_LEFT_DISTANCE / 2) {
				currentState = false;
			} else if (slideBtn_left >= MAX_LEFT_DISTANCE / 2) {
				currentState = true;
			}

			// 由开关的状态标志,确定应该是打开还是关闭状态
			flushState();
			break;
		}

		// 根据状态标志来刷新对应的滑块停止位置,从而实现打开或者关闭效果
		flushView();

		// 返回true意味着消费掉本次事件,不让其他控件还可以接收到这个事件
		return true;
	}



写成现在这样会导致只有拖动效果,而点击效果不起作用了。。。


问题在哪里呢,实际上对于一个view来说,当点击事件传入的时候是先会调用onTouchEvent方法,然后才是调用onClickonLongClick等方法;但是我们也没有看到这个方法里面能够如何调用onClick方法啊?其实,秘密就在我们之前删掉的return super.onTouchEvent(event);这一句里面。


我们不妨进入到super.onTouchEvent(event);里面一探究竟,找到switch(event.getAction()){}这一块核心代码:


 if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
		  case MotionEvent.ACTION_UP:
			...
			 if (!post(mPerformClick)) {
                                    performClick();
                                }
			...
		break;
		case MotionEvent.ACTION_DOWN:
			...
		 	else {
                        	// Not inside a scrolling container, so show the feedback right away
                        	setPressed(true);
                        	checkForLongClick(0);
                    	}
			...
		break;
		...
	}
	...
}


而在performClick()之中我们可以发现:

public boolean performClick() {
	...
	li.mOnClickListener.onClick(this);
	...
}


到这里基本上就清楚了,onClickonLongClick是在super.onTouchEvent方法里被调用的,onClick是在ACTION_UP的时候可能被调用,而onLongClick是在ACTION_DOWN的时候可能被调用。所以在这里我们虽然去掉了 return super.onTouchEvent(event);这一句,但是super.onTouchEvent(event);是需要保留的。我们可以将它放到onTouchEvent方法的第一句。


做完这一步之后我们会发现又可以响应到onClick方法了,但是还是有些不完美;我们希望在拖动之后就不要有点击操作,点击也不要有拖动效果(点击当然不会有拖动效果...),由于onClick点击的判断只是单纯的检测ACTION_DOWN之后是否有一个ACTION_UP,如果有,那么就判断为一次点击事件,至于中间的过程是否滑动了,它却不管。所以在这里我们需要再加上一个boolean类型的判断标志isDrag,在ACTION_DOWN的时候将其设置为false,如果触发了ACTION_MOVE了,则将其置为true。然后在onClick方法里面,加上一个条件,如果isDrag为假才执行点击的对应操作,否则就跳过。这样就比较完美了。。。来看看最终的onTouchEventonClick的写法:

@Override
	public boolean onTouchEvent(MotionEvent event) {


		super.onTouchEvent(event);// 这一句不能少,否则无法触发onclick事件


		switch (event.getAction()) {


		case MotionEvent.ACTION_DOWN:
			// 当按下的时候
			isDrag = false;
			firstX = secondX = (int) event.getX();
			break;
		case MotionEvent.ACTION_MOVE:
			// 当移动的时候
			isDrag = true;
			// 计算手指在屏幕上移动的距离
			int disX = (int) (event.getX() - secondX);
			secondX = (int) event.getX();


			// 更新slideBtn_left的大小
			slideBtn_left = slideBtn_left + disX;


			// 做一个判断,防止滑块划出边界,滑块的范围应该是在[0,MAX_LEFT_DISTANCE];
			if (slideBtn_left < 0) {
				slideBtn_left = 0;
			} else {
				if (slideBtn_left > MAX_LEFT_DISTANCE) {
					slideBtn_left = MAX_LEFT_DISTANCE;
				}
			}


			break;
		case MotionEvent.ACTION_UP:
			// 当抬起的时候


			// 抬起的时候,判断松开的位置是哪里,来由此来决定开关的状态是打开还是关闭
			if (slideBtn_left < MAX_LEFT_DISTANCE / 2) {
				currentState = false;
			} else if (slideBtn_left >= MAX_LEFT_DISTANCE / 2) {
				currentState = true;
			}


			// 由开关的状态标志,确定应该是打开还是关闭状态
			flushState();
			break;
		}


		// 根据状态标志来刷新对应的滑块停止位置,从而实现打开或者关闭效果
		flushView();


		// 返回true意味着消费掉本次事件,不让其他控件还可以接收到这个事件
		return true;
	}



@Override
	public void onClick(View v) {
		if (!isDrag) {
			// 如果不是拖动事件,才进行点击事件的响应操作
			currentState = !currentState;
			flushState();
			flushView();
		} else {
			// 如果是拖动事件,则不进行点击事件的响应操作
		}
	}



至此,关于自定义开关的触摸事件就基本完成了,其实关于触摸点击的机制,Android是有一整套的流程的。。找时间会写一篇关于此的博文吧。下一篇会讲解如何自己定义控件的自定义属性,谢谢关注!






Android自定义控件系列二:自定义开关按钮(一)

这一次我们将会实现一个完整纯粹的自定义控件,而不是像之前的组合控件一样,拿系统的控件来实现;计划分为三部分:自定义控件的基本部分,自定义控件的触摸事件的处理和自定义控件的自定义属性;敬请关注。...
  • cyp331203
  • cyp331203
  • 2014年11月03日 10:55
  • 6001

仿IOS开关button 自定义ios开关控件

我是安卓一年新人,好像最近有那种突然入门了的感觉,觉得什么都可以做,什么都能做,什么都能做出来,做好,朋友说这是第一个门槛,我猜可能真的是,但是不管怎么样,这种感觉和手感一定要保留下来,所以漫无天日的...
  • hyf97135
  • hyf97135
  • 2016年10月22日 17:35
  • 672

Android 自定义实现switch开关按钮

前几天在看蘑菇街上有个开关按钮: 就在想是怎样实现的,于是反编译了它的源码,但是这时得到了下面的几张图片: 那我们就用这几张图片来实现类似的效果吧。 代码: SwitchButton类: pa...
  • swust_chenpeng
  • swust_chenpeng
  • 2014年02月26日 10:47
  • 83769

android 三档开关做法

项目中要实现一个如下的开关按钮,可以滑动到左,中,右的三档开关,然后如果滑动到超过25%,则自动滑到第二个档位,如果滑动到超过75%,则自动滑到第三个档位。 符合这种需求的,我想到了用androi...
  • lb1207087645
  • lb1207087645
  • 2017年11月12日 19:30
  • 86

Android自定义控件之实现滑动选择开关

前言:今天我们仿照着Google给我们提供的Switch控件来进行一次模仿,自己动手打造一个可以换滑动图片以及背景的图片。...
  • qq_32306361
  • qq_32306361
  • 2017年05月30日 22:59
  • 470

自定义view_开关按钮

注:源自传智播客视频教程 现整理此案例,以供日后自己或大家学习、参考: android自定义view实现的简单demo 实现效果: 1.点击按钮可以改变开关的状态 2.拖动按钮可以改变...
  • wang725
  • wang725
  • 2015年05月24日 23:46
  • 624

【Android】自定义控件实现可滑动的开关(switch)

~转载请注明来源:http://blog.csdn.net/u013015161/article/details/46704745介绍最近项目里有要用到滑动开关, 即SlideSwitch,就自己写了...
  • u013015161
  • u013015161
  • 2015年07月01日 01:46
  • 3976

Qt之自定义控件(开关按钮)

简述接触过IOS系统的童鞋们应该对开关按钮很熟悉了,它的切换以及滑动比较帅气。通常说的开关按钮,有两个状态:on、off。下面,我们利用自定义控件来实现一个开关按钮。简述 原理 源码 示例 效果 源码...
  • u011012932
  • u011012932
  • 2016年08月09日 17:03
  • 10594

Android自定义控件系列三:自定义开关按钮(二)

转自:http://blog.csdn.net/cyp331203/article/details/45198549 接上一篇自定义开关按钮(一)的内容继续。上一次实现了一个开关按钮的基本功...
  • qq_20816947
  • qq_20816947
  • 2015年04月30日 13:23
  • 239

Android自定义控件系列三:自定义开关按钮(二)

接上一篇自定义开关按钮(一)的内容继续。上一次实现了一个开关按钮的基本功能,即自定义了一个控件,开关按钮,实现了点击切换开关状态的功能。今天我们想在此基础之上,进一步实现触摸拖拽开关滑块来实现开关的功...
  • bcanyang
  • bcanyang
  • 2015年04月23日 11:00
  • 238
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Android自定义控件系列三:自定义开关按钮(二)
举报原因:
原因补充:

(最多只允许输入30个字)