自定义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); }}
以上源代码:`
源码奉上各位
- 自定义view实现水波纹效果
- 自定义view实现水波纹效果
- Android 自定义view实现水波纹效果
- android 自定义view实现水波纹效果
- 自定义view实现水波纹效果
- Android 自定义view实现水波纹效果
- Android 自定义view实现水波纹效果
- 自定义view实现水波纹效果
- 自定义view实现水波纹效果
- 自定义view实现水波纹效果
- Android 自定义view实现水波纹效果
- Android自定义View实现水波纹效果
- 自定义view实现水波纹效果
- Android利用自定义View实现水波纹效果
- Android 之自定义view实现水波纹效果
- 自定义view实现水波纹效果(已优化)
- Android自定义控件10----继承View实现水波纹效果
- 自定义view实现水波荡漾的效果
- Java中的数据类型
- UBUNTU禁止触摸板
- 【Spring】Spring基础配置-依赖注入
- Python cPickle模块用法
- oracle SQL整理全套(3)
- 自定义view实现水波纹效果
- Android里handler线程间的通信详解
- Java内存模型
- Qt第三方库QCustomPlot——认识图表的各个部分
- 适用于智能电网的组合公钥密码体制研究
- 【JZOJ4824】配对游戏
- 抽象类
- poj 1743 后缀数组+二分答案 求一个串的最长无重叠的重复出现次数最多的子串
- Tomcat介绍