zoukankan      html  css  js  c++  java
  • 自定义view实现水波纹效果

    今天看到一篇自定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);
        }
    
    }
    

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

  • 相关阅读:
    475.Heaters java
    爬取豆瓣新热门电影数据
    ORALCE逻辑存储结构
    UnicodeDecodeError: 'utf-8' codec can't decode byte 问题
    ORA-32004: obsolete or deprecated parameter(s) specified for RDBMS instance
    oracle和mysql区别
    ORACLE ITL事务槽
    oracle的锁种类知识普及
    仅主机、NAT、桥接模式
    oracle11g和12c区别
  • 原文地址:https://www.cnblogs.com/muyuge/p/6152113.html
Copyright © 2011-2022 走看看