自定义view实现水波纹效果

来源:互联网 发布:gta5捏脸数据女爱丽丝 编辑:程序博客网 时间:2024/05/16 11:13

今天看到一篇自定view 实现水波纹效果 觉得真心不错 学习之后再次写下笔记和心得.但是感觉原作者写得有些晦涩难懂,也许是本人愚笨 所以重写此作者教程.原作者博文大家可以去看下,感觉他在自定义view方面非常厉害,本文是基于此作者原文重新改写,拥有大量像相似部分

先看下效果吧:
1. 效果1:
这里写图片描述
2. 效果2
这里写图片描述


我先们来学习效果1:

效果1实现本质:用一张波形图和一个圆形图的图片,然后圆形图波形图上方,然后使用安卓的图片遮罩模式desIn(不懂?那么先记住有这样一个遮罩模式).(只显示上部图像和下部图像公共部分的下半部分),是不是很难懂?那么我在说清一点并且配图.假设圆形图波形图上面,那么只会显示两者相交部分的波形图
下面是解释效果图(正方形蓝色图片在黄色圆形上面):
这里写图片描述

学习此模式具体地址学习安卓图片遮罩模式

这里写图片描述

所用到波形图:
这里写图片描述

所用到圆形图:

这里写图片描述

这次的实现我们都选择继承view,在实现的过程中我们需要关注如下几个方法:

1.onMeasure():最先回调,用于控件的测量;

2.onSizeChanged():在onMeasure后面回调,可以拿到view的宽高等数据,在横竖屏切换时也会回调;

3.onDraw():真正的绘制部分,绘制的代码都写到这里面;

先来看看我们定义的变量:

    //波形图    Bitmap waveBitmap;    //圆形遮罩图    Bitmap circleBitmap;    //波形图src    Rect waveSrcRect;    //波形图dst    Rect waveDstRect;    //圆形遮罩src    Rect circleSrcRect;    //圆形遮罩dst    Rect circleDstRect;    //画笔    Paint mpaint;    //图片遮罩模式    PorterDuffXfermode mode;    //控件的宽    int viewWidth;    //控件的高    int viewHeight;    //图片过滤器    PaintFlagsDrawFilter paintFlagsDrawFilter ;    //每次移动的距离    int speek = 10 ;    //当前移动距离    int nowOffSet;

介绍一个方法:

 void android.graphics.Canvas.drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)

此方法的参数:

参数1:你的图片

参数2:矩形 .也就是说此矩形决定你画出图片参数1 的哪个位置,比如说你的矩形是设定是Rect rect= new Rect(0,0,图片宽,图片高) 那么将会画出图片全部

参数3:矩形.决定你图片缩放比例和在view中的位置.假设你的矩形Rect rect= new Rect(0,0,100,100) 那么你将在自定义view中(0,0)点到(100,100)绘画此图片并且如果图片大于(小于)此矩形那么按比例缩小(放大)

来看看 初始化方法

//初始化    private void init() {        mpaint = new Paint();        //处理图片抖动        mpaint.setDither(true);        //抗锯齿        mpaint.setAntiAlias(true);        //设置图片过滤波        mpaint.setFilterBitmap(true);        //设置图片遮罩模式        mode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);        //给画布直接设定参数        paintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.DITHER_FLAG|Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);        //初始化图片        //使用drawable获取的方式,全局只会生成一份,并且系统会进行管理,        //而BitmapFactory.decode()出来的则decode多少次生成多少张,务必自己进行recycle;        //获取波形图        waveBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.wave_2000)).getBitmap();        //获取圆形遮罩图        circleBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.circle_500)).getBitmap();        //不断刷新波形图距离 读者可以先不看这部分内容  因为需要结合其他方法        new Thread(){            public void run() {                while (true) {                    try {                        //移动波形图                        nowOffSet=nowOffSet+speek;                        //如果移动波形图的末尾那么重新来                        if (nowOffSet>=waveBitmap.getWidth()) {                            nowOffSet=0;                        }                        sleep(30);                        postInvalidate();                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }            };        }.start();    }

以下获取view的宽高并设置对应的波形图和圆形图矩形(会在onMesure回调后执行)

@Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        //获取view宽高        viewWidth = w;        viewHeight = h ;        //波形图的矩阵初始化        waveSrcRect = new Rect();        waveDstRect = new Rect(0,0,w,h);        //圆球矩阵初始化        circleSrcRect = new Rect(0,0,circleBitmap.getWidth(),circleBitmap.getHeight());        circleDstRect = new Rect(0,0,viewWidth,viewHeight);    }

那么最后来看看绘画部分吧

@Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //给图片直接设置过滤效果        canvas.setDrawFilter(paintFlagsDrawFilter);        //给图片上色        canvas.drawColor(Color.TRANSPARENT);        //添加图层 注意!!!!!使用图片遮罩模式会影响全部此图层(也就是说在canvas.restoreToCount 所有图都会受到影响)         int saveLayer = canvas.saveLayer(0,0, viewWidth,viewHeight,null, Canvas.ALL_SAVE_FLAG);        //画波形图部分 矩形        waveSrcRect.set(nowOffSet, 0, nowOffSet+viewWidth/2, viewHeight);        //画矩形        canvas.drawBitmap(waveBitmap,waveSrcRect,waveDstRect,mpaint);        //设置图片遮罩模式        mpaint.setXfermode(mode);        //画遮罩        canvas.drawBitmap(circleBitmap, circleSrcRect, circleDstRect,mpaint);        //还原画笔模式        mpaint.setXfermode(null);        //将图层放上        canvas.restoreToCount(saveLayer);    }

最后看下完整的代码

package com.fmy.shuibo1;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.PaintFlagsDrawFilter;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.Rect;import android.graphics.drawable.BitmapDrawable;import android.icu.text.TimeZoneFormat.ParseOption;import android.util.AttributeSet;import android.view.View;public class MySinUi extends View{    //波形图    Bitmap waveBitmap;    //圆形遮罩图    Bitmap circleBitmap;    //波形图src    Rect waveSrcRect;    //波形图dst    Rect waveDstRect;    //圆形遮罩src    Rect circleSrcRect;    //圆形遮罩dst    Rect circleDstRect;    //画笔    Paint mpaint;    //图片遮罩模式    PorterDuffXfermode mode;    //控件的宽    int viewWidth;    //控件的高    int viewHeight;    //图片过滤器    PaintFlagsDrawFilter paintFlagsDrawFilter ;    //每次移动的距离    int speek = 10 ;    //当前移动距离    int nowOffSet;    public MySinUi(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    //初始化    private void init() {        mpaint = new Paint();        //处理图片抖动        mpaint.setDither(true);        //抗锯齿        mpaint.setAntiAlias(true);        //设置图片过滤波        mpaint.setFilterBitmap(true);        //设置图片遮罩模式        mode = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);        //给画布直接设定参数        paintFlagsDrawFilter = new PaintFlagsDrawFilter(0, Paint.DITHER_FLAG|Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG);        //初始化图片        //使用drawable获取的方式,全局只会生成一份,并且系统会进行管理,        //而BitmapFactory.decode()出来的则decode多少次生成多少张,务必自己进行recycle;        //获取波形图        waveBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.wave_2000)).getBitmap();        //获取圆形遮罩图        circleBitmap = ((BitmapDrawable)getResources().getDrawable(R.drawable.circle_500)).getBitmap();        //不断刷新波形图距离 读者可以先不看这部分内容  因为需要结合其他方法        new Thread(){            public void run() {                while (true) {                    try {                        //移动波形图                        nowOffSet=nowOffSet+speek;                        //如果移动波形图的末尾那么重新来                        if (nowOffSet>=waveBitmap.getWidth()) {                            nowOffSet=0;                        }                        sleep(30);                        postInvalidate();                    } catch (InterruptedException e) {                        // TODO Auto-generated catch block                        e.printStackTrace();                    }                }            };        }.start();    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //给图片直接设置过滤效果        canvas.setDrawFilter(paintFlagsDrawFilter);        //给图片上色        canvas.drawColor(Color.TRANSPARENT);        //添加图层 注意!!!!!使用图片遮罩模式会影响全部此图层(也就是说在canvas.restoreToCount 所有图都会受到影响)         int saveLayer = canvas.saveLayer(0,0, viewWidth,viewHeight,null, Canvas.ALL_SAVE_FLAG);        //画波形图部分 矩形        waveSrcRect.set(nowOffSet, 0, nowOffSet+viewWidth/2, viewHeight);        //画矩形        canvas.drawBitmap(waveBitmap,waveSrcRect,waveDstRect,mpaint);        //设置图片遮罩模式        mpaint.setXfermode(mode);        //画遮罩        canvas.drawBitmap(circleBitmap, circleSrcRect, circleDstRect,mpaint);        //还原画笔模式        mpaint.setXfermode(null);        //将图层放上        canvas.restoreToCount(saveLayer);    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        //获取view宽高        viewWidth = w;        viewHeight = h ;        //波形图的矩阵初始化        waveSrcRect = new Rect();        waveDstRect = new Rect(0,0,w,h);        //圆球矩阵初始化        circleSrcRect = new Rect(0,0,circleBitmap.getWidth(),circleBitmap.getHeight());        circleDstRect = new Rect(0,0,viewWidth,viewHeight);    }}

学习效果2:

此方法实现原理:运用三角函数画出两个不同速率正弦函数图

我们先来复习三角函数吧

正余弦函数方程为:
y = Asin(wx+b)+h ,这个公式里:w影响周期,A影响振幅,h影响y位置,b为初相;

w:周期就是一个完整正弦曲线图此数值越大sin的周期越小 (cos越大)
如下图:
这里写图片描述
(原作者说我们画一个以自定义view的宽度为周期的图:意思是说你view的宽度正好可以画一个上面的图.)

这里写图片描述

A:振幅两个山峰最大的高度.如果A越大两个山峰越高和越低

h:你正弦曲线和y轴相交点.(影响正弦图初始高度的位置)

b:初相会让你图片向x轴平移

具体大家可以百度学习,我们在学编程,不是数学


为什么要两个正弦图画?好看…..
先来看看变量:

// 波纹颜色    private static final int WAVE_PAINT_COLOR = 0x880000aa;    // 第一个波纹移动的速度    private int oneSeep = 7;    // 第二个波纹移动的速度    private int twoSeep = 10;    // 第一个波纹移动速度的像素值    private int oneSeepPxil;    // 第二个波纹移动速度的像素值    private int twoSeepPxil;    // 存放原始波纹的每个y坐标点    private float wave[];    // 存放第一个波纹的每一个y坐标点    private float oneWave[];    // 存放第二个波纹的每一个y坐标点    private float twoWave[];    // 第一个波纹当前移动的距离    private int oneNowOffSet;    // 第二个波纹当前移动的    private int twoNowOffSet;    // 振幅高度    private int amplitude = 20;    // 画笔    private Paint mPaint;    // 创建画布过滤    private DrawFilter mDrawFilter;    // view的宽度    private int viewWidth;    // view高度    private int viewHeight;

画初始的波形图并且保存到数组中

// 大小改变    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        // 获取view的宽高        viewHeight = h;        viewWidth = w;        // 初始化保存波形图的数组        wave = new float[w];        oneWave = new float[w];        twoWave = new float[w];        // 设置波形图周期        float zq = (float) (Math.PI * 2 / w);        // 设置波形图的周期        for (int i = 0; i < viewWidth; i++) {            wave[i] = (float) (amplitude * Math.sin(zq * i));        }    }

初始化各种

// 初始化    private void init() {        // 创建画笔        mPaint = new Paint();        // 设置画笔颜色        mPaint.setColor(WAVE_PAINT_COLOR);        // 设置绘画风格为实线        mPaint.setStyle(Style.FILL);        // 抗锯齿        mPaint.setAntiAlias(true);        // 设置图片过滤波和抗锯齿        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);        // 第一个波的像素移动值 换算成手机像素值让其在各个手机移动速度差不多        oneSeepPxil = dpChangPx(oneSeep);        // 第二个波的像素移动值        twoSeepPxil = dpChangPx(twoSeep);    }
// 绘画方法    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        canvas.setDrawFilter(mDrawFilter);        oneNowOffSet =oneNowOffSet+oneSeepPxil;        twoNowOffSet = twoNowOffSet+twoSeepPxil;        if (oneNowOffSet>=viewWidth) {            oneNowOffSet = 0;        }        if (twoNowOffSet>=viewWidth) {            twoNowOffSet = 0;        }        //此方法会让两个保存波形图的 数组更新 头到NowOffSet变成尾部,尾部的变成头部实现动态移动        reSet();        Log.e("fmy", Arrays.toString(twoWave));        for (int i = 0; i < viewWidth; i++) {            canvas.drawLine(i, viewHeight, i, viewHeight-400-oneWave[i], mPaint);            canvas.drawLine(i, viewHeight, i, viewHeight-400-twoWave[i], mPaint);        }        postInvalidate();    }

来看看能让两个数组重置的

    public void reSet() {        // one是指 走到此处的波纹的位置 (这个理解方法看个人了)        int one = viewWidth - oneNowOffSet;        // 把未走过的波纹放到最前面 进行重新拼接        System.arraycopy(wave, oneNowOffSet, oneWave, 0, one);        // 把已走波纹放到最后        System.arraycopy(wave, 0, oneWave, one, oneNowOffSet);        // one是指 走到此处的波纹的位置 (这个理解方法看个人了)        int two = viewWidth - twoNowOffSet;        // 把未走过的波纹放到最前面 进行重新拼接        System.arraycopy(wave, twoNowOffSet, twoWave, 0, two);        // 把已走波纹放到最后        System.arraycopy(wave, 0, twoWave, two, twoNowOffSet);    }

最后大家看下完整代码

package com.exam1ple.myshuibo2;import java.util.Arrays;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.DrawFilter;import android.graphics.Paint;import android.graphics.Paint.Style;import android.graphics.PaintFlagsDrawFilter;import android.graphics.PorterDuff;import android.graphics.PorterDuffXfermode;import android.graphics.Rect;import android.graphics.drawable.BitmapDrawable;import android.icu.text.TimeZoneFormat.ParseOption;import android.util.AttributeSet;import android.util.DisplayMetrics;import android.util.Log;import android.view.View;import android.view.WindowManager;public class MyUi2 extends View {    // 波纹颜色    private static final int WAVE_PAINT_COLOR = 0x880000aa;    // 第一个波纹移动的速度    private int oneSeep = 7;    // 第二个波纹移动的速度    private int twoSeep = 10;    // 第一个波纹移动速度的像素值    private int oneSeepPxil;    // 第二个波纹移动速度的像素值    private int twoSeepPxil;    // 存放原始波纹的每个y坐标点    private float wave[];    // 存放第一个波纹的每一个y坐标点    private float oneWave[];    // 存放第二个波纹的每一个y坐标点    private float twoWave[];    // 第一个波纹当前移动的距离    private int oneNowOffSet;    // 第二个波纹当前移动的    private int twoNowOffSet;    // 振幅高度    private int amplitude = 20;    // 画笔    private Paint mPaint;    // 创建画布过滤    private DrawFilter mDrawFilter;    // view的宽度    private int viewWidth;    // view高度    private int viewHeight;    // xml布局构造方法    public MyUi2(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    // 初始化    private void init() {        // 创建画笔        mPaint = new Paint();        // 设置画笔颜色        mPaint.setColor(WAVE_PAINT_COLOR);        // 设置绘画风格为实线        mPaint.setStyle(Style.FILL);        // 抗锯齿        mPaint.setAntiAlias(true);        // 设置图片过滤波和抗锯齿        mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);        // 第一个波的像素移动值 换算成手机像素值让其在各个手机移动速度差不多        oneSeepPxil = dpChangPx(oneSeep);        // 第二个波的像素移动值        twoSeepPxil = dpChangPx(twoSeep);    }    // 绘画方法    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        canvas.setDrawFilter(mDrawFilter);        oneNowOffSet =oneNowOffSet+oneSeepPxil;        twoNowOffSet = twoNowOffSet+twoSeepPxil;        if (oneNowOffSet>=viewWidth) {            oneNowOffSet = 0;        }        if (twoNowOffSet>=viewWidth) {            twoNowOffSet = 0;        }        reSet();        Log.e("fmy", Arrays.toString(twoWave));        for (int i = 0; i < viewWidth; i++) {            canvas.drawLine(i, viewHeight, i, viewHeight-400-oneWave[i], mPaint);            canvas.drawLine(i, viewHeight, i, viewHeight-400-twoWave[i], mPaint);        }        postInvalidate();    }    public void reSet() {        // one是指 走到此处的波纹的位置 (这个理解方法看个人了)        int one = viewWidth - oneNowOffSet;        // 把未走过的波纹放到最前面 进行重新拼接        System.arraycopy(wave, oneNowOffSet, oneWave, 0, one);        // 把已走波纹放到最后        System.arraycopy(wave, 0, oneWave, one, oneNowOffSet);        // one是指 走到此处的波纹的位置 (这个理解方法看个人了)        int two = viewWidth - twoNowOffSet;        // 把未走过的波纹放到最前面 进行重新拼接        System.arraycopy(wave, twoNowOffSet, twoWave, 0, two);        // 把已走波纹放到最后        System.arraycopy(wave, 0, twoWave, two, twoNowOffSet);    }    // 大小改变    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        // 获取view的宽高        viewHeight = h;        viewWidth = w;        // 初始化保存波形图的数组        wave = new float[w];        oneWave = new float[w];        twoWave = new float[w];        // 设置波形图周期        float zq = (float) (Math.PI * 2 / w);        // 设置波形图的周期        for (int i = 0; i < viewWidth; i++) {            wave[i] = (float) (amplitude * Math.sin(zq * i));        }    }    // dp换算成px 为了让移动速度在各个分辨率的手机的都差不多    public int dpChangPx(int dp) {        DisplayMetrics metrics = new DisplayMetrics();        ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);        return (int) (metrics.density * dp + 0.5f);    }}

以上源代码:`
源码奉上各位

5 0