Android RippleEffect波纹效果,重写ViewGroup
2015-06-13 15:36
507 查看
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代码:
和其它自定义View或者ViewGroup一样,构造方法中调用此方法初始化自定义属性及一些Paint,canvas等,这里也初始化了gestureDetector,是为了在长按事件中也会触发RippleEffect波纹效果。
看看attrs.xml中的自定义属性:
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方法:
在初始化的时候,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
一直觉得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中直接拿来用就可以了:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:ripple="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ScrollView android:layout_width="match_parent" android:layout_height="wrap_content" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" android:orientation="vertical" > <!-- 1 rv_centered="true" rv_type="simpleRipple" --> <com.example.RippleEffect.RippleView android:id="@+id/more" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5dp" ripple:rv_centered="true" > <ImageView android:layout_width="100dp" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#BAC9FF" android:padding="15dp" android:src="@drawable/ic_launcher" /> </com.example.RippleEffect.RippleView> <!-- 2 rv_centered="false" rv_type="simpleRipple" --> <com.example.RippleEffect.RippleView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5dp" ripple:rv_centered="false" ripple:rv_type="simpleRipple" > <ImageView android:layout_width="100dp" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#BAC9FF" android:padding="15dp" android:src="@drawable/ic_launcher" /> </com.example.RippleEffect.RippleView> <!-- 3 rv_type="doubleRipple" --> <com.example.RippleEffect.RippleView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5dp" ripple:rv_type="doubleRipple" > <ImageView android:layout_width="100dp" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#BAC9FF" android:padding="15dp" android:src="@drawable/ic_launcher" /> </com.example.RippleEffect.RippleView> <!-- 4 rv_type="rectangle" --> <com.example.RippleEffect.RippleView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" ripple:rv_type="doubleRipple" > <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#BAC9FF" android:padding="15dp" android:src="@drawable/ic_launcher" /> </com.example.RippleEffect.RippleView> <!-- 5 rv_zoom ="true" rv_ripplePadding ="20dp" ripple:rv_zoomScale="1.25" --> <com.example.RippleEffect.RippleView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="5dp" ripple:rv_centered="false" ripple:rv_color="#D91615" ripple:rv_rippleDuration="2000" ripple:rv_ripplePadding="20dp" ripple:rv_zoom="true" ripple:rv_zoomDuration="200" ripple:rv_zoomScale="1.25" > <ImageView android:layout_width="100dp" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#BAC9FF" android:padding="15dp" android:src="@drawable/ic_launcher" /> </com.example.RippleEffect.RippleView> <!-- 6 rv_type="simpleRipple" rv_alpha="10" rv_framerate="100" --> <com.example.RippleEffect.RippleView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" ripple:rv_alpha="200" ripple:rv_framerate="100" ripple:rv_type="simpleRipple" > <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#BAC9FF" android:padding="15dp" android:src="@drawable/ic_launcher" /> </com.example.RippleEffect.RippleView> <!-- 7 rv_type="simpleRipple" rv_alpha="10" rv_framerate="2" --> <com.example.RippleEffect.RippleView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" ripple:rv_alpha="200" ripple:rv_framerate="2" ripple:rv_type="simpleRipple" > <ImageView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#BAC9FF" android:padding="15dp" android:src="@drawable/ic_launcher" /> </com.example.RippleEffect.RippleView> <!-- 8 rv_type="simpleRipple" rv_alpha="10" rv_framerate="2" --> <com.example.RippleEffect.RippleView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="5dp" ripple:rv_alpha="200" ripple:rv_framerate="2" ripple:rv_type="simpleRipple" > <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:background="#BAC9FF" android:padding="15dp" android:text="Button" /> </com.example.RippleEffect.RippleView> </LinearLayout> </ScrollView> </LinearLayout>
DEMO下载地址:http://download.csdn.net/detail/yalinfendou/8803431
相关文章推荐
- android.database.sqlite.SQLiteException: table tb_diary2 has no column named recordPath 解决办法
- Android:View事件分发机制
- Android学习笔记:对Android应用进行单元测试
- Android学习笔记:对Android应用进行单元测试
- android dtmf
- 【Ubuntu】Android Studio配置
- Android使ScrollView显示到底部或任意位置
- Android学习记录<五>
- Android实战简易教程-第十六枪(LineChart实现数据趋势展示)
- 实现记住用户名和密码(加了一个引导页)
- android studio 安装启动出现 The environment variable Java_home does not point to JVm。。
- android 最下面菜单框的拉动效果
- Android学习心得(7) --- 迭代器Iterator反编译smali分析
- android-adb通用
- 如何让android的service一直在后台运行?
- Android Studio开发入门-引用jar及so文件
- Android StateMachine解析( 1 )
- AndroidStudio_local path doesn't exist解决办法
- android5.0(Lollipop) BLE Central(Scanner)牛刀小试
- mac下androidstudio下ndk开发简单操作流程