zoukankan      html  css  js  c++  java
  • Android自己定义控件系列三:自己定义开关button(二)

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



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


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


    对于触摸事件来说。一般返回值为true的话,那么就代表在这里消费掉本次触摸。而返回false的话,就在当前位置对本次触摸不做处理或者不能全然处理,还须要继续将本次事件分发给兴许view或者viewgroup响应,对于这里。我们定义的开关button已经是子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是有一整套的流程的。。找时间会写一篇关于此的博文吧。

    下一篇会解说怎样自己定义控件的自己定义属性。谢谢关注!






  • 相关阅读:
    回顾2016,工作总结!
    上传base64格式的图片到服务器
    input输入提示历史记录
    input输入时软键盘回车显示搜索
    JS设置和读取Cookie
    正则表达式识别字符串中的URL
    X-Frame-Options配置
    pytest学习笔记
    测试理论基础总结
    redis杂七杂八
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/7079381.html
Copyright © 2011-2022 走看看