android水波纹涟漪效果的实现 ---- 入门+初步提高
android水波纹涟漪效果的实现<入门+初步提高>
作为一个android开发着,水波纹效果是常见的效果,可以优化ui提高用户的交互,在android5.0之前是不会自带水波纹的,随着material design的提出水波纹不仅仅被用于btn的点击还有部分ui的跳转,让anroid界面变得比较炫酷起来;
首先今天下午没事干实现了一个水波纹的demo下面先展示一下:
ok下面开始进入正题,剖析实现的代码:
1
.MainActivity中 没有进行任何操作,只是button所在xml的一个载体<可以跳过>,看一下 activity_main.layout:
<?xml version="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" />
2
可以看到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);
3
下面看进入动画的涟漪效果的逻辑: 首先交互的过程就是点击的过程 所以需要监听点击;应为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; }
4
传参开始奠定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);
5
那么下面看关键的代码,进入动画的调度 和退出动画的调度
首先来看进入动画:
/**进入动画*/ //进入动画的进度值 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
更多相关文章
- Android(安卓)AVD创建及设置中各参数详解
- 关于解决 AVD的中文路径出现的问题和更改avd的默认路径
- android listview选中某一行,成选中状态颜色高亮显示,ListView的U
- android实现3D效果翻页
- Android开发之使用Preferences设计软件设置界面(源代码分享)
- 将Android(安卓)Studio的设置恢复到初始化
- Android属性动画简析
- ZXing生成二维码和带logo的二维码,模仿微信生成二维码效果
- android 新交互方式