自定义view 波浪效果
实现波浪效果view,可以自定义view,也可以自定义drawable,我个人比较喜欢重写drawable,因此这里是自定义drawable实现效果,费话少说,先看效果。
这里用了两种方式实现波浪效果,一种是通过正弦函数去画路径,一种是通过三阶贝塞尔曲线画出类似正弦曲线的效果
先看看实现波浪效果需要用到的一些参数,看注释大概就能了解
- /**
- * 画布的宽
- */
- int mWidth;
- /**
- * 画布的高
- */
- int mHeight;
- /**
- * 初始偏移量
- */
- float offset = 0;
- /**
- * 线的宽度,当lineWidth>0时,是画线模式,否则是填充模式
- */
- float lineWidth = 0;
- /**
- * 显示的周期数
- */
- float period = 1;
- /**
- * 移动速度,每秒钟移动的周期数
- */
- float speedPeriod = 0.5f;
- /**
- * 波浪的振幅,单位px
- */
- float mSwing = 20;
再来看看正弦函数的实现方式
- private class WaveSin extends Wave {
- /**
- * 初始偏移量
- */
- float offRadian = 0;
- /**
- * 每个像素占的弧度
- */
- double perRadian;
- /**
- * 每秒移动的弧度数
- */
- float speedRadian;
- @Override
- public void onDraw(Canvas canvas, boolean isBottom) {
- float y = mHeight;
- mPath.reset();
- //计算路径点的初始位置
- if (lineWidth > 0) {
- y = (float) (mSwing * Math.sin(offRadian) + mSwing);
- mPath.moveTo(-lineWidth, isBottom ? mHeight - y - lineWidth / 2 : y + lineWidth / 2);
- } else {
- mPath.moveTo(0, isBottom ? 0 : mHeight);
- }
- //步长越小越精细,当然越消耗cpu性能,过大则会有锯齿
- int step = mWidth / 100 > 20 ? 20 : mWidth / 100;
- //通过正弦函数计算路径点,放入mPath中
- for (int x = 0; x <= mWidth + step; x += step) {
- y = (float) (mSwing * Math.sin(perRadian * x + offRadian) + mSwing);
- mPath.lineTo(x, isBottom ? mHeight - y - lineWidth / 2 : y + lineWidth / 2);
- }
- //填充模式时,画完完整路径
- if (lineWidth <= 0) {
- mPath.lineTo(mWidth, isBottom ? mHeight - y : y);
- mPath.lineTo(mWidth, isBottom ? 0 : mHeight);
- mPath.lineTo(0, isBottom ? 0 : mHeight);
- mPath.close();
- }
- canvas.drawPath(mPath, mPaint);
- }
- @Override
- void init() {
- perRadian = (float) (2 * Math.PI * period / mWidth);
- speedRadian = (float) (speedPeriod * Math.PI * 2);
- offRadian = (float) (offset * 2 * Math.PI);
- }
- @Override
- public void move(float delta) {
- offRadian += speedRadian * delta;
- }
- }
首先`init()`方法中,perRadian是计算每弧度所占的宽度,speedRadian计算每秒移动的弧度,offRadian是当前偏移弧度,在`move(float delta)`中可以看到delta是时间变化量,所以
`下一次的偏移量 = 当前偏移量+每秒移动的弧度*时间的变化量`,即`offRadian += speedRadian * delta;`
再来看看主要的onDraw方法,Canvas是画布,isBottom是指波浪是否在整个画布的底部。
下面是通过贝塞尔曲线实现波浪效果
- private class WaveBezier extends Wave {
- /**
- * 根据贝塞尔曲线公式计算的一个常量值
- */
- private static final double MAX_Y = 0.28867513459481287;
- /**
- * 一个周期的宽度
- */
- float periodWidth;
- /**
- * 每秒钟移动的宽度
- */
- float speedWidth;
- /**
- * 贝塞尔曲线控制点的Y轴坐标
- */
- float conY;
- /**
- * 当前偏移量
- */
- float currentOffset = 0;
- @Override
- public void onDraw(Canvas canvas, boolean isBottom) {
- mPath.reset();
- // 移动到第一个周期的起始点
- mPath.moveTo(-currentOffset, 0);
- float conX = periodWidth / 2;
- int w = (int) -currentOffset;
- for (int i = 0; i <= mWidth + currentOffset; i += periodWidth) {
- mPath.rCubicTo(conX, conY, conX, -conY, periodWidth, 0);//注意,这里用的是相对坐标
- w += periodWidth;
- }
- // 闭合路径
- if (lineWidth <= 0) {
- mPath.rLineTo(0, isBottom ? -mHeight : mHeight);
- mPath.rLineTo(-w, 0);
- mPath.close();
- }
- // 对Y轴整体偏移
- mPath.offset(0, (isBottom ? mHeight - mSwing - lineWidth / 2 : mSwing + lineWidth / 2));
- canvas.drawPath(mPath, mPaint);
- }
- @Override
- void init() {
- periodWidth = mWidth / period;
- speedWidth = speedPeriod * periodWidth;
- currentOffset = offset * periodWidth;
- conY = (float) (mSwing / MAX_Y);
- isReInit = false;
- }
- @Override
- public void move(float delta) {
- if (periodWidth <= 0) {
- isReInit = true;
- return;
- }
- currentOffset += speedWidth * delta;
- if (currentOffset < 0) {
- currentOffset += periodWidth;
- } else {
- if (currentOffset > periodWidth) {
- currentOffset -= periodWidth;
- }
- }
- }
- }
在 `init()`方法中periodWidth为单个周期宽度,speedWidth为每秒移动的宽度,currentOffset为当前偏移量,conY为控制点的Y轴坐标。
最后贴上完整代码
- package cn.sskbskdrin.wave;
- import android.animation.ValueAnimator;
- import android.graphics.Canvas;
- import android.graphics.ColorFilter;
- import android.graphics.Paint;
- import android.graphics.Path;
- import android.graphics.PixelFormat;
- import android.graphics.Rect;
- import android.graphics.drawable.Animatable;
- import android.graphics.drawable.Drawable;
- import android.view.animation.LinearInterpolator;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.Map;
- import java.util.WeakHashMap;
- /**
- * Created by sskbskdrin on 2018/4/4.
- *
- * @author sskbskdrin
- */
- public class WaveDrawable extends Drawable implements Animatable {
- private final List<Wave> list;
- private int mWidth;
- private int mHeight;
- private boolean animIsStart = false;
- private boolean isBottom = false;
- public WaveDrawable() {
- this(1);
- }
- public WaveDrawable(int count) {
- this(count, false);
- }
- public WaveDrawable(int count, boolean isSin) {
- if (count <= 0) {
- throw new IllegalArgumentException("Illegal count: " + count);
- }
- list = new ArrayList<>(count);
- for (int i = 0; i < count; i++) {
- list.add(isSin ? new WaveSin() : new WaveBezier());
- }
- }
- public void setBottom(boolean isBottom) {
- this.isBottom = isBottom;
- }
- /**
- * 设置填充的颜色
- *
- * @param color
- */
- public void setColor(int color) {
- for (Wave wave : list) {
- wave.setColor(color);
- }
- }
- /**
- * 设置填充的颜色
- *
- * @param color
- */
- public void setColor(int color, int index) {
- if (index < list.size()) {
- list.get(index).setColor(color);
- }
- }
- public void setOffset(float offset) {
- for (Wave wave : list) {
- wave.offset(offset);
- }
- }
- /**
- * 设置初始相位
- *
- * @param offset
- * @param index
- */
- public void setOffset(float offset, int index) {
- if (index < list.size()) {
- list.get(index).offset(offset);
- }
- }
- /**
- * 波浪的大小
- *
- * @param swing
- */
- public void setSwing(int swing) {
- for (Wave wave : list) {
- wave.setSwing(swing);
- }
- }
- /**
- * 波浪的大小
- *
- * @param swing
- * @param index
- */
- public void setSwing(int swing, int index) {
- checkIndex(index);
- list.get(index).setSwing(swing);
- }
- /**
- * 设置波浪流动的速度
- *
- * @param speed
- */
- public void setSpeed(float speed) {
- for (Wave wave : list) {
- wave.setSpeed(speed);
- }
- }
- /**
- * 设置波浪流动的速度
- *
- * @param speed
- */
- public void setSpeed(float speed, int index) {
- checkIndex(index);
- list.get(index).setSpeed(speed);
- }
- /**
- * 设置波浪周期数
- *
- * @param period (0,--)
- */
- public void setPeriod(float period) {
- for (Wave wave : list) {
- wave.setPeriod(period);
- }
- }
- public void setPeriod(float period, int index) {
- checkIndex(index);
- list.get(index).setPeriod(period);
- }
- private void checkIndex(int index) {
- if (index < 0 || index >= list.size()) {
- throw new IllegalArgumentException("Illegal index. list size=" + list.size() + " index=" + index);
- }
- }
- public void setLineWidth(float width) {
- for (Wave wave : list) {
- wave.setLineWidth(width);
- }
- }
- public void setLineWidth(float width, int index) {
- if (index >= 0 && index < list.size()) {
- list.get(index).setLineWidth(width);
- }
- }
- @Override
- protected void onBoundsChange(Rect bounds) {
- mWidth = bounds.width();
- mHeight = bounds.height();
- for (Wave wave : list) {
- wave.onSizeChange(mWidth, mHeight);
- }
- }
- @Override
- public void draw(Canvas canvas) {
- for (Wave wave : list) {
- if (wave.isReInit) {
- wave.init();
- wave.isReInit = false;
- }
- wave.onDraw(canvas, isBottom);
- }
- }
- @Override
- public int getIntrinsicWidth() {
- return mWidth;
- }
- @Override
- public int getIntrinsicHeight() {
- return mHeight;
- }
- private void move(float delta) {
- for (Wave wave : list) {
- wave.move(delta);
- }
- }
- @Override
- public void setAlpha(int alpha) {
- for (Wave wave : list) {
- wave.mPaint.setAlpha(alpha);
- }
- }
- @Override
- public void setColorFilter(ColorFilter cf) {
- for (Wave wave : list) {
- wave.mPaint.setColorFilter(cf);
- }
- }
- @Override
- public int getOpacity() {
- return PixelFormat.TRANSLUCENT;
- }
- @Override
- public boolean setVisible(boolean visible, boolean restart) {
- if (visible) {
- if (animIsStart) {
- AnimateListener.start(this);
- }
- } else {
- if (animIsStart) {
- AnimateListener.start(this);
- }
- }
- return super.setVisible(visible, restart);
- }
- @Override
- public void start() {
- animIsStart = true;
- AnimateListener.start(this);
- }
- @Override
- public void stop() {
- AnimateListener.cancel(this);
- animIsStart = false;
- }
- @Override
- public boolean isRunning() {
- return AnimateListener.isRunning(this);
- }
- private static class AnimateListener implements ValueAnimator.AnimatorUpdateListener {
- private static WeakHashMap<WaveDrawable, Boolean> map = new WeakHashMap<>();
- private static int lastTime = 0;
- private static ValueAnimator valueAnimator;
- private static void initAnimation() {
- valueAnimator = ValueAnimator.ofInt(0, 1000);
- valueAnimator.setDuration(1000);
- valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
- valueAnimator.setInterpolator(new LinearInterpolator());
- valueAnimator.addUpdateListener(new AnimateListener());
- }
- private static void start(WaveDrawable drawable) {
- if (!map.containsKey(drawable)) {
- map.put(drawable, true);
- }
- if (valueAnimator == null) {
- initAnimation();
- }
- if (!valueAnimator.isRunning()) {
- valueAnimator.start();
- }
- }
- private static void cancel(WaveDrawable drawable) {
- if (map.containsKey(drawable)) {
- map.put(drawable, false);
- }
- }
- private static boolean isRunning(WaveDrawable drawable) {
- return map.containsKey(drawable) && map.get(drawable);
- }
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- int current = (int) animation.getAnimatedValue();
- int delta = current - lastTime;
- if (delta < 0) {
- delta = current + 1000 - lastTime;
- }
- float deltaF = delta / 1000f;
- lastTime = current;
- if (map.size() == 0) {
- animation.cancel();
- valueAnimator = null;
- return;
- }
- for (Map.Entry<WaveDrawable, Boolean> wave : map.entrySet()) {
- if (wave != null && wave.getValue()) {
- WaveDrawable drawable = wave.getKey();
- drawable.move(deltaF);
- drawable.invalidateSelf();
- }
- }
- }
- }
- private abstract class Wave {
- /**
- * 画布的宽
- */
- int mWidth;
- /**
- * 画布的高
- */
- int mHeight;
- Path mPath = new Path();
- Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- /**
- * 初始偏移量
- */
- float offset = 0;
- /**
- * 线的宽度,当lineWidth>0时,是画线模式,否则是填充模式
- */
- float lineWidth = 0;
- /**
- * 显示的周期数
- */
- float period = 1;
- /**
- * 移动速度,每秒钟移动的周期数
- */
- float speedPeriod = 0.5f;
- /**
- * 波浪的振幅,单位px
- */
- float mSwing = 20;
- boolean isReInit = true;
- /**
- * drawable 大小改变
- *
- * @param width
- * @param height
- */
- void onSizeChange(int width, int height) {
- mWidth = width;
- mHeight = height;
- isReInit = true;
- }
- abstract void onDraw(Canvas canvas, boolean isBottom);
- abstract void init();
- /**
- * 移动的时间变化量
- *
- * @param delta
- */
- abstract void move(float delta);
- /**
- * 设置线的宽度
- *
- * @param width
- */
- void setLineWidth(float width) {
- lineWidth = width;
- if (lineWidth > 0) {
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeWidth(lineWidth);
- } else {
- mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
- }
- isReInit = true;
- }
- void setColor(int color) {
- mPaint.setColor(color);
- }
- /**
- * 每秒移动的像素数
- *
- * @param speedPeriod
- */
- void setSpeed(float speedPeriod) {
- this.speedPeriod = speedPeriod;
- isReInit = true;
- }
- /**
- * 振幅大小
- *
- * @param swing
- */
- void setSwing(float swing) {
- if (swing <= 0) {
- throw new IllegalArgumentException("Illegal swing: " + swing);
- }
- mSwing = swing;
- isReInit = true;
- }
- /**
- * 显示周期数
- *
- * @param period
- */
- void setPeriod(float period) {
- if (period <= 0) {
- throw new IllegalArgumentException("Illegal period: " + period);
- }
- this.period = period;
- isReInit = true;
- }
- /**
- * 起始偏移量
- *
- * @param offPeriod
- */
- void offset(float offPeriod) {
- this.offset = offPeriod;
- isReInit = true;
- }
- }
- private class WaveSin extends Wave {
- /**
- * 初始偏移量
- */
- float offRadian = 0;
- /**
- * 每个像素占的弧度
- */
- double perRadian;
- /**
- * 每秒移动的弧度数
- */
- float speedRadian;
- @Override
- public void onDraw(Canvas canvas, boolean isBottom) {
- float y = mHeight;
- mPath.reset();
- //计算路径点的初始位置
- if (lineWidth > 0) {
- y = (float) (mSwing * Math.sin(offRadian) + mSwing);
- mPath.moveTo(-lineWidth, isBottom ? mHeight - y - lineWidth / 2 : y + lineWidth / 2);
- } else {
- mPath.moveTo(0, isBottom ? 0 : mHeight);
- }
- //步长越小越精细,当然越消耗cpu性能,过大则会有锯齿
- int step = mWidth / 100 > 20 ? 20 : mWidth / 100;
- //通过正弦函数计算路径点,放入mPath中
- for (int x = 0; x <= mWidth + step; x += step) {
- y = (float) (mSwing * Math.sin(perRadian * x + offRadian) + mSwing);
- mPath.lineTo(x, isBottom ? mHeight - y - lineWidth / 2 : y + lineWidth / 2);
- }
- //填充模式时,画完完整路径
- if (lineWidth <= 0) {
- mPath.lineTo(mWidth, isBottom ? mHeight - y : y);
- mPath.lineTo(mWidth, isBottom ? 0 : mHeight);
- mPath.lineTo(0, isBottom ? 0 : mHeight);
- mPath.close();
- }
- canvas.drawPath(mPath, mPaint);
- }
- @Override
- void init() {
- perRadian = (float) (2 * Math.PI * period / mWidth);
- speedRadian = (float) (speedPeriod * Math.PI * 2);
- offRadian = (float) (offset * 2 * Math.PI);
- }
- @Override
- public void move(float delta) {
- offRadian += speedRadian * delta;
- }
- }
- private class WaveBezier extends Wave {
- /**
- * 根据贝塞尔曲线公式计算的一个常量值
- */
- private static final double MAX_Y = 0.28867513459481287;
- /**
- * 一个周期的宽度
- */
- float periodWidth;
- /**
- * 每秒钟移动的宽度
- */
- float speedWidth;
- /**
- * 贝塞尔曲线控制点的Y轴坐标
- */
- float conY;
- /**
- * 当前偏移量
- */
- float currentOffset = 0;
- @Override
- public void onDraw(Canvas canvas, boolean isBottom) {
- mPath.reset();
- // 移动到第一个周期的起始点
- mPath.moveTo(-currentOffset, 0);
- float conX = periodWidth / 2;
- int w = (int) -currentOffset;
- for (int i = 0; i <= mWidth + currentOffset; i += periodWidth) {
- mPath.rCubicTo(conX, conY, conX, -conY, periodWidth, 0);//注意,这里用的是相对坐标
- w += periodWidth;
- }
- // 闭合路径
- if (lineWidth <= 0) {
- mPath.rLineTo(0, isBottom ? -mHeight : mHeight);
- mPath.rLineTo(-w, 0);
- mPath.close();
- }
- // 对Y轴整体偏移
- mPath.offset(0, (isBottom ? mHeight - mSwing - lineWidth / 2 : mSwing + lineWidth / 2));
- canvas.drawPath(mPath, mPaint);
- }
- @Override
- void init() {
- periodWidth = mWidth / period;
- speedWidth = speedPeriod * periodWidth;
- currentOffset = offset * periodWidth;
- conY = (float) (mSwing / MAX_Y);
- isReInit = false;
- }
- @Override
- public void move(float delta) {
- if (periodWidth <= 0) {
- isReInit = true;
- return;
- }
- currentOffset += speedWidth * delta;
- if (currentOffset < 0) {
- currentOffset += periodWidth;
- } else {
- if (currentOffset > periodWidth) {
- currentOffset -= periodWidth;
- }
- }
- }
- }
- }
自定义view 波浪效果的更多相关文章
- Android -- 自定义view实现keep欢迎页倒计时效果
1,最近打开keep的app的时候,发现它的欢迎页面的倒计时效果还不错,所以打算自己来写写,然后就有了这篇文章. 2,还是老规矩,先看一下我们今天实现的效果 相较于我们常见的倒计时,这次实现的效果是多 ...
- 嵌套RecyclerView左右滑动替代自定义view
以前的左右滑动效果采用自定义scrollview或者linearlayout来实现,recyclerview可以很好的做这个功能,一般的需求就是要么一个独立的左右滑动效果,要么在一个列表里的中间部分一 ...
- 自定义view实现水波纹效果
水波纹效果: 1.标准正余弦水波纹: 2.非标准圆形液柱水波纹: 虽说都是水波纹,但两者在实现上差异是比较大的,一个通过正余弦函数模拟水波纹效果,另外一个会运用到图像的混合模式(PorterDuffX ...
- Android 自定义view实现水波纹效果
http://blog.csdn.net/tianjian4592/article/details/44222565 在实际的开发中,很多时候还会遇到相对比较复杂的需求,比如产品妹纸或UI妹纸在哪看了 ...
- Android自定义View之圆环交替 等待效果
学习了前面两篇的知识,对于本篇实现的效果,相信大家都不会感觉太困难,我要实现的效果是什么样呢?下面请先看效果图: 看上去是不很炫的样子,它的实现上也不是很复杂,重点在与onDraw()方法的绘制. 首 ...
- Android自定义View 画弧形,文字,并增加动画效果
一个简单的Android自定义View的demo,画弧形,文字,开启一个多线程更新ui界面,在子线程更新ui是不允许的,但是View提供了方法,让我们来了解下吧. 1.封装一个抽象的View类 B ...
- 分析自定义view的实现过程-实现雪花飞舞效果(转载有改动)
声明:本文源码出自实现雪花飞舞效果(有改动)主要通过这篇文来分析自定义view的实现过程. 没事时,比较喜欢上网看看一些新的东西,泡在网上的日子就是一个很不错的网站. 下面开始了,哈哈.^_^ 大家都 ...
- Android 自定义View修炼-自定义弹幕效果View
一.概述 现在有个很流行的效果就是弹幕效果,满屏幕的文字从右到左飘来飘去.看的眼花缭乱,看起来还蛮cool的 现在就是来实现这一的一个效果,大部分的都是从右向左移动漂移,本文的效果中也支持从左向右的漂 ...
- Android 自定义View修炼-【2014年最后的分享啦】Android实现自定义刮刮卡效果View
一.简介: 今天是2014年最后一天啦,首先在这里,我祝福大家在新的2015年都一个个的新健康,新收入,新顺利,新如意!!! 上一偏,我介绍了用Xfermode实现自定义圆角和椭圆图片view的博文& ...
随机推荐
- maven工程,java代码加载resources下面资源文件的路径
1 通过类加载器加载器, 1. URL resource = TestMain.class.getResource("/18500228040.txt");File file = ...
- Linux 内核模块编译 Makefile
驱动编译分为静态编译和动态编译:静态编译即为将驱动直接编译进内核,动态编译即为将驱动编译成模块. 而动态编译又分为两种: a -- 内部编译 在内核源码目录内编译 b -- 外部编译 在内核源码的目录 ...
- CXF整合spring
近公司需要弄webservics,还说不用框架整合(提倡使用hessian,他们既然说与操作系统有兼容问题,由于人员单薄,不得不屈服,哎),我想了老半天没弄明白他说的不用框架整合spring,尝试过直 ...
- Oracle数据库查询优化方案(处理上百万级记录如何提高处理查询速度)
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引.2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引 ...
- 记录几个经典的字符串hash算法
记录几个经典的字符串hash算法,方便以后查看: 推荐一篇文章: http://www.partow.net/programming/hashfunctions/# (1)暴雪字符串hash #inc ...
- 什么是设计思维Design Thinking——风靡全球的创造力培养方法
“把学习带到现实中,让孩子用自己的力量创造改变,可以直接提升他们的幸福感和竞争力.” 这是“全球孩童创意行动”的发起人——Kiran Sethi在TED演讲时说的一句话,这个行动旨在引导中小学生主动寻 ...
- 7-25 :active :after :before :disabled
1:<list,<datalist>,required,<select>,<option>,title,draggable,hidden 2:data-*和命 ...
- Log4j2中RollingFile的文件滚动更新机制
一.什么是RollingFile RollingFileAppender是Log4j2中的一种能够实现日志文件滚动更新(rollover)的Appender. rollover的意思是当满足一定条件( ...
- &&和&、||和|的区别
&& 和 || 为短路与 短路或&&若前面的表达式为false,整个逻辑表达式为false,所以后面的表达式无论true和false都无法影响整个表达式的逻辑结果,所以 ...
- bzoj3534 [Sdoi2014]重建
变形的$Martix-Tree$定理 发现我们要求的是$\prod_{i \in E}{p_{i}} * \prod_{i \notin E}{(1-p_{i})}$ 然后呢? 矩阵树对重边也有效对吧 ...