自定义view实现水波纹效果
Posted 不会写代码的丝丽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义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);
}
}
以上源代码:`
源码奉上各位
以上是关于自定义view实现水波纹效果的主要内容,如果未能解决你的问题,请参考以下文章
Android自定义View——实现水波纹效果类似剩余流量球
Android 使用Kotlin来实现水波纹的自定义View
Android 使用Kotlin来实现水波纹的自定义View