android自定义控件(2)-拖拽实现开关切换
在这里,我们的主要工作就是在原有代码的基础上,增加一个重写的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 firstX和int secondX,firstX负责记录上一次ACTION_MOVE时候的x值,secondX负责记录本次ACTION_MOVE时候的x值,然后这两个值相减,则可以得到手指在两次ACTION_MOVE触发的时间里移动的距离,得到这个距离之后还需要将firstX调整为当前的值,以便下次使用。然后将这个距离差值与我们滑块的位置进行求和,这样就可以调整我们滑块的位置随手指移动了,当然滑块的位置是有一个范围的,这里应该是[0,MAX_LEFT_DISTANCE],MAX_LEFT_DISTANCE = backgroundBitmap.getWidth()- slideButton.getWidth();,所以我们需要做一个判断,来限制滑块,不让其划出边界,好了,基本逻辑到这里,看看代码:
/**
* 获取屏幕点击事件
* 类似于ios中touchBegan方法
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
/**
* 处理触摸手势
* recognizer.state
* UIGestureRecognizerStateBegan
* UIGestureRecognizerStateChanged
* UIGestureRecognizerStateEnd
*/
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
/**
* 获取触摸点的x坐标
* 类似于ios中的CGPoint point = [recognizer locationInView:recogznier.view];
*/
firstTouchX = secondTouchX = (int)event.getX();
isDrag = false;
Log.i("test", "ACTION_DOWN..........");
break;
case MotionEvent.ACTION_MOVE:
// 当移动的时候,计算手指在屏幕上移动的距离
int distanceX = (int) (event.getX() - secondTouchX);
if (Math.abs(distanceX) > 5) {
isDrag = true;
}
secondTouchX = (int) event.getX();
// 将滑动的距离累加到滑动按钮的X值
switchBtnX = switchBtnX+distanceX;
// 做一个判断,防止滑块超出边界,滑块的范围应该在[0,MAX_LEFT_DISTANCE]
if (switchBtnX < 0) {
switchBtnX = 0;
} else {
if (switchBtnX > switchBtnMaxSlideDistance) { // 如果按钮的X坐标超过了按钮可以移动的最大距离
switchBtnX = switchBtnMaxSlideDistance;
}
}
Log.i("test", "ACTION_MOVE..........");
break;
case MotionEvent.ACTION_UP:
// 当抬起的时候,判断松开的位置是哪里,由此决定开关的的状态
if (switchBtnX < switchBtnMaxSlideDistance / 2) {
switchBtnX = 0;
} else if (switchBtnX >= switchBtnMaxSlideDistance /2 ) {
switchBtnX = switchBtnMaxSlideDistance;
}
break;
default:
break;
}
invalidate(); // 刷新当前状态,类似于ios中的setNeedDisplay方法
return true;
}
写成现在这样会导致只有拖动效果,而点击效果不起作用了。。。
问题在哪里呢,实际上对于一个view来说,当点击事件传入的时候是先会调用onTouchEvent方法,然后才是调用onClick和onLongClick等方法;但是我们也没有看到这个方法里面能够如何调用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);
...
}
到这里基本上就清楚了,onClick和onLongClick是在super.onTouchEvent方法里被调用的,onClick是在ACTION_UP的时候可能被调用,而onLongClick是在ACTION_DOWN的时候可能被调用。所以在这里我们虽然去掉了 return super.onTouchEvent(event);这一句,但是super.onTouchEvent(event);是需要保留的。
做完这一步之后我们会发现又可以响应到onClick方法了,但是还是有些不完美;我们希望在拖动之后就不要有点击操作,点击也不要有拖动效果(点击当然不会有拖动效果...),由于onClick点击的判断只是单纯的检测ACTION_DOWN之后是否有一个ACTION_UP,如果有,那么就判断为一次点击事件,至于中间的过程是否滑动了,它却不管。所以在这里我们需要再加上一个boolean类型的判断标志isDrag,在ACTION_DOWN的时候将其设置为false,如果触发了ACTION_MOVE了,则将其置为true。然后在onClick方法里面,加上一个条件,如果isDrag为假才执行点击的对应操作,否则就跳过。这样就比较完美了。。。来看看最终的onTouchEvent和onClick的写法:
private boolean isDrag = false;
/**
* 获取屏幕点击事件
* 类似于ios中touchBegan方法
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
/**
* 处理触摸手势
* recognizer.state
* UIGestureRecognizerStateBegan
* UIGestureRecognizerStateChanged
* UIGestureRecognizerStateEnd
*/
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
/**
* 获取触摸点的x坐标
* 类似于ios中的CGPoint point = [recognizer locationInView:recogznier.view];
*/
firstTouchX = secondTouchX = (int)event.getX();
isDrag = false;
Log.i("test", "ACTION_DOWN..........");
break;
case MotionEvent.ACTION_MOVE:
// 当移动的时候,计算手指在屏幕上移动的距离
int distanceX = (int) (event.getX() - secondTouchX);
if (Math.abs(distanceX) > 5) {
isDrag = true;
}
secondTouchX = (int) event.getX();
// 将滑动的距离累加到滑动按钮的X值
switchBtnX = switchBtnX+distanceX;
// 做一个判断,防止滑块超出边界,滑块的范围应该在[0,MAX_LEFT_DISTANCE]
if (switchBtnX < 0) {
switchBtnX = 0;
} else {
if (switchBtnX > switchBtnMaxSlideDistance) { // 如果按钮的X坐标超过了按钮可以移动的最大距离
switchBtnX = switchBtnMaxSlideDistance;
}
}
Log.i("test", "ACTION_MOVE..........");
break;
case MotionEvent.ACTION_UP:
// 当抬起的时候,判断松开的位置是哪里,由此决定开关的的状态
if (switchBtnX < switchBtnMaxSlideDistance / 2) {
switchBtnX = 0;
} else if (switchBtnX >= switchBtnMaxSlideDistance /2 ) {
switchBtnX = switchBtnMaxSlideDistance;
}
break;
default:
break;
}
invalidate(); // 刷新当前状态,类似于ios中的setNeedDisplay方法
return super.onTouchEvent(event);
}
/** 当点击时调用此方法 */
private void switchBtnClick() {
if (switchBtnX == 0) {
switchBtnX = switchBtnMaxSlideDistance;
} else {
switchBtnX = 0;
}
invalidate(); // 类似于ios中[self setNeedDisplay]方法
}
最终效果:

android自定义控件(2)-拖拽实现开关切换的更多相关文章
- 【转】C#.net拖拽实现获得文件路径
C#.net拖拽实现获得文件路径 作者Attilax , EMAIL:1466519819@qq.com 思路: 通过DragEnter事件获得被拖入窗口的“信息”(可以是若干文件,一些文字等等), ...
- 【WPF】一些拖拽实现方法的总结(Window,UserControl)
原文:[WPF]一些拖拽实现方法的总结(Window,UserControl) 原文地址 https://www.cnblogs.com/younShieh/p/10811456.html 前文 本文 ...
- android自定义控件(1)-点击实现开关按钮切换
自定义控件的步骤.用到的主要方法: 1.首先需要定义一个类,继承自View:对于继承View的类,会需要实现至少一个构造方法:实际上这里一共有三个构造方法: public View (Contex ...
- C#.net拖拽实现获得文件路径
思路: 通过DragEnter事件获得被拖入窗口的“信息”(可以是若干文件,一些文字等等), 在DragDrop事件中对“信息”进行解析. 窗体的AllowDrop属性必须设置成tru ...
- HTML5元素拖拽实现示例
HTML5现在前端圈中,已然成为一个不那么新的技术词汇了,很多公司也把HTML5也当成了硬性的技能要求,但是很多前端恐怕都不了解HTML5的拖拽怎么实现吧. 看了下极客学院的视频,大概的了解了下思路. ...
- Silverlight中的拖拽实现的图片上传
原文 http://blog.csdn.net/dujingjing1230/article/details/5443003 在Silverlight中因为可以直接从系统的文件夹里面拖出来一个文件直接 ...
- selenium操作拖拽实现无效果的替代方案
如果碰到这种情况,无论你是直接通过draganddrop()还是分步执行clickandhold(),dragtoelement(),或通过by_offset位移都无法实现元素拖拽.只能物理模拟了 w ...
- HTML5 元素拖拽实现 及 jquery.event.drag插件
如上图片: <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" c ...
- html5简单拖拽实现自动左右贴边+幸运大转盘
此篇文章主要实现两个功能: 1.点击屏幕下方签到悬浮按钮: 2.弹出幸运大转盘,转盘抽奖签到 效果如图: 在网上找了很多移动端拖拽的js实现方式,大部分都是这一种,html5的touch事件,但是 ...
随机推荐
- C++强制类型转换操作符 static_cast
static_cast是一个强制类型转换操作符.强制类型转换,也称为显式转换,C++中强制类型转换操作符有static_cast.dynamic_cast.const_cast.reinterpert ...
- Oracle计算时间差表达式
有两个日期数据START_DATE,END_DATE,欲得到这两个日期的时间差(以天,小时,分钟,秒,毫秒): 天: ROUND(TO_NUMBER(END_DATE - START_DATE)) 小 ...
- BZOJ 4551: [Tjoi2016&Heoi2016]树
4551: [Tjoi2016&Heoi2016]树 Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 748 Solved: 394[Subm ...
- Leetcode 400. Nth digits
解法一: 一个几乎纯数学的解法 numbers: 1,...,9, 10, ..., 99, 100, ... 999, 1000 ,..., 9999, ... # of digits: 9 ...
- 【BZOJ-3876】支线剧情 有上下界的网络流(有下界有源有汇最小费用最大流)
3876: [Ahoi2014]支线剧情 Time Limit: 10 Sec Memory Limit: 256 MBSubmit: 821 Solved: 502[Submit][Status ...
- Android成长日记-APP的签名和打包
签名的意义: 1. 为了保证每个应用程序开发者的合法 2. 防止部分人通过使用相同的Package Name来混淆替换已经安装的程序,从而出现一些恶意篡改 3. 保证我们每次发布的版本的一致性 (如自 ...
- bzoj1801[AHOI2009]CHESS中国象棋
题意:在棋盘上放一些炮使得它们不互相攻击.其实就是一行/一列最多放两个. 50分的数据中n,m至少有一个不超过8,比较直接的想法是对n/m中较小的一维做状态压缩,状态f[i][S1][S2]表示在前i ...
- fork子进程僵尸问题及解决方案
额,原来用 c 写 cgi 的时候用过 fork .那时候 cgi 的生命很短,所以遇到的问题压根没出现过.这次也是更加深入的对 fork 机制进行了一下了解. 参考这里的文档:http://ju.o ...
- ADC/DAC的一些参数
1.LSB,Least Significant Bit LSB是指最低位一个bit的权值,比喻ADC是一把尺子,那LSB则是它的最小刻度.LSB=Vfs/(2^N),Vfs为full scale vo ...
- json的解释
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.JSON采用完全独立于语言的文本格式,这些特性使JSON成为理想的数据交换语言.易于人阅读和编写,同时也易 ...