自定义View-仿波浪扩散效果

老规矩,先看效果图吧


device-2018-12-08-162806.gif

View效果是在巨人肩膀上改进的,源自:https://blog.csdn.net/q1242027878/article/details/73832074?utm_source=blogxgwz7

说一下修改的地方:
1.新增圆心的图片绘制,图片设置属性app:imageSrc="";
2.修复点击事件无效的bug,原因在于其checkIsInCircle()方法判断是否点击在此view中心圆圈内逻辑不对;
3.修复原view画波浪扩散圈第一个会最后一个会重叠问题(仔细观察原view的动态图会发现某个圈重叠显得更粗),原因在与其计算绘制间隔位置有错
4.新增从中间实心圈位置边缘开始绘制扩散波纹,并且在第一个和第二个波浪间实现淡出效果,否则看起来第一个波浪出现比较生硬

下面开始贴代码:

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.theaty.wavetest.R;

import java.util.Timer;
import java.util.TimerTask;

public class WaveView extends View {

    private static final String TAG = "ICE";
    private Paint mPaint;
    private int mRadius;//里面圆圈的半径
    private Context mContext;
    private int mWidth;//控件的宽度
    /**
     * 设置波浪圆圈的宽度
     */
    private int mStrokeWidth = 2;
    /**
     * 中心圆填充颜色
     */
    private int mFillColor;
    /**
     * 最中间的圆的边框颜色
     */
    private int mCircleStrokeColor;
    /**
     * 字体大小
     */
    private int mTextSize = 30;
    
    /**
     * 中间图片padding值大小,即中心图片距离四周的距离
     */
    private int mImagePadding = 50;
    /**
     * 中间图片的id
     */
    private int mImageSrc = 0;
    /**
     * 多个扩散波浪间隙大小
     */
    private int gapSize;
    /**
     * 第一个扩散波浪的半径
     */
    private int firstRadius;
    /**
     * 控制生成扩散波浪的数量
     */
    private int numberOfCircle = 4;
    /**
     * 扩散波浪颜色
     */
    private int mLineColor;
  /**
     *绘制的文字
     */
    private String mText;
    /**
     * 中间文字颜色
     */
    private int mTextColor;
    private Paint mTextPaint;
    private boolean isFirstTime = true;
    //点击事件监听器
    private float mDownX, mDownY;
    private OnClickListener mClickListener;

/**
*是否绘制图片
**/
    private boolean isShowBitmap = true;


    /**
     * 设置波浪扩散的速度,单位毫秒,值越小扩散越快
     */
    private int period = 100;

    public WaveView(Context context) {
        this(context, null);
    }

    public WaveView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.WaveView);
        mText = ta.getString(R.styleable.WaveView_text);
        if (mText == null) mText = "";
        mTextColor = ta.getColor(R.styleable.WaveView_textColor, Color.BLACK);
        mTextSize = ta.getDimensionPixelSize(R.styleable.WaveView_textSize, mTextSize);
        mFillColor = ta.getColor(R.styleable.WaveView_fillColor, Color.WHITE);
        mLineColor = ta.getColor(R.styleable.WaveView_waveColor, Color.BLACK);
        mCircleStrokeColor = ta.getColor(R.styleable.WaveView_strokeColor, mFillColor);
        mImagePadding = ta.getDimensionPixelSize(R.styleable.WaveView_imagePadding, mImagePadding);
        mImageSrc = ta.getResourceId(R.styleable.WaveView_imageSrc, R.drawable.ic_scan);
        ta.recycle();  //注意回收

        init(context);
    }

    private void init(Context context) {
        mContext = context;

        mWidth = dip2px(50);
        mStrokeWidth = 2;

        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStyle(Paint.Style.STROKE);

        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setStrokeCap(Paint.Cap.ROUND);
        mTextPaint.setStyle(Paint.Style.STROKE);
        mTextPaint.setTextSize(mTextSize);
        mTextPaint.setColor(mTextColor);
        numberOfCircle = 4;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        switch (widthMode) {
            case MeasureSpec.EXACTLY:
                //match_parent 或者 精确的数值
                mWidth = width;
                break;
        }
        switch (heightMode) {
            case MeasureSpec.EXACTLY:
                mWidth = Math.min(mWidth, height);
                break;
        }
        mRadius = mWidth / numberOfCircle;
        gapSize = (mWidth / 2 - mRadius) / numberOfCircle;
        firstRadius = mRadius;// + gapSize;
        setMeasuredDimension(mWidth, mWidth);

    }

    //将图片按比例缩放
    private Bitmap scaleBitmap(int total, Bitmap bitmap) {
        int width = total;
        //一定要强转成float 不然有可能由于精度不够 出现 scale为0 的错误
        float scale = (float) width / (float) bitmap.getWidth();
        Matrix matrix = new Matrix();
        matrix.postScale(scale, scale);
        return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.translate(mWidth / 2, mWidth / 2);//平移

        //画中间的实体圆
        mPaint.setAlpha(255);
        mPaint.setColor(mFillColor);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawCircle(0, 0, mRadius, mPaint);

        //画圆的边(这是中间实体圆圈的描边,不需要可以去掉此绘制步骤)
        mPaint.setStrokeWidth(mStrokeWidth);
        mPaint.setColor(mCircleStrokeColor);
        mPaint.setStyle(Paint.Style.STROKE);
        canvas.drawCircle(0, 0, mRadius, mPaint);

        if (!isShowBitmap) {
            //画文字
            Rect rect = new Rect();//文字的区域
            mTextPaint.getTextBounds(mText, 0, mText.length(), rect);
            int height = rect.height();
            int width = rect.width();
            canvas.drawText(mText, -width / 2, height / 2, mTextPaint);
        } else {
            //画中间的图片
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), mImageSrc);
            //这样缩放仅仅是确认中间的图片的宽高大小位置,如果再使用这图片会失真,所以画的时候还是用原图
            bitmap = scaleBitmap(mRadius - mImagePadding, bitmap);
            int bWidth = bitmap.getWidth();
            int bHeight = bitmap.getHeight();
            canvas.drawBitmap(BitmapFactory.decodeResource(getResources(), mImageSrc), null, new RectF(-bWidth, -bHeight, bWidth, bHeight), mPaint);
        }


        //画周围的波浪
        firstRadius += 3;//每次刷新半径增加3像素
        firstRadius %= (mWidth / 2);//控制在控件的范围中
        if (firstRadius < mRadius) isFirstTime = false;
        firstRadius = checkRadius(firstRadius);//检查半径的范围
        mPaint.setColor(mLineColor);
        mPaint.setStyle(Paint.Style.STROKE);


        //画波浪
        for (int i = 0; i < numberOfCircle; i++) {
            //控制外部最大圆半径只有最大宽度的一半
            int radius = (firstRadius + i * gapSize) % (mWidth / 2);
            if (isFirstTime && radius > firstRadius) {
                continue;
            }
            //检查半径的范围
            radius = checkRadius(radius);
            //用半径来计算透明度  半径越大  越透明
            double x = 1.0;
            if (radius > mRadius + gapSize) {
                //后面圆的透明度是淡出
                x = (mWidth / 2 - radius) * 1.0 / (mWidth / 2 - mRadius);
            } else {
                //第一个圆的透明度是淡入效果
                x = (radius - mRadius * 1.0) / gapSize;
            }
            //255*0.8表示最白的时候都是80%
            mPaint.setAlpha((int) (255 * 0.8 * x));
            canvas.drawCircle(0, 0, radius, mPaint);


        }
    }

    //检查波浪的半径  如果小于圆圈,那么加上圆圈的半径
    private int checkRadius(int radius) {
        if (radius < mRadius) {
            return radius + mRadius;// + gapSize;
        }
        return radius;
    }

    public int dip2px(float dpValue) {
        final float scale = mContext.getResources().getDisplayMetrics().density;
        return (int) (dpValue * scale + 0.5f);
    }

    public void startAnimation() {

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                postInvalidate();
            }
        }, 0, period);

    }
    

    /**
     * 设置最中间的圆圈的颜色
     *
     * @param mCircleStrokeColor
     */
    public void setmCircleStrokeColor(int mCircleStrokeColor) {
        this.mCircleStrokeColor = mCircleStrokeColor;
    }

    @Override
    public void setOnClickListener(OnClickListener l) {
        mClickListener = l;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {

            case MotionEvent.ACTION_DOWN:
                mDownX = event.getX();
                mDownY = event.getY();
                return checkIsInCircle((int) mDownX, (int) mDownY);

            case MotionEvent.ACTION_UP:
                int upX = (int) event.getX(), upY = (int) event.getY();
                if (checkIsInCircle(upX, upY) && mClickListener != null) {
                    mClickListener.onClick(this);
                }
                break;

        }
        return true;
    }

    /**
     * 检查点x,y是否落在圆圈内
     *
     * @param x
     * @param y
     * @return
     */
    private boolean checkIsInCircle(int x, int y) {

        int centerX = (getRight() - getLeft()) / 2;
        int centerY = (getBottom() - getTop()) / 2;
        boolean b = Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2) < Math.pow(mRadius, 2);
        return b;
    }


    public void setText(String text) {
        this.mText = text;
        invalidate();
    }

    public int getTextColor() {
        return mTextColor;
    }

    public void setTextColor(int mTextColor) {
        this.mTextColor = mTextColor;
    }

    public String getText() {
        return mText;
    }


    public int getLineColor() {
        return mLineColor;
    }

    public void setLineColor(int mLineColor) {
        this.mLineColor = mLineColor;
    }

    public int getGapSize() {
        return gapSize;
    }

    public void setGapSize(int gapSize) {
        this.gapSize = gapSize;
    }

    public int getFillColor() {
        return mFillColor;
    }

    public void setFillColor(int mFillColor) {
        this.mFillColor = mFillColor;
    }

    public int getTextSize() {
        return mTextSize;
    }

    public void setTextSize(int mTextSize) {
        this.mTextSize = mTextSize;
    }

    public int getStrokeWidth() {
        return mStrokeWidth;
    }

    public void setStrokeWidth(int mStrokeWidth) {
        this.mStrokeWidth = mStrokeWidth;
    }


    public int getPeriod() {
        return this.period;
    }

    public void setPeriod(int period) {
        this.period = period;
    }

    public int getmCircleStrokeColor() {
        return this.mCircleStrokeColor;
    }

    public int getmImagePadding() {
        return this.mImagePadding;
    }

    public void setmImagePadding(int mImagePadding) {
        this.mImagePadding = mImagePadding;
    }
}

attrs.xml属性文件:

<?xml version="1.0" encoding="utf-8"?>
<resources>
        <declare-styleable name="WaveView">
        <attr name="textSize" ></attr>
        <attr name="text"></attr>
        <attr name="textColor"></attr>
        <attr name="fillColor" format="color"/>
        <attr name="waveColor" format="color"/>
        <attr name="strokeColor" format="color"/>
        <attr name="imagePadding" format="dimension"/>
        <attr name="imageSrc" format="dimension"/>
    </declare-styleable>
</resources>

在activity中的使用:
布局文件:

 <com.theaty.wavetest.view.WaveView
            android:id="@+id/wave"
            android:layout_width="250dp"
            android:layout_height="250dp"
            app:imagePadding="25dp"
           app:fillColor="#FFF"
            app:strokeColor="@color/colorMain55"
            app:text="点我匹配"
            app:textColor="@color/colorAccent"
            app:textSize="18sp"
            app:imageSrc="@drawable/avatar"
            app:waveColor="@color/colorAccent" />

Activtiy:

  WaveView waveView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        waveView = findViewById(R.id.wave);
        waveView.setPeriod(120);
        waveView.setTextSize(waveView.dip2px(18));
        waveView.setStrokeWidth(1);
        waveView.setTextColor(getResources().getColor(R.color.colorPrimary));
        waveView.setmCircleStrokeColor(getResources().getColor(R.color.white));
        waveView.setLineColor(getResources().getColor(R.color.white));
        waveView.setFillColor(getResources().getColor(R.color.colorMain55));
        waveView.setGapSize(30);
        waveView.startAnimation();
        waveView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "点击了wave", Toast.LENGTH_SHORT).show();
            }
        });
}

代码理解也不难,有问题可以留言一起交流交流, ^_^

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,219评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,363评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,933评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,020评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,400评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,640评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,896评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,597评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,327评论 1 244
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,581评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,072评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,399评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,054评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,849评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,672评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,585评论 2 270

推荐阅读更多精彩内容

  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,476评论 2 59
  • 有的时候,你觉得自己特别忙,但是还被抱怨效率低?或许这篇文字可以帮到你。 懂得去拒绝一些无意义的或是和你无关的工作...
    Andy呆鹿阅读 355评论 0 2
  • 其实我一直都觉得自己是个挺幸运的女孩,幸运可以遇到一个毫无保留去爱我的人,一个愿意掏空自己去成全我的人。他教会要努...
    听听别人的故事阅读 162评论 0 0
  • 对 如题目 我想和你聊一聊 至于聊一些什么 我也没有具体的方向 于我对你而言 我不知道是一种怎样的存在 但是我知道...
    小爽菇凉阅读 1,155评论 0 0
  • 我看完书喜欢写读书笔记,发公众号、发朋友圈。不免有人疑惑,为什么要费神写文章?这不是多此一举、自找麻烦吗? 更何况...
    舟半程阅读 163评论 0 0