android水波纹涟漪效果的实现 ---- 入门+初步提高

android水波纹涟漪效果的实现<入门+初步提高>

作为一个android开发着,水波纹效果是常见的效果,可以优化ui提高用户的交互,在android5.0之前是不会自带水波纹的,随着material design的提出水波纹不仅仅被用于btn的点击还有部分ui的跳转,让anroid界面变得比较炫酷起来;
首先今天下午没事干实现了一个水波纹的demo下面先展示一下:
android水波纹涟漪效果的实现 ---- 入门+初步提高_第1张图片

    ok下面开始进入正题,剖析实现的代码:

1

.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"
        />


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
android水波纹涟漪效果的实现 ---- 入门+初步提高_第2张图片

    最下面是一个美女的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,custom,widget)