Android自定义WaveView实现波浪进度效果
发布时间 - 2026-01-10 22:15:24 点击率:次实现原理

首先就是自定义个WaveView 继承View,然后再WaveView 内部实现代码逻辑:
① 水波就波嘛? sin函数? 贝塞尔曲线? 都行,这里就用二阶贝塞 尔曲线去画吧
② 波要动嘛,怎么动呢?线程? 好吧 这里用了个Handler。
③绘制波首先要找点,那么在onMeasure()里找出需要的点咯,这里就暂时展示一个波段吧,一个波长移动左边不就没了?OK 那就两个波吧,吼吼,两个波(猥琐男潜质表露无遗啊)。接下来就是Handler 结合 onDraw()绘制。OK,那就先看我Word绘制的粗瘪的波动图,请看VCR,oh,no... gif
意思就是波平移一个波长之后回到初始位置继续平移循环。
好吧,有人说了,这么简单的逻辑你要啰嗦那么多???
好吧,我承认,我有唐僧的潜质。。。
闲话就不说了,先上
效果图
示例代码如下
调用的Activity
* Created by LiuDong on 2016/12/22.
* Email:15002102128@126.com
*/
public class WaveActivity extends Activity {
LD_WaveView waveView;//方形
LD_WaveView waveCircleView;//圆形
private int progrees=0;//进度
private Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
if (progrees==100) progrees=0;
Log.i("progress",progrees+"");
waveView.setmProgress(progrees++);
waveCircleView.setmProgress(progrees++);
mHandler.sendEmptyMessageDelayed(0,100);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wave);
waveView= (LD_WaveView) findViewById(R.id.waveView);
waveCircleView= (LD_WaveView) findViewById(R.id.waveViewCircle);
mHandler.sendEmptyMessageDelayed(0,10);
}
}
xml布局
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:orientation="vertical" android:layout_width="match_parent" android:background="@color/ld_White" android:layout_height="match_parent"> <com.dadong.ld_tools.widget.LD_WaveView android:id="@+id/waveViewCircle" android:layout_marginTop="20dp" android:layout_width="100dp" android:layout_centerHorizontal="true" android:layout_height="100dp" app:wave_color="@color/ld_Black" app:wave_circle="true" /> <com.dadong.ld_tools.widget.LD_WaveView android:id="@+id/waveView" android:layout_width="100dp" android:layout_height="100dp" app:wave_color="@color/ld_Black" app:wave_circle="false" android:layout_centerInParent="true" /> </RelativeLayout>
自定义WaveView
/**
* Created by LiuDong on 2016/12/23.
* Email:15002102128@126.com
*/
public class LD_WaveView extends View {
private int mProgress;//进度
private int mTimeStep = 10;//时间间隔
private int mSpeed = 5;//波单次移动的距离
private int mViewHeight;//视图宽高
private int mViewWidth;//视图宽度
private int mLevelLine;// 基准线
private int mWaveLength;//波长 暂定view宽度为一个波长
private int mStrokeWidth;//园的线宽
private RectF rectF;//圆环区域
private int mWaveHeight;//波峰高度
private int mLeftWaveMoveLength;//波平移的距离,用来控制波的起点位置
private int mWaveColor;//波的颜色
private Paint mPaint;//画笔
private Paint mCirclePaint;//圆环画笔
private Paint mBorderPaint;//边界画笔
private int mBorderWidth=4;//边界宽度
private Paint mTextPaint;//文字画笔
private Path mPath;//绘画线
private List<Point> mPoints;//点的集合
private boolean isMeasure = false;//是否已测量过
private boolean isCircle=false;//是否圆形默认false,可属性代码设置
//处理消息
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
initWaveMove();
}
};
/**
* 初始化波的移动
*/
private void initWaveMove(){
mLeftWaveMoveLength+=mSpeed;//波向右移动距离增加mSpeed;
if (mLeftWaveMoveLength>=mWaveLength){//当增加到一个波长时回复到0
mLeftWaveMoveLength=0;
}
invalidate();
}
public LD_WaveView(Context context) {
this(context, null);
}
public LD_WaveView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public LD_WaveView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
getAttr(context, attrs, defStyleAttr);
init();
}
/**
* 初始化画笔
*/
private void init() {
mPoints = new ArrayList<Point>();
//波浪轨迹画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(mWaveColor);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPath = new Path();
//文字画笔
mTextPaint=new Paint();
mTextPaint.setColor(Color.RED);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextSize(48);
//圆环画笔
mCirclePaint=new Paint();
mCirclePaint.setAntiAlias(true);
mCirclePaint.setColor(Color.WHITE);
mCirclePaint.setStyle(Paint.Style.STROKE);
//边界线画笔
mBorderPaint=new Paint();
mBorderPaint.setAntiAlias(true);
mBorderPaint.setColor(mWaveColor);
mBorderPaint.setStrokeWidth(mBorderWidth);
mBorderPaint.setStyle(Paint.Style.STROKE);
}
/**
* 获取自定义的属性值
*
* @param attrs
*/
private void getAttr(Context context, AttributeSet attrs, int defStyle) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.LD_WaveView, defStyle, 0);
mWaveColor = a.getColor(R.styleable.LD_WaveView_wave_color, Color.RED);
isCircle=a.getBoolean(R.styleable.LD_WaveView_wave_circle,false);
a.recycle();
}
/**
*
* @param widthMeasureSpec
* @param heightMeasureSpec
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMeasure&&Math.abs(getMeasuredHeight()-getMeasuredWidth())<50) {//只计算一次就够了 ,relativelayout的时候要绘制两次 加个宽高判断
mViewHeight = getMeasuredHeight();
mViewWidth = getMeasuredWidth();
mLevelLine = mViewHeight; //初始化波的准位线 起始位视图最底部
{
mLevelLine = mViewHeight * (100-mProgress) / 100;
if (mLevelLine < 0) mLevelLine = 0;
}
//计算波峰值
mWaveHeight = mViewHeight / 20;//波峰暂定为view高度的1/20,如果需要设置 可设置set方法赋值;
mWaveLength = getMeasuredWidth();
//计算所有的点 这里取宽度为整个波长 往左再延伸一个波长 两个波长则需要9个点
for (int i = 0; i < 9; i++) {
int y = 0;
switch (i % 4) {
case 0:
y = mViewHeight;
break;
case 1:
y =mViewHeight+ mWaveHeight;
break;
case 2:
y = mViewHeight;
break;
case 3:
y = mViewHeight-mWaveHeight;
break;
}
Point point = new Point(-mWaveLength + i * mWaveLength / 4, y);
mPoints.add(point);
}
/**
* 计算圆环宽度
*/
int mIncircleRadius=mViewHeight<mViewWidth?mViewHeight/2:mViewWidth/2;//内切圆半径
int mcircumcircleRadius= (int) (Math.sqrt((float)(Math.pow(mViewHeight/2,2)+Math.pow(mViewWidth/2,2)))+0.5);//外接圆半径
int radius=mcircumcircleRadius/2+mIncircleRadius/2;
rectF=new RectF(mViewWidth/2-radius,mViewHeight/2-radius,mViewWidth/2+radius,mViewHeight/2+radius);
mStrokeWidth=mcircumcircleRadius-mIncircleRadius;
mCirclePaint.setStrokeWidth(mStrokeWidth);//线是有宽度的 采用了这种方式画圆环
isMeasure = true;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 绘制线条
*/
mPath.reset();
int i = 0;
mPath.moveTo(mPoints.get(0).getX()+mLeftWaveMoveLength, mPoints.get(0).getY()-mViewHeight*mProgress/100);
for (; i < mPoints.size() - 2; i += 2) {
mPath.quadTo(mPoints.get(i + 1).getX()+mLeftWaveMoveLength, mPoints.get(i + 1).getY()-mViewHeight*mProgress/100, mPoints.get(i + 2).getX()+mLeftWaveMoveLength, mPoints.get(i + 2).getY()-mViewHeight*mProgress/100);
}
mPath.lineTo(mPoints.get(i).getX()+mLeftWaveMoveLength, mViewHeight);
mPath.lineTo(mPoints.get(0).getX()+mLeftWaveMoveLength, mViewHeight);
mPath.close();
/**
* 绘制轨迹
*/
canvas.drawPath(mPath,mPaint);
Rect rect = new Rect();
String progress=String.format("%d%%",mProgress);
mTextPaint.getTextBounds(progress,0,progress.length(), rect);
int textHeight = rect.height();
if (mProgress>=50)//如果进度达到50 颜色变为白色,没办法啊,进度在中间 不变颜色看不到
mTextPaint.setColor(Color.WHITE);
else
mTextPaint.setColor(mWaveColor);
canvas.drawText(progress,0,progress.length(),mViewWidth/2,mViewHeight/2+textHeight/2,mTextPaint);
if (isCircle) {
/**
* 绘制圆环
*/
canvas.drawArc(rectF, 0, 360, true, mCirclePaint);
Paint circlePaint = new Paint();
circlePaint.setStrokeWidth(5);
circlePaint.setColor(Color.WHITE);
circlePaint.setAntiAlias(true);
circlePaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(mViewWidth / 2, mViewHeight / 2, mViewHeight / 2, circlePaint);
/**
* 绘制边界
*/
mBorderPaint.setStrokeWidth(mBorderWidth/2);
canvas.drawCircle(mViewWidth/2,mViewHeight/2,mViewHeight/2-mBorderWidth/2,mBorderPaint);
}else {
/**
* 绘制矩形边框
*/
canvas.drawRect(0,0,mViewWidth,mViewHeight,mBorderPaint);
}
//
handler.sendEmptyMessageDelayed(0,mTimeStep);
}
/**
* 设置进度 基准线
* @param mProgress
*/
public void setmProgress(int mProgress) {
this.mProgress = mProgress;
mLevelLine=(100-mProgress)*mViewHeight/100;
}
/**
* 设置是否为圆形
* @param circle
*/
public void setCircle(boolean circle) {
isCircle = circle;
}
}
自定义属性
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="LD_WaveView"> <attr name="wave_color" format="color"></attr> <attr name="wave_circle" format="boolean"></attr> </declare-styleable> </resources>
总结
好了,以上就是这篇文章的全部内容了,代码里备注应该还算比较清楚了,希望能对一些人有一些帮助,瑕疵不足之处欢迎指正,或者有好的建议。也可以留言交流。
# android
# webview
# 进度
# 波浪进度条
# waveview
# Android实现波浪球效果
# android贝塞尔曲线实现波浪效果
# android自定义波浪加载动画的实现代码
# Android使用自定义View实现360手机卫士波浪球进度的效果
# Android实现波浪线效果(xml bitmap)
# Android自定义View实现波浪动画
# 自定义
# 好吧
# 说了
# 唐僧
# 好了
# 是有
# 那就
# 你要
# 我有
# 就不
# 那么多
# 两次
# 没办法
# 用了
# 采用了
# 看我
# 不就
# 然后再
# 没了
# 还算
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
通义万相免费版怎么用_通义万相免费版使用方法详细指南【教程】
Win11搜索不到蓝牙耳机怎么办 Win11蓝牙驱动更新修复【详解】
Laravel怎么实现模型属性转换Casting_Laravel自动将JSON字段转为数组【技巧】
如何快速生成凡客建站的专业级图册?
桂林网站制作公司有哪些,桂林马拉松怎么报名?
Laravel如何实现多级无限分类_Laravel递归模型关联与树状数据输出【方法】
Laravel中Service Container是做什么的_Laravel服务容器与依赖注入核心概念解析
如何自定义safari浏览器工具栏?个性化设置safari浏览器界面教程【技巧】
如何用西部建站助手快速创建专业网站?
Laravel怎么上传文件_Laravel图片上传及存储配置
详解Nginx + Tomcat 反向代理 负载均衡 集群 部署指南
猪八戒网站制作视频,开发一个猪八戒网站,大约需要多少?或者自己请程序员,需要什么程序员,多少程序员能完成?
用v-html解决Vue.js渲染中html标签不被解析的问题
Laravel如何实现API资源集合?(Resource Collection教程)
常州企业网站制作公司,全国继续教育网怎么登录?
Laravel怎么设置路由分组Prefix_Laravel多级路由嵌套与命名空间隔离【步骤】
javascript基本数据类型及类型检测常用方法小结
如何快速查询域名建站关键信息?
如何正确选择百度移动适配建站域名?
JS中页面与页面之间超链接跳转中文乱码问题的解决办法
中山网站推广排名,中山信息港登录入口?
网站优化排名时,需要考虑哪些问题呢?
如何用狗爹虚拟主机快速搭建网站?
Laravel中间件起什么作用_Laravel Middleware请求生命周期与自定义详解
Laravel Blade组件怎么用_Laravel可复用视图组件的创建与使用
HTML5建模怎么导出为FBX格式_FBX格式兼容性及导出步骤【指南】
浅谈redis在项目中的应用
如何在建站主机中优化服务器配置?
网站制作报价单模板图片,小松挖机官方网站报价?
Laravel如何自定义分页视图?(Pagination示例)
UC浏览器如何切换小说阅读源_UC浏览器阅读源切换【方法】
Java解压缩zip - 解压缩多个文件或文件夹实例
如何选择PHP开源工具快速搭建网站?
Win11怎么更改系统语言为中文_Windows11安装语言包并设为显示语言
消息称 OpenAI 正研发的神秘硬件设备或为智能笔,富士康代工
动图在线制作网站有哪些,滑动动图图集怎么做?
如何快速完成中国万网建站详细流程?
如何生成腾讯云建站专用兑换码?
如何在云虚拟主机上快速搭建个人网站?
rsync同步时出现rsync: failed to set times on “xxxx”: Operation not permitted
如何在 React 中条件性地遍历数组并渲染元素
HTML5打空格有哪些误区_新手常犯的空格使用错误【技巧】
Laravel定时任务怎么设置_Laravel Crontab调度器配置
Android滚轮选择时间控件使用详解
大型企业网站制作流程,做网站需要注册公司吗?
谷歌浏览器如何更改浏览器主题 Google Chrome主题设置教程
利用vue写todolist单页应用
Win11怎么设置默认图片查看器_Windows11照片应用关联设置
如何做网站制作流程,*游戏网站怎么搭建?
Laravel Eloquent性能优化技巧_Laravel N+1查询问题解决

