android水波纹涟漪效果的实现<入门+初步提高>
作为一个android开发着,水波纹效果是常见的效果,可以优化ui提高用户的交互,在android5.0之前是不会自带水波纹的,随着material design的提出水波纹不仅仅被用于btn的点击还有部分ui的跳转,让anroid界面变得比较炫酷起来;
首先今天下午没事干实现了一个水波纹的demo下面先展示一下:
ok下面开始进入正题,剖析实现的代码:
.MainActivity中 没有进行任何操作,只是button所在xml的一个载体<可以跳过>,看一下 activity_main.layout:
"1.0" encoding="utf-8"?>
"http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingTop="15dp"
android:paddingLeft="30dp"
android:paddingRight="30dp">
<com.example.houruixiang.touchripple.widget.RippleButton
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="center"
android:background="@drawable/bg_1"
/>
"10dp"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center">
<com.example.houruixiang.touchripple.widget.RippleButton
android:layout_width="150dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="click1"
android:background="@color/colorAccent"
/>
<com.example.houruixiang.touchripple.widget.RippleButton
android:layout_marginLeft="10dp"
android:layout_width="150dp"
android:layout_height="match_parent"
android:gravity="center"
android:text="click2"
android:background="@color/colorPrimary"
/>
<com.example.houruixiang.touchripple.widget.RippleButton
android:layout_marginTop="15dp"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
/>
可以看到RippleButton就是 我们要实现的自定义button,但是需要注意水波纹涟漪效果的实现是继承于drawable实现的,而rippleButton只是调用了其中的drwa()方法:
为了好理解先来看drawable的子类RippleDrawable的代码:
首先来看需要实现的方法:
public class RippleDrawable extends Drawable {
@Override
public void draw(Canvas canvas) {}
@Override
public void setAlpha(int alpha) {
mAlpha = alpha;
onColorOrAlphaChange();
}
@Override
public int getAlpha() {
return mAlpha;
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
//滤镜效果
}
@Override
public int getOpacity() {
//返回透明度
if (mAlpha == 255){
return PixelFormat.OPAQUE;
}else if (mAlpha == 0){
return PixelFormat.TRANSPARENT;
}else{
return PixelFormat.TRANSLUCENT;
}
}
介绍一下:
draw():用来完成控件的绘制
setColorFilter():实现滤镜效果
getAlpha()/setAlpha():set/get方法 设置和返回透明度
getOpacity():同样是返回透明度,就是把相关透明度转化;eg:mAlpha = 255 —->PixelFormat.OPAQUE
if (mAlpha == 255){
return PixelFormat.OPAQUE;
}else if (mAlpha == 0){
return PixelFormat.TRANSPARENT;
}else{
return PixelFormat.TRANSLUCENT;
}
首先在RippleDrawable的构造方法中完成初始化<画笔的初始化,颜色透明度的配置>,通过 用户设置的透明度*画笔的透明度 得到准确透明度;即:
if (mAlpha != 255){
int pAlpha = mPaint.getAlpha();
int realAlpha = (int) (pAlpha * (mAlpha/255f));
//设置透明度
mPaint.setAlpha(realAlpha);
}
public RippleDrawable() {
//抗锯齿的画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置抗锯齿
mPaint.setAntiAlias(true);
//设置放防抖动
mPaint.setDither(true);
setRippleColor(0x60000000);
}
public void setRippleColor(int rippleColor) {
this.rippleColor = rippleColor;
onColorOrAlphaChange();
}
private void onColorOrAlphaChange() {
//设置画笔颜色
mPaint.setColor(rippleColor);
if (mAlpha != 255){
int pAlpha = mPaint.getAlpha();
int realAlpha = (int) (pAlpha * (mAlpha/255f));
//设置透明度
mPaint.setAlpha(realAlpha);
}
invalidateSelf();
}
ok画笔颜色和透明度设置完后,来看下draw方法,顾名思义draw()就是完成绘制的过程;在这个demo中有3层bg
最下面是一个美女的bg 点击时候可以看出来 上层是一个灰色的bg ,在上面是一个灰色的圆形背景,需要注意的是上面两层bg实在自定义drawable中实现的,需要注意透明度的计算不能再交互的过程中遮挡button原有的bg
下面来看一个方法<代码中进行标注 看起来会易懂一些>:
public int getCircleAlpha(int preAlpha,int bgAlpha){
int dAlpha = preAlpha<用户设置的透明度> - bgAlpha<默认bg上层的bg>;
//根据以上的透明度得到最上层的透明度应该是多少时不会遮挡
return (int) ((dAlpha*255f)/(255f - bgAlpha));
}
根据不同透明度绘制bg<详细看代码>:
//获取用户设置的透明度 就是Z
int preAlpha = mPaint.getAlpha();
//当前背景
int bgAlpha = (int) (preAlpha * (mBgAlpha/255f));
//bg + prebg运算得到得背景
int maxCircleAlpha = getCircleAlpha(preAlpha,bgAlpha);
int circleAlpha = (int) (maxCircleAlpha * (mCircleAlpha/255f));
mPaint.setAlpha(bgAlpha);
canvas.drawColor(mPaint.getColor());
mPaint.setAlpha(circleAlpha);
canvas.drawCircle(mRipplePointX,mRipplePointY,mRippleRadius,mPaint);
//设置最初的透明度 保证下次进入运算不会出错
mPaint.setAlpha(preAlpha);
下面看进入动画的涟漪效果的逻辑:
首先交互的过程就是点击的过程 所以需要监听点击;应为drawable的子类中没有onTonchEvent的方法 所以需要通过RippleButton的onTouchEvent方法中传入参给RippleDrawable的自定义ontouch中完成down --- move --- up --- cancel:
先看RippleButton ;其中
//设置刷新接口,View中已经实现 --->源码 button继承子drawable.callback
rippleDrawable.setCallback(this);
//设置刷新区域--->源码
rippleDrawable.setBounds(0,0,getWidth(),getHeight());
@Override
protected boolean verifyDrawable(Drawable who) {
return who == rippleDrawable || super.verifyDrawable(who);
}
以上代码用来解决 RippleDrawable 不能刷新的问题 详细请看源码,之后博文会有提到这里不多说,然后继续看RippleButton的代码
/**
* Created by houruixiang on 2017/7/18.
*/
public class RippleButton extends Button {
private RippleDrawable rippleDrawable;
private Paint paint;
public RippleButton(Context context) {
this(context,null);
}
public RippleButton(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public RippleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
rippleDrawable = new RippleDrawable();
//设置刷新接口,View中已经实现 --->源码 button继承子drawable.callback
rippleDrawable.setCallback(this);
//rippleDrawable
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//设置刷新区域--->源码
rippleDrawable.setBounds(0,0,getWidth(),getHeight());
}
@Override
protected boolean verifyDrawable(Drawable who) {
return who == rippleDrawable || super.verifyDrawable(who);
}
@Override
protected void onDraw(Canvas canvas) {
rippleDrawable.draw(canvas);
super.onDraw(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
rippleDrawable.onTouch(event);
return true;
}
}
在RippleButton的:
@Override
public boolean onTouchEvent(MotionEvent event) {
rippleDrawable.onTouch(event);
return true;
}
传参开始奠定RippleDrawble的点击监听:
public void onTouch(MotionEvent event){
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
//按下
mClickPointX = event.getX();
mClickPointY = event.getY();
onTouchDown(mClickPointX, mClickPointY);
break;
case MotionEvent.ACTION_MOVE:
//移动
//onTouchMove(moveX,moveY);
break;
case MotionEvent.ACTION_UP:
//抬起
onTouchUp();
break;
case MotionEvent.ACTION_CANCEL:
//退出
//onTouchCancel();
break;
}
}
public void onTouchDown(float x,float y){
//Log.i("onTouchDown====",x + "" + y );
//unscheduleSelf(runnable);
mUpDone = false;
mRipplePointX = x;
mRipplePointY = y;
mRippleRadius = 0;
startEnterRunnable();
}
public void onTouchMove(float x,float y){
}
public void onTouchUp(){
mUpDone = true;
if (mEnterDone){
startExitRunnable();
}
}
public void onTouchCancel(){
mUpDone = true;
if (mEnterDone){
startExitRunnable();
}
}
可以看到在ontouchDown中提到startEnterRunnable(),在onTouchUp和onTouchCancel中有startExitRunnable();那么接着看着两个方法:
/**
* 开启进入动画
* */
public void startEnterRunnable(){
mProgress = 0;
//mEnterDone = false;
unscheduleSelf(exitRunnable);
unscheduleSelf(runnable);
scheduleSelf(runnable,SystemClock.uptimeMillis());
}
/**
* 开启退出动画
* */
public void startExitRunnable(){
mExitProgress = 0;
unscheduleSelf(runnable);
unscheduleSelf(exitRunnable);
scheduleSelf(exitRunnable,SystemClock.uptimeMillis());
}
其中drawable中有handler机制来进出队列完成绘制:而一下方法就是分别开始调度和终止调度:
unscheduleSelf(runnable);
unscheduleSelf(exitRunnable);
那么下面看关键的代码,进入动画的调度 和退出动画的调度
首先来看进入动画:
/**进入动画*/
//进入动画的进度值
private float mProgress;
//每次递增的时间
private float mEnterIncrement = 16f/360;
//进入动画添加插值器
private DecelerateInterpolator mEnterInterpolator = new DecelerateInterpolator(2);
private Runnable runnable = new Runnable() {
@Override
public void run() {
mEnterDone = false;
mCircleAlpha = 255;
mProgress = mProgress + mEnterIncrement;
if (mProgress > 1){
onEnterPrograss(1);
enterDone();
return;
}
float interpolation = mEnterInterpolator.getInterpolation(mProgress);
onEnterPrograss(interpolation);
scheduleSelf(this, SystemClock.uptimeMillis() + 16);
}
};
/**进入动画刷新的方法
* @parms realProgress */
public void onEnterPrograss(float realPrograss){
mRippleRadius = getCenter(startRadius,endRadius,realPrograss);
mRipplePointX = getCenter(mClickPointX,mCenterPointX,realPrograss);
mRipplePointY = getCenter(mClickPointY,mCenterPointY,realPrograss);
mBgAlpha = (int) getCenter(0,182,realPrograss);
invalidateSelf();
}
private void enterDone() {
mEnterDone = true;
if(mUpDone)
startExitRunnable();
}
然后在看退出动画的调度:
/**退出动画*/
//退出动画的进度值
private float mExitProgress;
//每次递增的时间
private float mExitIncrement = 16f/280;
//退出动画添加插值器
private AccelerateInterpolator mExitInterpolator = new AccelerateInterpolator(2);
private Runnable exitRunnable = new Runnable() {
@Override
public void run() {
if (!mEnterDone){
return;
}
mExitProgress = mExitProgress + mExitIncrement;
if (mExitProgress > 1){
onExitPrograss(1);
exitDone();
return;
}
float interpolation = mExitInterpolator.getInterpolation(mExitProgress);
onExitPrograss(interpolation);
scheduleSelf(this, SystemClock.uptimeMillis() + 16);
}
};
/**退出动画刷新的方法
* @parms realProgress */
public void onExitPrograss(float realPrograss){
//设置背景
mBgAlpha = (int) getCenter(182,0,realPrograss);
//设置圆形区域
mCircleAlpha = (int) getCenter(255,0,realPrograss);
invalidateSelf();
}
private void exitDone() {
mEnterDone = false;
}
ok~基本的讲解已经完毕 下面展示完整代码:
RippleButton:
package com.example.houruixiang.touchripple.widget;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.example.houruixiang.touchripple.R;
/**
* Created by houruixiang on 2017/7/18.
*/
public class RippleButton extends Button {
private RippleDrawable rippleDrawable;
private Paint paint;
public RippleButton(Context context) {
this(context,null);
}
public RippleButton(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public RippleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
rippleDrawable = new RippleDrawable();
//设置刷新接口,View中已经实现 --->源码 button继承子drawable.callback
rippleDrawable.setCallback(this);
//rippleDrawable
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//设置刷新区域--->源码
rippleDrawable.setBounds(0,0,getWidth(),getHeight());
}
@Override
protected boolean verifyDrawable(Drawable who) {
return who == rippleDrawable || super.verifyDrawable(who);
}
@Override
protected void onDraw(Canvas canvas) {
rippleDrawable.draw(canvas);
super.onDraw(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
rippleDrawable.onTouch(event);
return true;
}
}
RippleDrawable:
package com.example.houruixiang.touchripple.widget;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Interpolator;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.SystemClock;
import android.util.Log;
import android.view.MotionEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import java.security.PrivilegedExceptionAction;
import java.util.Timer;
import java.util.TimerTask;
/**
* Created by houruixiang on 2017/7/18.
*/
public class RippleDrawable extends Drawable {
private Paint mPaint;
private Bitmap bitmap;
private int rippleColor;
private float mRipplePointX = 0;
private float mRipplePointY = 0;
private float mRippleRadius = 0;
private int mAlpha = 200;
private float mCenterPointX,mCenterPointY;
private float mClickPointX;
private float mClickPointY;
//最大半径
private float MaxRadius;
//开始半径
private float startRadius;
//结束半径
private float endRadius;
//记录是否抬起手--->boolean
private boolean mUpDone;
//记录进入动画是否完毕
private boolean mEnterDone;
/**进入动画*/
//进入动画的进度值
private float mProgress;
//每次递增的时间
private float mEnterIncrement = 16f/360;
//进入动画添加插值器
private DecelerateInterpolator mEnterInterpolator = new DecelerateInterpolator(2);
private Runnable runnable = new Runnable() {
@Override
public void run() {
mEnterDone = false;
mCircleAlpha = 255;
mProgress = mProgress + mEnterIncrement;
if (mProgress > 1){
onEnterPrograss(1);
enterDone();
return;
}
float interpolation = mEnterInterpolator.getInterpolation(mProgress);
onEnterPrograss(interpolation);
scheduleSelf(this, SystemClock.uptimeMillis() + 16);
}
};
/**进入动画刷新的方法
* @parms realProgress */
public void onEnterPrograss(float realPrograss){
mRippleRadius = getCenter(startRadius,endRadius,realPrograss);
mRipplePointX = getCenter(mClickPointX,mCenterPointX,realPrograss);
mRipplePointY = getCenter(mClickPointY,mCenterPointY,realPrograss);
mBgAlpha = (int) getCenter(0,182,realPrograss);
invalidateSelf();
}
private void enterDone() {
mEnterDone = true;
if(mUpDone)
startExitRunnable();
}
/**退出动画*/
//退出动画的进度值
private float mExitProgress;
//每次递增的时间
private float mExitIncrement = 16f/280;
//退出动画添加插值器
private AccelerateInterpolator mExitInterpolator = new AccelerateInterpolator(2);
private Runnable exitRunnable = new Runnable() {
@Override
public void run() {
if (!mEnterDone){
return;
}
mExitProgress = mExitProgress + mExitIncrement;
if (mExitProgress > 1){
onExitPrograss(1);
exitDone();
return;
}
float interpolation = mExitInterpolator.getInterpolation(mExitProgress);
onExitPrograss(interpolation);
scheduleSelf(this, SystemClock.uptimeMillis() + 16);
}
};
/**退出动画刷新的方法
* @parms realProgress */
public void onExitPrograss(float realPrograss){
//设置背景
mBgAlpha = (int) getCenter(182,0,realPrograss);
//设置圆形区域
mCircleAlpha = (int) getCenter(255,0,realPrograss);
invalidateSelf();
}
private void exitDone() {
mEnterDone = false;
}
//设置渐变效果 包括半径/bg color/圆心位置等
public float getCenter(float start,float end,float prograss){
return start + (end - start)*prograss;
}
public RippleDrawable() {
//抗锯齿的画笔
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//设置抗锯齿
mPaint.setAntiAlias(true);
//设置放防抖动
mPaint.setDither(true);
setRippleColor(0x60000000);
}
//private int mPaintAlpha = 255;
//背景的透明度
private int mBgAlpha;
//圆形区域的透明度
private int mCircleAlpha;
@Override
public void draw(Canvas canvas) {
//获取用户设置的透明度 就是Z
int preAlpha = mPaint.getAlpha();
//当前背景
int bgAlpha = (int) (preAlpha * (mBgAlpha/255f));
//bg + prebg运算得到得背景
int maxCircleAlpha = getCircleAlpha(preAlpha,bgAlpha);
int circleAlpha = (int) (maxCircleAlpha * (mCircleAlpha/255f));
mPaint.setAlpha(bgAlpha);
canvas.drawColor(mPaint.getColor());
mPaint.setAlpha(circleAlpha);
canvas.drawCircle(mRipplePointX,mRipplePointY,mRippleRadius,mPaint);
//设置最初的透明度 保证下次进入运算不会出错
mPaint.setAlpha(preAlpha);
}
public int getCircleAlpha(int preAlpha,int bgAlpha){
int dAlpha = preAlpha - bgAlpha;
return (int) ((dAlpha*255f)/(255f - bgAlpha));
}
public void onTouch(MotionEvent event){
switch (event.getActionMasked()){
case MotionEvent.ACTION_DOWN:
//按下
mClickPointX = event.getX();
mClickPointY = event.getY();
onTouchDown(mClickPointX, mClickPointY);
break;
case MotionEvent.ACTION_MOVE:
//移动
//onTouchMove(moveX,moveY);
break;
case MotionEvent.ACTION_UP:
//抬起
onTouchUp();
break;
case MotionEvent.ACTION_CANCEL:
//退出
//onTouchCancel();
break;
}
}
public void onTouchDown(float x,float y){
//Log.i("onTouchDown====",x + "" + y );
//unscheduleSelf(runnable);
mUpDone = false;
mRipplePointX = x;
mRipplePointY = y;
mRippleRadius = 0;
startEnterRunnable();
}
public void onTouchMove(float x,float y){
}
public void onTouchUp(){
mUpDone = true;
if (mEnterDone){
startExitRunnable();
}
}
public void onTouchCancel(){
mUpDone = true;
if (mEnterDone){
startExitRunnable();
}
}
/**
* 开启进入动画
* */
public void startEnterRunnable(){
mProgress = 0;
//mEnterDone = false;
unscheduleSelf(exitRunnable);
unscheduleSelf(runnable);
scheduleSelf(runnable,SystemClock.uptimeMillis());
}
/**
* 开启退出动画
* */
public void startExitRunnable(){
mExitProgress = 0;
unscheduleSelf(runnable);
unscheduleSelf(exitRunnable);
scheduleSelf(exitRunnable,SystemClock.uptimeMillis());
}
public int changeColorAlpha(int color,int alpha){
//设置透明度
int a = (color >> 24) & 0xFF;
a = a * alpha/255;
int red = Color.red(color);
int green = Color.green(color);
int blue = Color.blue(color);
int argb = Color.argb(a, red, green, blue);
return argb;
}
//取所绘制区域的中心点
@Override
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
mCenterPointX = bounds.centerX();
mCenterPointY = bounds.centerY();
MaxRadius = Math.max(mCenterPointX,mCenterPointY);
startRadius = MaxRadius * 0.1f;
endRadius = MaxRadius * 0.8f;
}
@Override
public void setAlpha(int alpha) {
mAlpha = alpha;
onColorOrAlphaChange();
}
@Override
public int getAlpha() {
return mAlpha;
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
//滤镜效果
if (mPaint.getColorFilter() != colorFilter){
mPaint.setColorFilter(colorFilter);
invalidateSelf();
}
}
@Override
public int getOpacity() {
//返回透明度
if (mAlpha == 255){
return PixelFormat.OPAQUE;
}else if (mAlpha == 0){
return PixelFormat.TRANSPARENT;
}else{
return PixelFormat.TRANSLUCENT;
}
}
public void setRippleColor(int rippleColor) {
this.rippleColor = rippleColor;
onColorOrAlphaChange();
}
private void onColorOrAlphaChange() {
//设置画笔颜色
mPaint.setColor(rippleColor);
if (mAlpha != 255){
int pAlpha = mPaint.getAlpha();
int realAlpha = (int) (pAlpha * (mAlpha/255f));
//设置透明度
mPaint.setAlpha(realAlpha);
}
invalidateSelf();
}
}
简单水波纹实现是不是很炫酷,兼容android Api 实现水波纹涟漪的Ui,有没有心动.
demo下载地址:https://github.com/SoulLines/Ripple-master