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

wkq72376 8年前

来自: http://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,并设置相应的属性包括设置画布的抗锯齿标志、画图的实心空心、透明度颜色的设置。</span>

[java]  view plain copy
</div> </div>
  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>  
</div> 然后创建手势,因为我们的点击有可能为长点击,我们用手势来做一些操作
</span>

[java]  view plain copy
</div> </div>
  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>  
</div> 接下来重写OnDraw()方法</span>

[java]  view plain copy
</div> </div>
  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>  
</div> 这里面包括我们设置圆的颜色、半径大小,透明度(透明度是根据距离的增长而越来越透明的)

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

[java]  view plain copy
</div> </div>
  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>  
</div> 我们可以在这里面设置圆的最大半径,最大半径越大,我们得到的水波涟漪效果越快,越小,得到的水波涟漪效果越慢,也就是radiusMax /=1,这句代码。</span>

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

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

[java]  view plain copy
</div> </div>
  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>  
</div> 当然你也可以把库中的RippleView直接拷到我们的项目里面,还可以该里面的动画快慢速度等,注意也要把库里面的styles,attrs拷进来,放到自己的项目里面,就可以自己改一些配置了。

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

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

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

那么怎么破呢?

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

[java]  view plain copy
</div> </div>
  1. <span style="font-size:14px;">public interface OnRippleCompleteListener {    
  2.         void onComplete(RippleView rippleView);    
  3.     } </span>  
</div> 也就是动画完成的事件</span>
[java]  view plain copy
</div> </div>
  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>  
</div> 这样我们就实现了动画完成之后才来实现界面跳转了</span>

小伙伴们,快试一下吧。

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

[java]  view plain copy
</div> </div>
  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.     }    
</div>
这里先提一下,以后会详细说怎么用的.....</span>

github地址:

https://github.com/traex/RippleEffect

</div>