Android源码解析--Material Design之水波纹点击效果RippleEffect使用

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/48505041 Android5.0已经出了好久了,但是目前市场上的App好像没有多少用5.0上面的一些效果,依旧延续着之前的控件使用,但是既然新的东西已经出来了,就必定会淘汰旧的不好的,所以我们要与时俱进。
版权声明:本文为博主原创文章,转载请标明出处。 https://blog.csdn.net/lyhhj/article/details/48505041

Android5.0已经出了好久了,但是目前市场上的App好像没有多少用5.0上面的一些效果,依旧延续着之前的控件使用,但是既然新的东西已经出来了,就必定会淘汰旧的不好的,所以我们要与时俱进。其中Material Design真的很不错,其中有好多酷炫的动画,Android5.0的SwipeRefreshLayout会取代之前的PullToRefreshListView、RecyclerView,CardView也会取代ListView、MaterialEdittext也会取代Edittex以及一些FloatButton等等,以后会逐一介绍的。今天我们看一下RippleEffect水波纹点击效果,先上图:


大家可以看到按钮或者布局点击的时候会有水波涟漪的效果,很不错,用到你的app上一定会很高大上的。

下面我们分析一下源码,然后再看怎么使用,因为我觉得如果你光会用但是不了解怎么实现的你最多也就算个码农,所以我们要尝试着读懂源码,然后再尝试着自己定义view

首先在init()方法中初始化一些组件和styles,并设置相应的属性包括设置画布的抗锯齿标志、画图的实心空心、透明度颜色的设置。

[java]  view plain copy
  1. <span style="font-size:14px;"><span style="white-space: pre;">  </span>paint = new Paint();  
  2.         paint.setAntiAlias(true);   //设置画布抗锯齿标志  
  3.         paint.setStyle(Paint.Style.FILL);   //设置画图实心  
  4.         paint.setColor(rippleColor);    //设置画图颜色  
  5.         paint.setAlpha(rippleAlpha);    //设置透明度  
  6.         this.setWillNotDraw(false);     //设置将不绘画</span>  
然后创建手势,因为我们的点击有可能为长点击,我们用手势来做一些操作

[java]  view plain copy
  1. <span style="font-size:14px;"><span style="white-space:pre">    </span>/** 
  2.          * 创建新的手势 
  3.          */  
  4.         gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {  
  5.             @Override  
  6.             public void onLongPress(MotionEvent event) {  
  7.                 super.onLongPress(event);  
  8.                 animateRipple(event);   //创建动画  
  9.                 sendClickEvent(true);   //发送长点击事件  
  10.             }  
  11.   
  12.             @Override  
  13.             public boolean onSingleTapConfirmed(MotionEvent e) {  
  14.                 return true;  
  15.             }  
  16.   
  17.             @Override  
  18.             public boolean onSingleTapUp(MotionEvent e) {  
  19.                 return true;  
  20.             }  
  21.         });  
  22.   
  23.         this.setDrawingCacheEnabled(true);  //更新cache,提高绘图速度  
  24.         this.setClickable(true);</span>  
接下来重写OnDraw()方法

[java]  view plain copy
  1. <span style="font-size:14px;">@Override  
  2.     public void draw(Canvas canvas) {  
  3.         super.draw(canvas);  
  4.         if (animationRunning) {  
  5.             if (rippleDuration <= timer * frameRate) {  
  6.                 animationRunning = false;  
  7.                 timer = 0;  
  8.                 durationEmpty = -1;  
  9.                 timerEmpty = 0;  
  10.                 canvas.restore();  
  11.                 invalidate();  
  12.                 if (onCompletionListener != null) onCompletionListener.onComplete(this);  
  13.                 return;  
  14.             } else  
  15.                 canvasHandler.postDelayed(runnable, frameRate);  
  16.   
  17.             if (timer == 0)  
  18.                 canvas.save();  
  19.   
  20.   
  21.             canvas.drawCircle(x, y, (radiusMax * (((float) timer * frameRate) / rippleDuration)), paint);   //画圆的半径  
  22.   
  23.             paint.setColor(Color.parseColor("#ffff4444"));  //设置颜色  
  24.   
  25.             if (rippleType == 1 && originBitmap != null && (((float) timer * frameRate) / rippleDuration) > 0.4f) {  
  26.                 if (durationEmpty == -1)  
  27.                     durationEmpty = rippleDuration - timer * frameRate;  
  28.   
  29.                 timerEmpty++;  
  30.                 //创建圆的bitmap  
  31.                 final Bitmap tmpBitmap = getCircleBitmap((int) ((radiusMax) * (((float) timerEmpty * frameRate) / (durationEmpty))));  
  32.                 canvas.drawBitmap(tmpBitmap, 00, paint);  
  33.                 tmpBitmap.recycle();  
  34.             }  
  35.   
  36.             paint.setColor(rippleColor);  
  37.   
  38.             if (rippleType == 1) {  
  39.                 if ((((float) timer * frameRate) / rippleDuration) > 2f)  
  40.                     paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timerEmpty * frameRate) / (durationEmpty)))));  
  41.                 else  
  42.                     paint.setAlpha(rippleAlpha);  
  43.             }  
  44.             else  
  45.                 paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timer * frameRate) / rippleDuration))));  
  46.   
  47.             timer++;  
  48.         }  
  49.     }</span>  
这里面包括我们设置圆的颜色、半径大小,透明度(透明度是根据距离的增长而越来越透明的)

最重要的核心部分也就是创建动画了:

[java]  view plain copy
  1. <span style="font-size:18px;">     </span><span style="font-size:14px;">/** 
  2.      * Create Ripple animation centered at x, y 
  3.      * 
  4.      * @param x Horizontal position of the ripple center 
  5.      * @param y Vertical position of the ripple center 
  6.      */  
  7.     private void createAnimation(final float x, final float y) {  
  8.         if (this.isEnabled() && !animationRunning) {  
  9.             if (hasToZoom)  
  10.                 this.startAnimation(scaleAnimation);  
  11.   
  12.             radiusMax = Math.max(WIDTH, HEIGHT);  
  13.   
  14.             if (rippleType != 2)  
  15.                 radiusMax /= 1;  
  16.   
  17.             radiusMax -= ripplePadding;  
  18.   
  19.             if (isCentered || rippleType == 1) {  
  20.                 this.x = getMeasuredWidth() ;  
  21.                 this.y = getMeasuredHeight() ;  
  22.             } else {  
  23.                 this.x = x;  
  24.                 this.y = y;  
  25.             }  
  26.   
  27.             animationRunning = true;  
  28.   
  29.             if (rippleType == 1 && originBitmap == null)  
  30.                 originBitmap = getDrawingCache(true);  
  31.   
  32.             invalidate();  
  33.         }  
  34.     }</span>  
我们可以在这里面设置圆的最大半径,最大半径越大,我们得到的水波涟漪效果越快,越小,得到的水波涟漪效果越慢,也就是radiusMax /=1,这句代码。

那我们的动画怎么设置呢?当然用ScaleAnimation动画了

[java]  view plain copy
  1. <span style="font-size:14px;">@Override  
  2.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  3.         super.onSizeChanged(w, h, oldw, oldh);  
  4.         WIDTH = w;  
  5.         HEIGHT = h;  
  6.   
  7.         scaleAnimation = new ScaleAnimation(2.0f, zoomScale, 2.0f, zoomScale, w / 2, h / 2);  
  8.         scaleAnimation.setDuration(zoomDuration);  
  9.         scaleAnimation.setRepeatMode(Animation.REVERSE);  
  10.         scaleAnimation.setRepeatCount(1);  
  11.     }</span>  
它的参数如下:

ScaleAnimation(float fromX, float toX, float fromY, float toY,int pivotXType, float pivotXValue, int pivotYType, float pivotYValue)   

参数说明:   

float fromX 动画起始时 X坐标上的伸缩尺寸   

float toX 动画结束时 X坐标上的伸缩尺寸   

float fromY 动画起始时Y坐标上的伸缩尺寸   

float toY 动画结束时Y坐标上的伸缩尺寸   

int pivotXType 动画在X轴相对于物件位置类型   

float pivotXValue 动画相对于物件的X坐标的开始位置   

int pivotYType 动画在Y轴相对于物件位置类型   

float pivotYValue 动画相对于物件的Y坐标的开始位置  


好了,这样差不多就完成了我们的水波涟漪效果了。。。。

看一下怎么用吧?

如果你的开发IDE是Android Studio那么我们可以把github上的库集成到我们的项目中,

[java]  view plain copy
  1. <span style="font-size:14px;">dependencies {    
  2.     compile 'com.github.traex.rippleeffect:library:1.2.3'    
  3. } </span>  
在我们的布局中引用RippleEffect就OK了

[java]  view plain copy
  1. <span style="font-size:14px;"><com.Hankkin.library.RippleView    
  2.   android:id="@+id/more"    
  3.   android:layout_width="?android:actionBarSize"    
  4.   android:layout_height="?android:actionBarSize"    
  5.   android:layout_toLeftOf="@+id/more2"    
  6.   android:layout_margin="5dp"    
  7.   ripple:rv_centered="true">    
  8.      
  9.   <ImageView    
  10.     android:layout_width="?android:actionBarSize"    
  11.     android:layout_height="?android:actionBarSize"    
  12.     android:src="@android:drawable/ic_menu_edit"    
  13.     android:layout_centerInParent="true"    
  14.     android:padding="10dp"    
  15.     android:background="@android:color/holo_blue_dark"/>    
  16.      
  17. </com.Hankkin.library.RippleView>  </span>  
当然你也可以把库中的RippleView直接拷到我们的项目里面,还可以该里面的动画快慢速度等,注意也要把库里面的styles,attrs拷进来,放到自己的项目里面,就可以自己改一些配置了。

——————————————————————————————————————————————————————————————————————————————————————————————————————

下面再和大家说一下比较重要的一点吧,这个网上的demo都没有说,是我自己用的时候发现的

也就是我们的点击事件,这时候如果你还用普通的OnClickListener()是不行的,因为动画还没有结束,就直接startIntent()跳转界面了,如果你的界面没有finish()掉的话,返回的时候动画会继续执行完。

那么怎么破呢?

我们就需要给我们的RippleView设置监听事件而不是我们的控件设置监听事件了,因为我们的RippleView中有这样一个接口:

[java]  view plain copy
  1. <span style="font-size:14px;">public interface OnRippleCompleteListener {    
  2.         void onComplete(RippleView rippleView);    
  3.     } </span>  
也就是动画完成的事件
[java]  view plain copy
  1. <span style="font-size:14px;">RippleView view = (RippleView) findViewById(R.id.reView);    
  2.         view.setOnRippleCompleteListener(new RippleView.OnRippleCompleteListener() {    
  3.             @Override    
  4.             public void onComplete(RippleView rippleView) {    
  5.                 Intent intent = new Intent(getApplicationContext(),HelloActivity.class);    
  6.                 startActivity(intent);    
  7.             }    
  8.         });  </span>  
这样我们就实现了动画完成之后才来实现界面跳转了

小伙伴们,快试一下吧。

当然我们的ListView的item点击也可以实现这样的效果,因为我们的RippleView中是支持Listview点击的

[java]  view plain copy
  1. /**  
  2.      * Send a click event if parent view is a Listview instance  
  3.      * 若为Listview发送点击事件  
  4.      * @param isLongClick Is the event a long click ?  
  5.      */    
  6.     private void sendClickEvent(final Boolean isLongClick) {    
  7.         if (getParent() instanceof AdapterView) {    
  8.             final AdapterView adapterView = (AdapterView) getParent();    
  9.             final int position = adapterView.getPositionForView(this);    
  10.             final long id = adapterView.getItemIdAtPosition(position);    
  11.             if (isLongClick) {    
  12.                 if (adapterView.getOnItemLongClickListener() != null)    
  13.                     adapterView.getOnItemLongClickListener().onItemLongClick(adapterView, this, position, id);    
  14.             } else {    
  15.                 if (adapterView.getOnItemClickListener() != null)    
  16.                     adapterView.getOnItemClickListener().onItemClick(adapterView, this, position, id);    
  17.             }    
  18.         }    
  19.     }    

这里先提一下,以后会详细说怎么用的.....

github地址:

https://github.com/traex/RippleEffect

相关文章
|
5天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
5天前
|
存储 安全 Linux
Golang的GMP调度模型与源码解析
【11月更文挑战第11天】GMP 调度模型是 Go 语言运行时系统的核心部分,用于高效管理和调度大量协程(goroutine)。它通过少量的操作系统线程(M)和逻辑处理器(P)来调度大量的轻量级协程(G),从而实现高性能的并发处理。GMP 模型通过本地队列和全局队列来减少锁竞争,提高调度效率。在 Go 源码中,`runtime.h` 文件定义了关键数据结构,`schedule()` 和 `findrunnable()` 函数实现了核心调度逻辑。通过深入研究 GMP 模型,可以更好地理解 Go 语言的并发机制。
|
18天前
|
消息中间件 缓存 安全
Future与FutureTask源码解析,接口阻塞问题及解决方案
【11月更文挑战第5天】在Java开发中,多线程编程是提高系统并发性能和资源利用率的重要手段。然而,多线程编程也带来了诸如线程安全、死锁、接口阻塞等一系列复杂问题。本文将深度剖析多线程优化技巧、Future与FutureTask的源码、接口阻塞问题及解决方案,并通过具体业务场景和Java代码示例进行实战演示。
38 3
|
1月前
|
存储
让星星⭐月亮告诉你,HashMap的put方法源码解析及其中两种会触发扩容的场景(足够详尽,有问题欢迎指正~)
`HashMap`的`put`方法通过调用`putVal`实现,主要涉及两个场景下的扩容操作:1. 初始化时,链表数组的初始容量设为16,阈值设为12;2. 当存储的元素个数超过阈值时,链表数组的容量和阈值均翻倍。`putVal`方法处理键值对的插入,包括链表和红黑树的转换,确保高效的数据存取。
53 5
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
110 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
算法 Java 程序员
Map - TreeSet & TreeMap 源码解析
Map - TreeSet & TreeMap 源码解析
33 0
|
5天前
|
搜索推荐 Android开发 开发者
探索安卓开发中的自定义视图:打造个性化UI组件
【10月更文挑战第39天】在安卓开发的世界中,自定义视图是实现独特界面设计的关键。本文将引导你理解自定义视图的概念、创建流程,以及如何通过它们增强应用的用户体验。我们将从基础出发,逐步深入,最终让你能够自信地设计和实现专属的UI组件。
|
7天前
|
Android开发 Swift iOS开发
探索安卓与iOS开发的差异和挑战
【10月更文挑战第37天】在移动应用开发的广阔舞台上,安卓和iOS这两大操作系统扮演着主角。它们各自拥有独特的特性、优势以及面临的开发挑战。本文将深入探讨这两个平台在开发过程中的主要差异,从编程语言到用户界面设计,再到市场分布的不同影响,旨在为开发者提供一个全面的视角,帮助他们更好地理解并应对在不同平台上进行应用开发时可能遇到的难题和机遇。
|
9天前
|
XML 存储 Java
探索安卓开发之旅:从新手到专家
【10月更文挑战第35天】在数字化时代,安卓应用的开发成为了一个热门话题。本文旨在通过浅显易懂的语言,带领初学者了解安卓开发的基础知识,同时为有一定经验的开发者提供进阶技巧。我们将一起探讨如何从零开始构建第一个安卓应用,并逐步深入到性能优化和高级功能的实现。无论你是编程新手还是希望提升技能的开发者,这篇文章都将为你提供有价值的指导和灵感。

推荐镜像

更多