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

更多相关文章

  1. Android(安卓)AVD创建及设置中各参数详解
  2. 关于解决 AVD的中文路径出现的问题和更改avd的默认路径
  3. android listview选中某一行,成选中状态颜色高亮显示,ListView的U
  4. android实现3D效果翻页
  5. Android开发之使用Preferences设计软件设置界面(源代码分享)
  6. 将Android(安卓)Studio的设置恢复到初始化
  7. Android属性动画简析
  8. ZXing生成二维码和带logo的二维码,模仿微信生成二维码效果
  9. android 新交互方式

随机推荐

  1. 我的Android(安卓)4 学习系列
  2. Android(安卓)相机实例
  3. Android: 如何利用Handler、Thread更新视
  4. Android(安卓)Spinner
  5. Android之常用adb指令
  6. Android(安卓)Zip压缩解压缩
  7. android ubuntu /windows usb device lis
  8. Android的Activity动画切换
  9. Android时间获取之——Date、String、Lon
  10. Android实现widget定时更新