原地址: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代码:
- private void init(final Context context, final AttributeSet attrs) {
- if (isInEditMode())
- return;
-
- final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RippleView);
- rippleColor = typedArray.getColor(R.styleable.RippleView_rv_color, getResources().getColor(R.color.rippelColor));
- rippleType = typedArray.getInt(R.styleable.RippleView_rv_type, 0);
- hasToZoom = typedArray.getBoolean(R.styleable.RippleView_rv_zoom, false);
- isCentered = typedArray.getBoolean(R.styleable.RippleView_rv_centered, false);
- rippleDuration = typedArray.getInteger(R.styleable.RippleView_rv_rippleDuration, rippleDuration);
- frameRate = typedArray.getInteger(R.styleable.RippleView_rv_framerate, frameRate);
- rippleAlpha = typedArray.getInteger(R.styleable.RippleView_rv_alpha, rippleAlpha);
- ripplePadding = typedArray.getDimensionPixelSize(R.styleable.RippleView_rv_ripplePadding, 0);
- canvasHandler = new Handler();
- zoomScale = typedArray.getFloat(R.styleable.RippleView_rv_zoomScale, 1.03f);
- zoomDuration = typedArray.getInt(R.styleable.RippleView_rv_zoomDuration, 200);
- typedArray.recycle();
- paint = new Paint();
- paint.setAntiAlias(true);
- paint.setStyle(Paint.Style.FILL);
- paint.setColor(rippleColor);
- paint.setAlpha(rippleAlpha);
- this.setWillNotDraw(false);
-
- gestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
- @Override
- public void onLongPress(MotionEvent event) {
- super.onLongPress(event);
- animateRipple(event);
- sendClickEvent(true);
- }
-
- @Override
- public boolean onSingleTapConfirmed(MotionEvent e) {
- return true;
- }
-
- @Override
- public boolean onSingleTapUp(MotionEvent e) {
- return true;
- }
- });
-
- this.setDrawingCacheEnabled(true);
- this.setClickable(true);
- }
和其它自定义View或者ViewGroup一样,构造方法中调用此方法初始化自定义属性及一些Paint,canvas等,这里也初始化了gestureDetector,是为了在长按事件中也会触发RippleEffect波纹效果。
看看attrs.xml中的自定义属性:
-
- <declare-styleable name="RippleView">rv_zoomDuration
- <attr name="rv_alpha" format="integer" />
- <attr name="rv_framerate" format="integer"/>
- <attr name="rv_rippleDuration" format="integer"/>
- <attr name="rv_zoomDuration" format="integer" />
- <attr name="rv_color" format="color" />
- <attr name="rv_centered" format="boolean" />
- <attr name="rv_type" format="enum">
- <enum name="simpleRipple" value="0"/>
- <enum name="doubleRipple" value="1"/>
- <enum name="rectangle" value="2" />
- </attr>
- <attr name="rv_ripplePadding" format="dimension" />
- <attr name="rv_zoom" format="boolean" />
- <attr name="rv_zoomScale" format="float" />
- ;/declare-styleable>
- rv_alpha即paint.setAlpha()中设定的值,就是透明度,其取值范围是0---255,数值越小,越透明,颜色上表现越淡。
- rv_framerate是RippleEffect波纹每次半径增加相关的一个参数,值越大,每次的半径增量越大,效果看起来也就越粗糙。
- rv_rippleDuration为RippleEffect波纹的持续时间。(这里原博客写错了)
- 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方法:
- @Override
- public void draw(Canvas canvas) {
- super.draw(canvas);
- if (animationRunning) {
- if (rippleDuration <= timer * frameRate) {
- animationRunning = false;
- timer = 0;
- durationEmpty = -1;
- timerEmpty = 0;
- canvas.restore();
- invalidate();
- if (onCompletionListener != null) onCompletionListener.onComplete(this);
- return;
- } else
- canvasHandler.postDelayed(runnable, frameRate);
-
- if (timer == 0)
- canvas.save();
-
-
- canvas.drawCircle(x, y, (radiusMax * (((float) timer * frameRate) / rippleDuration)), paint);
-
- paint.setColor(Color.parseColor("#ffff4444"));
-
- if (rippleType == 1 && originBitmap != null && (((float) timer * frameRate) / rippleDuration) > 0.4f) {
- if (durationEmpty == -1)
- durationEmpty = rippleDuration - timer * frameRate;
-
- timerEmpty++;
- final Bitmap tmpBitmap = getCircleBitmap((int) ((radiusMax) * (((float) timerEmpty * frameRate) / (durationEmpty))));
- canvas.drawBitmap(tmpBitmap, 0, 0, paint);
- tmpBitmap.recycle();
- }
-
- paint.setColor(rippleColor);
-
- if (rippleType == 1) {
- if ((((float) timer * frameRate) / rippleDuration) > 0.6f)
- paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timerEmpty * frameRate) / (durationEmpty)))));
- else
- paint.setAlpha(rippleAlpha);
- }
- else
- paint.setAlpha((int) (rippleAlpha - ((rippleAlpha) * (((float) timer * frameRate) / rippleDuration))));
-
- timer++;
- }
- }
在初始化的时候,
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中直接拿来用就可以了:
DEMO
下载地址
:
http://download.csdn.net/detail/yalinfendou/8803431