(转)Android RippleEffect波纹效果,重写ViewGroup

原地址:http://blog.csdn.net/yalinfendou/article/details/46482969


    Android RippleEffect波纹效果,重写ViewGroup

   一直觉得Material Design很美,一直琢磨着打算给公司项目也换成Material Design风格,这里先介绍一种重写RelativeLayout实现的RippleEffect的波纹效果。

       先来看看Demo效果(GIF做得不好,见谅):


 


       先来看实现的思路,可以重写Button,ImageView等View,也可以重写ViewGroup。不同的是,重写ViewGroup时,需要在dispatchTouchEvent拦截点击事件,确定到底是出发了哪一个子view,然后再在onTouchEvent中去触发,当然也可以忽略dispatchTouchEvent,那么整个ViewGroup都会有RippleEffect效果。

       实现RippleEffect波纹效果的方式就是drawCircle,点击控件时,记录下点击位置的x,y值,作为波纹触发的中心点(当然也可以直接设定波纹触发的中心点为当前被点击控件的中心),再根据当前被点击的View的长宽,初始化一些与波纹半径相关的一些参数,每隔一定的时间,我们就绘制一次,每次绘制时,绘制波纹半径会在上一次的基础上,有一定的增量,可以匀速增,加速增,减速增,如果当绘制波纹半径大于我们设定的范围时,就停止绘制,这样就实现了RippleEffect效果。当然,绘制时可以用invalidate()+Handler的方式或者直接使用postInvalidate()刷新界面 。

来看看一个Git上的 RippleEffect代码:

[java]  view plain  copy
  1. private void init(final Context context, final AttributeSet attrs) {  
  2.        if (isInEditMode())  
  3.            return;  
  4.   
  5.        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RippleView);  
  6.        rippleColor = typedArray.getColor(R.styleable.RippleView_rv_color, getResources().getColor(R.color.rippelColor));  
  7.        rippleType = typedArray.getInt(R.styleable.RippleView_rv_type, 0);  
  8.        hasToZoom = typedArray.getBoolean(R.styleable.RippleView_rv_zoom, false);  
  9.        isCentered = typedArray.getBoolean(R.styleable.RippleView_rv_centered, false);  
  10.        rippleDuration = typedArray.getInteger(R.styleable.RippleView_rv_rippleDuration, rippleDuration);  
  11.        frameRate = typedArray.getInteger(R.styleable.RippleView_rv_framerate, frameRate);  
  12.        rippleAlpha = typedArray.getInteger(R.styleable.RippleView_rv_alpha, rippleAlpha);  
  13.        ripplePadding = typedArray.getDimensionPixelSize(R.styleable.RippleView_rv_ripplePadding, 0);  
  14.        canvasHandler = new Handler();  
  15.        zoomScale = typedArray.getFloat(R.styleable.RippleView_rv_zoomScale, 1.03f);  
  16.        zoomDuration = typedArray.getInt(R.styleable.RippleView_rv_zoomDuration, 200);  
  17.        typedArray.recycle();  
  18.        paint = new Paint();  
  19.        paint.setAntiAlias(true);  
  20.        paint.setStyle(Paint.Style.FILL);  
  21.        paint.setColor(rippleColor);  
  22.        paint.setAlpha(rippleAlpha);  
  23.        this.setWillNotDraw(false);  
  24.   
  25.        gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {  
  26.            @Override  
  27.            public void onLongPress(MotionEvent event) {  
  28.                super.onLongPress(event);  
  29.                animateRipple(event);  
  30.                sendClickEvent(true);  
  31.            }  
  32.   
  33.            @Override  
  34.            public boolean onSingleTapConfirmed(MotionEvent e) {  
  35.                return true;  
  36.            }  
  37.   
  38.            @Override  
  39.            public boolean onSingleTapUp(MotionEvent e) {  
  40.                return true;  
  41.            }  
  42.        });  
  43.   
  44.        this.setDrawingCacheEnabled(true);  
  45.        this.setClickable(true);  
  46.    }  

       和其它自定义View或者ViewGroup一样,构造方法中调用此方法初始化自定义属性及一些Paint,canvas等,这里也初始化了gestureDetector,是为了在长按事件中也会触发RippleEffect波纹效果。

       看看attrs.xml中的自定义属性:

[html]  view plain  copy
  1.           
  2. <declare-styleable name="RippleView">rv_zoomDuration  
  3.    <attr name="rv_alpha" format="integer" />  
  4.    <attr name="rv_framerate" format="integer"/>  
  5.    <attr name="rv_rippleDuration" format="integer"/>  
  6.    <attr name="rv_zoomDuration" format="integer" />  
  7.    <attr name="rv_color" format="color" />  
  8.    <attr name="rv_centered" format="boolean" />  
  9.    <attr name="rv_type" format="enum">  
  10.        <enum name="simpleRipple" value="0"/>  
  11.        <enum name="doubleRipple" value="1"/>  
  12.        <enum name="rectangle" value="2" />  
  13.    </attr>  
  14.    <attr name="rv_ripplePadding" format="dimension" />  
  15.    <attr name="rv_zoom" format="boolean" />  
  16.    <attr name="rv_zoomScale" format="float" />  
  17. ;/declare-styleable>    

  • rv_alpha即paint.setAlpha()中设定的值,就是透明度,其取值范围是0---255,数值越小,越透明,颜色上表现越淡。
  • rv_framerate是RippleEffect波纹每次半径增加相关的一个参数,值越大,每次的半径增量越大,效果看起来也就越粗糙。
  • rv_rippleDurationRippleEffect波纹的持续时间。(这里原博客写错了)
  • rv_zoomDuration就是RippleEffect波纹的范围了,超过此范围,就不再绘制了。
  • rv_color为RippleEffect波纹的颜色。
  • rv_centered值为true,则波纹的触发点为View的中心,否则为触摸点的位置。
  • rv_type为波纹效果的类型,simpleRipple就是最常见的那种,只产生一圈涟漪效果,doubleRipple两圈涟漪,rectangle的效果代码中没具体实现。
  • rv_zoom值为true,会有一个缩放的效果,rv_ripplePadding为和RippleEffect波纹半径相关的一个参数,rv_zoomScale为缩放动画结束时Y坐标上的伸缩尺寸。

       最重要的draw方法:

[html]  view plain  copy
  1. @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.               final Bitmap tmpBitmap = getCircleBitmap((int) ((radiusMax) * (((float) timerEmpty * frameRate) / (durationEmpty))));  
  31.               canvas.drawBitmap(tmpBitmap, 0, 0, paint);  
  32.               tmpBitmap.recycle();  
  33.           }  
  34.   
  35.           paint.setColor(rippleColor);  
  36.   
  37.           if (rippleType == 1) {  
  38.               if ((((float) timer * frameRate) / rippleDuration) > 0.6f)  
  39.                   paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timerEmpty * frameRate) / (durationEmpty)))));  
  40.               else  
  41.                   paint.setAlpha(rippleAlpha);  
  42.           }  
  43.           else  
  44.               paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timer * frameRate) / rippleDuration))));  
  45.   
  46.           timer++;  
  47.       }  
  48.   }  

       在初始化的时候, animationRunning值已被置为true,主要是为了保证一次完整地波纹效果不会被打断。 首先执行 canvasHandler.postDelayed(runnable, frameRate);每隔frameRate的时间,执行一次invalidate()。 canvas.save()用来保存Canvas的状态,注意在绘制结束的时候调用了canvas.restore(),成对使用。然后就开始绘制波纹了,每次的半径增量为(radiusMax * (((float) 1 * frameRate) / rippleDuration),每隔rameRate绘制一次。rippleType == 1的时候,保证在绘制到一定的时候,setAlpha的值加深,形成两次涟漪,否则setAlpha值就一直递减,波纹越大效果越弱。最后当rippleDuration <= timer * frameRate的时候,就return了,RippleEffect波纹效果结束。

       最后在XML中直接拿来用就可以了:

[html]  view plain  copy
  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:ripple="http://schemas.android.com/apk/res-auto"  
  3.     xmlns:tools="http://schemas.android.com/tools"  
  4.     android:layout_width="match_parent"  
  5.     android:layout_height="match_parent"  
  6.     android:orientation="vertical" >  
  7.   
  8.     <ScrollView  
  9.         android:layout_width="match_parent"  
  10.         android:layout_height="wrap_content" >  
  11.   
  12.         <LinearLayout  
  13.             android:layout_width="match_parent"  
  14.             android:layout_height="wrap_content"  
  15.             android:gravity="center_horizontal"  
  16.             android:orientation="vertical" >  
  17.   
  18.             <!-- 1 rv_centered="true" rv_type="simpleRipple" -->  
  19.   
  20.             <com.example.RippleEffect.RippleView  
  21.                 android:id="@+id/more"  
  22.                 android:layout_width="wrap_content"  
  23.                 android:layout_height="wrap_content"  
  24.                 android:layout_margin="5dp"  
  25.                 ripple:rv_centered="true" >  
  26.   
  27.                 <ImageView  
  28.                     android:layout_width="100dp"  
  29.                     android:layout_height="wrap_content"  
  30.                     android:layout_centerInParent="true"  
  31.                     android:background="#BAC9FF"  
  32.                     android:padding="15dp"  
  33.                     android:src="@drawable/ic_launcher" />  
  34.             </com.example.RippleEffect.RippleView>  
  35.   
  36.             <!-- 2 rv_centered="false" rv_type="simpleRipple" -->  
  37.   
  38.             <com.example.RippleEffect.RippleView  
  39.                 android:layout_width="wrap_content"  
  40.                 android:layout_height="wrap_content"  
  41.                 android:layout_margin="5dp"  
  42.                 ripple:rv_centered="false"  
  43.                 ripple:rv_type="simpleRipple" >  
  44.   
  45.                 <ImageView  
  46.                     android:layout_width="100dp"  
  47.                     android:layout_height="wrap_content"  
  48.                     android:layout_centerInParent="true"  
  49.                     android:background="#BAC9FF"  
  50.                     android:padding="15dp"  
  51.                     android:src="@drawable/ic_launcher" />  
  52.             </com.example.RippleEffect.RippleView>  
  53.   
  54.             <!-- 3 rv_type="doubleRipple" -->  
  55.   
  56.             <com.example.RippleEffect.RippleView  
  57.                 android:layout_width="wrap_content"  
  58.                 android:layout_height="wrap_content"  
  59.                 android:layout_margin="5dp"  
  60.                 ripple:rv_type="doubleRipple" >  
  61.   
  62.                 <ImageView  
  63.                     android:layout_width="100dp"  
  64.                     android:layout_height="wrap_content"  
  65.                     android:layout_centerInParent="true"  
  66.                     android:background="#BAC9FF"  
  67.                     android:padding="15dp"  
  68.                     android:src="@drawable/ic_launcher" />  
  69.             </com.example.RippleEffect.RippleView>  
  70.   
  71.             <!-- 4 rv_type="rectangle" -->  
  72.   
  73.             <com.example.RippleEffect.RippleView  
  74.                 android:layout_width="match_parent"  
  75.                 android:layout_height="wrap_content"  
  76.                 android:layout_margin="5dp"  
  77.                 ripple:rv_type="doubleRipple" >  
  78.   
  79.                 <ImageView  
  80.                     android:layout_width="match_parent"  
  81.                     android:layout_height="wrap_content"  
  82.                     android:layout_centerInParent="true"  
  83.                     android:background="#BAC9FF"  
  84.                     android:padding="15dp"  
  85.                     android:src="@drawable/ic_launcher" />  
  86.             </com.example.RippleEffect.RippleView>  
  87.   
  88.             <!-- 5  rv_zoom ="true" rv_ripplePadding ="20dp"   ripple:rv_zoomScale="1.25" -->  
  89.   
  90.             <com.example.RippleEffect.RippleView  
  91.                 android:layout_width="wrap_content"  
  92.                 android:layout_height="wrap_content"  
  93.                 android:layout_margin="5dp"  
  94.                 ripple:rv_centered="false"  
  95.                 ripple:rv_color="#D91615"  
  96.                 ripple:rv_rippleDuration="2000"  
  97.                 ripple:rv_ripplePadding="20dp"  
  98.                 ripple:rv_zoom="true"  
  99.                 ripple:rv_zoomDuration="200"  
  100.                 ripple:rv_zoomScale="1.25" >  
  101.   
  102.                 <ImageView  
  103.                     android:layout_width="100dp"  
  104.                     android:layout_height="wrap_content"  
  105.                     android:layout_centerInParent="true"  
  106.                     android:background="#BAC9FF"  
  107.                     android:padding="15dp"  
  108.                     android:src="@drawable/ic_launcher" />  
  109.             </com.example.RippleEffect.RippleView>  
  110.   
  111.             <!-- 6 rv_type="simpleRipple" rv_alpha="10" rv_framerate="100" -->  
  112.   
  113.             <com.example.RippleEffect.RippleView  
  114.                 android:layout_width="match_parent"  
  115.                 android:layout_height="wrap_content"  
  116.                 android:layout_margin="5dp"  
  117.                 ripple:rv_alpha="200"  
  118.                 ripple:rv_framerate="100"  
  119.                 ripple:rv_type="simpleRipple" >  
  120.   
  121.                 <ImageView  
  122.                     android:layout_width="match_parent"  
  123.                     android:layout_height="wrap_content"  
  124.                     android:layout_centerInParent="true"  
  125.                     android:background="#BAC9FF"  
  126.                     android:padding="15dp"  
  127.                     android:src="@drawable/ic_launcher" />  
  128.             </com.example.RippleEffect.RippleView>  
  129.   
  130.             <!-- 7 rv_type="simpleRipple" rv_alpha="10" rv_framerate="2" -->  
  131.   
  132.             <com.example.RippleEffect.RippleView  
  133.                 android:layout_width="match_parent"  
  134.                 android:layout_height="wrap_content"  
  135.                 android:layout_margin="5dp"  
  136.                 ripple:rv_alpha="200"  
  137.                 ripple:rv_framerate="2"  
  138.                 ripple:rv_type="simpleRipple" >  
  139.   
  140.                 <ImageView  
  141.                     android:layout_width="match_parent"  
  142.                     android:layout_height="wrap_content"  
  143.                     android:layout_centerInParent="true"  
  144.                     android:background="#BAC9FF"  
  145.                     android:padding="15dp"  
  146.                     android:src="@drawable/ic_launcher" />  
  147.             </com.example.RippleEffect.RippleView>  
  148.   
  149.             <!-- 8 rv_type="simpleRipple" rv_alpha="10" rv_framerate="2" -->  
  150.   
  151.             <com.example.RippleEffect.RippleView  
  152.                 android:layout_width="match_parent"  
  153.                 android:layout_height="wrap_content"  
  154.                 android:layout_margin="5dp"  
  155.                 ripple:rv_alpha="200"  
  156.                 ripple:rv_framerate="2"  
  157.                 ripple:rv_type="simpleRipple" >  
  158.   
  159.                 <Button  
  160.                     android:layout_width="match_parent"  
  161.                     android:layout_height="wrap_content"  
  162.                     android:layout_centerInParent="true"  
  163.                     android:background="#BAC9FF"  
  164.                     android:padding="15dp"  
  165.                     android:text="Button" />  
  166.             </com.example.RippleEffect.RippleView>  
  167.         </LinearLayout>  
  168.     </ScrollView>  
  169.   
  170. </LinearLayout>  

DEMO 下载地址 http://download.csdn.net/detail/yalinfendou/8803431
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值