(4.2.31)RippleEffect(水波纹效果)的实现

Demo 下载:

https://github.com/CodingForAndroid/RippleEffect

学习 谷歌 material design的交互设计、向新技术靠拢~ 


由于谷歌的只有在5.0+才可以有这个效果~ 而我们手头手机大部分还是4.+的、因此自己去实现这个效果、让各种版本的都可以用 无疑是挺好的、

这个Demo 实现了以下几点功能:

①:要实现水波纹效果,首先这个View 必须是可点击的,也就是说clickable :true 才可以触发 比如默认 Button 的Clickable =true,TextView, ImageView =false,但是可以手动设置 true。

②:这个是一个布局、可以包裹 需要实现水波纹效果的 view,任何View 只要是可点击的,都可以包裹进来。

③:可以保证,当手指按下在当前View 上,如果手滑动出了当前View,不会触发该 点击事件, 也就是只有手按下,和手抬起,都是同一个View 才触发 点击事件。

④:这个效果,保证是 水波纹 结束以后,再去响应 View的点击事件 。

⑤:一个布局 包裹这么多View,怎么区分每一个的点击事件呢? 答: 根据 每个View 的id 去响应不同的 事件。

大体就这么多,具体的可以自己尝试,与补充。这个效果参考了  singwhatiwanna 写的、

大体思路 可以去看他的博客、写的很详细、http://blog.csdn.net/singwhatiwanna/article/details/42614953、自己整理 ,为了积累加深一遍。

水波纹效果、就是一圈一圈 向外扩散的圆、实现思路 、就是 以手指触摸的位置为圆心、不断的改变圆的半径、向外画圆、以此达到效果。

而为每一个View 去实现这样一个效果、显然比较费精力,有这样一个布局,可以让其包裹的子View 实现 Ripple 效果,就比较符合我们的需求。

实现思想

首先我们自定义一个layout,这里我们选取LinearLayout,至于原因,文章下面会进行分析。当用户点击一个可点击的元素时,比如button,我们需要得到用户点击的元素的信

息,包含:用户点击了哪个元素、用户点击的那个元素的宽、高、位置信息等。得到了button的信息后,我就可以确定水波纹的范围,然后通过layout进行重绘去绘制水波纹,

这样水波纹效果就实现了,当然,这只是大概步骤,中间还是有一些细节需要处理的。

实现过程

实现过程主要是如下几个问题的解决:

①. 如何得知用户点击了哪个元素

②. 如何取得被点击元素的信息

③. 如何通过layout进行重绘绘制水波纹

④. 如果延迟up事件的分发

如何得知用户点击了哪个元素

这个问题好弄,为了得知用户点击了哪个元素(这个元素一般来说要是可点击的,否则是无意义的),我们要提前拦截所有的点击事件,于是,我们应该重写layout中的

dispatchTouchEvent方法,注意,这里不推荐用onInterceptTouchEvent,因为onInterceptTouchEvent不是一直会被回调的。

然后当用户点击的时候,会有一系列的down、move、up事件,我们要在down的时候来确定事件落在哪个元素上,down的元素就是用户点击的元素,当然为了严谨,我们还

要判断up的时候是否也落在同一个元素上面,因为,系统click事件的判断规则就是:down和up同时落在同一个可点击的元素上。

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. @Override  
  2. public boolean dispatchTouchEvent(MotionEvent event) {  
  3.     int x = (int) event.getRawX();  
  4.     int y = (int) event.getRawY();  
  5.     int action = event.getAction();  
  6.     if (action == MotionEvent.ACTION_DOWN) {  
  7.         View touchTarget = getTouchTarget(this, x, y);  
  8.         if (touchTarget.isClickable() && touchTarget.isEnabled()) {  
  9.             mTouchTarget = touchTarget;  
  10.             initParametersForChild(event, touchTarget);  
  11.             postInvalidateDelayed(INVALIDATE_DURATION);  
  12.         }  
  13.     } else if (action == MotionEvent.ACTION_UP) {  
  14.         mIsPressed = false;  
  15.         postInvalidateDelayed(INVALIDATE_DURATION);  
  16.         mDispatchUpTouchEventRunnable.event = event;  
  17.         postDelayed(mDispatchUpTouchEventRunnable, 400);  
  18.         return true;  
  19.     } else if (action == MotionEvent.ACTION_CANCEL) {  
  20.         mIsPressed = false;  
  21.         postInvalidateDelayed(INVALIDATE_DURATION);  
  22.     }  
  23.   
  24.     return super.dispatchTouchEvent(event);  
  25. }  

通过上述代码,我们可以知道,当down的时候,我们取出点击事件的屏幕坐标,然后去遍历view树找到用户所点击的那个view,代码如下,就是判断事件的坐标是否落在view

的范围内,这个不再多说了,比较好理解。需要注意的是,事件的坐标我们不能用getX和getY,而要用getRawX和getRawY,二者的区别是:前者是相对于被点击view的坐

标,后者是相对于屏幕的坐标,而我们的目标view具体位于layout的哪一层我们无法知道,所以,必须用屏幕的绝对坐标来进行计算。而有了事件的坐标,再根据view在屏幕中

的绝对坐标,只要判断事件的xy是否落在view的上下左右四个角之内,就可以知道事件是否落在view上,从而取出用户所点击的那个view。

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. private View getTouchTarget(View view, int x, int y) {  
  2.     View target = null;  
  3.     ArrayList<View> TouchableViews = view.getTouchables();  
  4.     for (View child : TouchableViews) {  
  5.         if (isTouchPointInView(child, x, y)) {  
  6.             target = child;  
  7.             break;  
  8.         }  
  9.     }  
  10.   
  11.     return target;  
  12. }  
  13.   
  14. private boolean isTouchPointInView(View view, int x, int y) {  
  15.     int[] location = new int[2];  
  16.     view.getLocationOnScreen(location);  
  17.     int left = location[0];  
  18.     int top = location[1];  
  19.     int right = left + view.getMeasuredWidth();  
  20.     int bottom = top + view.getMeasuredHeight();  
  21.     if (view.isClickable() && y >= top && y <= bottom  
  22.             && x >= left && x <= right) {  
  23.         return true;  
  24.     }  
  25.     return false;  
  26. }  

如何取得被点击元素的信息

这个比较简单,被点击元素的信息有:宽、高、left、top、right、bottom,获取它们的代码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. int[] location = new int[2];  
  2. mTouchTarget.getLocationOnScreen(location);  
  3. int left = location[0] - mLocationInScreen[0];  
  4. int top = location[1] - mLocationInScreen[1];  
  5. int right = left + mTouchTarget.getMeasuredWidth();  
  6. int bottom = top + mTouchTarget.getMeasuredHeight();  

说明:mTouchTarget指的是用户点击的那个view

如何通过layout进行重绘绘制水波纹

这个会水波纹比较简单,只要用drawCircle绘制一个半透明的圆环即可,这里主要说下绘

制时机。一般来说,我们会选择在onDraw中去进行绘制,这是没错的,但是对于L中的效果不太适合,查看view的绘制过程,我们会明白,view的绘制大致遵循如下流程:先

绘制背景,再绘制自己(onDraw),接着绘制子元素(dispatchDraw),最后绘制一些装饰等比如滚动条(onDrawScrollBars),因此,如果我们在onDraw中绘制波纹,那

么由于子元素的绘制在onDraw之后,就会导致子元素盖住我们所绘制的圆环,这样,圆环就有可能看不全了,因为,把我绘制的时机很重要。根据view的绘制流程,我们选择

dispatchDraw比较合适,当所有的子元素都绘制完成后,再进行波纹的绘制。读到这里,大家会更加明白,为什么我们要选择LinearLayout以及为什么不建议view的嵌套层级

太深,因为如果view本身比较重或者嵌套层级太深,就会导致dispatchDraw执行的耗时增加,这样水波的绘制就会收到些许影响。因此,性能的平滑在代码中也很重要,也是

需要考虑的。同时,为了不让绘制的圆环超出被点击元素的范围,我们需要对canvas进行clip。为了有波纹效果,我们需要频繁地进行layout重绘,并且在重绘的过程中改变圆

环的半径,这样一个动态的水波纹就出来了。仍然,我来性能的考虑,我们选择用postInvalidateDelayed(long delayMilliseconds, int left, int top, int right, int bottom)来进行

view的部分重绘,因为,其他区域是不需要重绘的,仅仅是被点击的元素所在的区域需要重绘。为什么要采用Delayed这个方法,原因是我们不能一直进行刷新,必须有一点点

时间间隔,这样做的好处是:避免view的重绘抢占过多时间片从而造成潜在的间接栈溢出,因为invalidate会直接导致draw的调用。具体代码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. protected void dispatchDraw(Canvas canvas) {  
  2.     super.dispatchDraw(canvas);  
  3.     if (!mShouldDoAnimation || mTargetWidth <= 0 || mTouchTarget == null) {  
  4.         return;  
  5.     }  
  6.   
  7.     if (mRevealRadius > mMinBetweenWidthAndHeight / 2) {  
  8.         mRevealRadius += mRevealRadiusGap * 4;  
  9.     } else {  
  10.         mRevealRadius += mRevealRadiusGap;  
  11.     }  
  12.     int[] location = new int[2];  
  13.     mTouchTarget.getLocationOnScreen(location);  
  14.     int left = location[0] - mLocationInScreen[0];  
  15.     int top = location[1] - mLocationInScreen[1];  
  16.     int right = left + mTouchTarget.getMeasuredWidth();  
  17.     int bottom = top + mTouchTarget.getMeasuredHeight();  
  18.   
  19.     canvas.save();  
  20.     canvas.clipRect(left, top, right, bottom);  
  21.     canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);  
  22.     canvas.restore();  
  23.   
  24.     if (mRevealRadius <= mMaxRevealRadius) {  
  25.         postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);  
  26.     } else if (!mIsPressed) {  
  27.         mShouldDoAnimation = false;  
  28.         postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);  
  29.     }  
  30. }  

上面就是实现水波纹的大体步骤,到了这里,我们的布局已经可以让子View 具备水波纹效果了。

如何实现事件的监听

但是 如果 我们要实现点击事件的话,此时当你点击View水波还没有结束,就已经响应了点击事件,所以我们还有最后一步要做,等水波纹绘制结束后,去响应点击事件,如何

控制呢?如果绘制完了,能告诉我们,然后我们再去响应点击事件就容易多了。

按这个思路,我们写一个回调函数,当绘制完成后,告诉我们一声,我们去响应点击事件。

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. // 绘制完成后的 监听  
  2. private OnRippleCompleteListener onCompletionListener;  
  3.   
  4. public void setOnRippleCompleteListener(OnRippleCompleteListener listener) {  
  5.     this.onCompletionListener = listener;  
  6. }  
我们可以仿照我们给View setOnClickListener()的方式 设置一个 完成后的监听。

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1.     /** 
  2.      * 绘制 子view 
  3.      */  
  4.     @Override  
  5.     protected void dispatchDraw(Canvas canvas) {  
  6.         super.dispatchDraw(canvas);  
  7.         // 如果目标view 不存在 或者 以及绘制完成 则取消绘制  
  8.         if (mTargetView == null || !mShouldDoAnimation || mTargetWidth <= 0)  
  9.             return;  
  10.         // 如果 圆的当前半径 超过了 按钮 宽度 或者高度的 1/2 则 半径增加幅度变大  
  11.         if (mRevealRadius > mMinBetweenWidthAndHeight /4)  
  12.             mRevealRadius += mRevealRadiusGap * 4;  
  13.         else  
  14.             mRevealRadius += mRevealRadiusGap;  
  15.         int[] location = new int[2];  
  16.         this.getLocationOnScreen(mLocation);  
  17.         mTargetView.getLocationOnScreen(location);  
  18.         // 计算当前目标view 的 l t r b  
  19.         int top = location[1] - mLocation[1];  
  20.         int left = location[0] - mLocation[0];  
  21.         int right = left + mTargetView.getMeasuredWidth();  
  22.         int bottom = top + mTargetView.getMeasuredHeight();  
  23.         canvas.save();  
  24.         // 设置绘制区域 起点(left,top)宽度 mTargetView.getMeasuredWidth(),高度  
  25.         // mTargetView.getMeasuredHeight()  
  26.         canvas.clipRect(left, top, right, bottom);  
  27.         // 画圆 圆心(mCenterX,mCenterY) 半径 :mRevealRadius 画笔 :mPaint  
  28.         canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);  
  29.         // 恢复原来的状态  
  30.         canvas.restore();  
  31. //      canvas.drawLine(startX, startY, stopX, stopY, paint)  
  32.         // 如果当前半径 还没有超过 最大半径 表示 还没有覆盖整个button 还需要继续 护自己  
  33.         if (mRevealRadius <= mMaxRadius) {  
  34.             postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);  
  35.         } else if (!mIsPressed) {  
  36.             // 当绘制完成 时候执行, 让 button 恢复原来的样子  
  37.             mShouldDoAnimation = false;  
  38.             postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);  
  39.             // 对外 实现 点击事件的效果,等button 刷新完成后执行  
  40.             if (onCompletionListener != null&&onOneView)  
  41.                 onCompletionListener.onComplete(mTargetView.getId());  
  42.               
  43.         }  
  44.   
  45.     }  

这样在我们DispatchDraw 方法 ,判断 当圆的半径足够大了,那我们就让我们的监听事件告诉我们,可以相应点击事件了。

当然这里还有一点要注意的,要保证 我们的View 是同一个,如果,你手指按下在id为xxxx1的Button 上,然后滑动到id 为xxxxx2的Button,当然不能响应点击事件,因此我们

加了一个onOneView。

Android交流群:230274309 一起分享,一起进步!少划水,多晒干货!!欢迎大家!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
自动控制节水灌溉技术的高低代表着农业现代化的发展状况,灌溉系统自动化水平较低是制约我国高效农业发展的主要原因。本文就此问题研究了单片机控制的滴灌节水灌溉系统,该系统可对不同土壤的湿度进行监控,并按照作物对土壤湿度的要求进行适时、适量灌水,其核心是单片机和PC机构成的控制部分,主要对土壤湿度与灌水量之间的关系、灌溉控制技术及设备系统的硬件、软件编程各个部分进行了深入的研究。 单片机控制部分采用上下位机的形式。下位机硬件部分选用AT89C51单片机为核心,主要由土壤湿度传感器,信号处理电路,显示电路,输出控制电路,故障报警电路等组成,软件选用汇编语言编程。上位机选用586型以上PC机,通过MAX232芯片实现同下位机的电平转换功能,上下位机之间通过串行通信方式进行数据的双向传输,软件选用VB高级编程语言以建立友好的人机界面。系统主要具有以下功能:可在PC机提供的人机对话界面上设置作物要求的土壤湿度相关参数;单片机可将土壤湿度传感器检测到的土壤湿度模拟量转换成数字量,显示于LED显示器上,同时单片机可采用串行通信方式将此湿度值传输到PC机上;PC机通过其内设程序计算出所需的灌水量和灌水时间,且显示于界面上,并将有关的灌水信息反馈给单片机,若需灌水,则单片机系统启动鸣音报警,发出灌水信号,并经放大驱动设备,开启电磁阀进行倒计时定时灌水,若不需灌水,即PC机上显示的灌水量和灌水时间均为0,系统不进行灌水。
智慧农业是一种结合了现代信息技术,包括物联网、大数据、云计算等,对农业生产过程进行智能化管理和监控的新模式。它通过各种传感器和设备采集农业生产中的关键数据,如大气、土壤和水质参数,以及生物生长状态等,实现远程诊断和精准调控。智慧农业的核心价值在于提高农业生产效率,保障食品安全,实现资源的可持续利用,并为农业产业的转型升级提供支持。 智慧农业的实现依赖于多个子系统,包括但不限于设施蔬菜精细化种植管理系统、农业技术资料库、数据采集系统、防伪防串货系统、食品安全与质量追溯系统、应急追溯系统、灾情疫情防控系统、农业工作管理系统、远程诊断系统、监控中心、环境监测系统、智能环境控制系统等。这些系统共同构成了一个综合的信息管理和服务平台,使得农业生产者能够基于数据做出更加科学的决策。 数据采集是智慧农业的基础。通过手工录入、传感器自动采集、移动端录入、条码/RFID扫描录入、拍照录入以及GPS和遥感技术等多种方式,智慧农业系统能够全面收集农业生产过程中的各种数据。这些数据不仅包括环境参数,还涵盖了生长状态、加工保存、检验检疫等环节,为农业生产提供了全面的数据支持。 智慧农业的应用前景广阔,它不仅能够提升农业生产的管理水平,还能够通过各种应用系统,如库房管理、无公害监控、物资管理、成本控制等,为农业生产者提供全面的服务。此外,智慧农业还能够支持政府监管,通过发病报告、投入品报告、死亡报告等,加强农业产品的安全管理和质量控制。 面对智慧农业的建设和发展,存在一些挑战,如投资成本高、生产过程标准化难度大、数据采集和监测的技术难题等。为了克服这些挑战,需要政府、企业和相关机构的共同努力,通过政策支持、技术创新和教育培训等手段,推动智慧农业的健康发展。智慧农业的建设需要明确建设目的,选择合适的系统模块,并制定合理的设备布署方案,以实现农业生产的智能化、精准化和高效化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值