一、概述

最近项目来个需求,波纹进度条。想起来之前看到的一些实现,也想了一下原理啥的,就自己写个吧。不过为了适配以后更多各种不规则的波纹进度条,因此需要能适配各种不同png图片的波纹进度条。

1. 效果图

no picture say a j8!

2. 原理分析

波纹进度条,不外乎一张背景bitmap,一张进度波纹bitmap。之后则不停的向一个方向循环移动波纹即可。如下图(手画,轻喷):

当然最关键的问题是如何把多余的波纹给隐藏起来,这里就要用到Android绘图里的位图运算了。PorterDuffXfermode给我们提供了一种实现复杂的位图运算的支持。其包含16中运算模式,如图(这个图网上到处都是,我是从APIDemo中截来的):

大概说一下,一般先画的是DST,设置Xfermode之后画的则是Src,我们会先绘制波纹,再绘制图片。这里我们可以看到,要实现Dst不需要的部分隐藏,而Src不会隐藏,则使用DstATop即可。

二、实现

自定义View实现步骤一般来说都很固定,先measure再draw即可。在这里我大概写一下这个波纹进度条的实现步骤:

  • measure,确定尺寸以及背景图片
  • 计算波纹相关属性
  • 画水波纹
  • 设置Xfermode
  • 画背景图篇
  • 画提示文字

1. onMeasure与计算

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int measuredWidth = measureWidth(widthMeasureSpec);int measuredHeight = measureHeight(heightMeasureSpec);setMeasuredDimension(measuredWidth, measuredHeight);if (null == mTmpBackground) {mIsAutoBack = true;int min = Math.min(measuredWidth, measuredHeight);mStrokeWidth = DEFAULT_STROKE_RADIO * min;float spaceWidth = DEFAULT_SPACE_RADIO * min;   // 默认背景时,线和波纹图片间距mWidth = (int) (min - (mStrokeWidth + spaceWidth) * 2);mHeight = (int) (min - (mStrokeWidth + spaceWidth) * 2);mBackground = autoCreateBitmap(mWidth / 2);} else {mIsAutoBack = false;mBackground = getBitmapFromDrawable(mTmpBackground);if (mBackground != null && !mBackground.isRecycled()) {mWidth = mBackground.getWidth();mHeight = mBackground.getHeight();}}mWaveCount = calWaveCount(mWidth, mWaveWidth);
}/*** 测量view高度,如果是wrap_content,则默认是200*/
private int measureHeight(int heightMeasureSpec) {int height = 0;int mode = MeasureSpec.getMode(heightMeasureSpec);int size = MeasureSpec.getSize(heightMeasureSpec);if (mode == MeasureSpec.EXACTLY) {height = size;} else if (mode == MeasureSpec.AT_MOST) {if (null != mTmpBackground) {height = mTmpBackground.getMinimumHeight();} else {height = 400;}}return height;
}/*** 测量view宽度,如果是wrap_content,则默认是200*/
private int measureWidth(int widthMeasureSpec) {int width = 0;int mode = MeasureSpec.getMode(widthMeasureSpec);int size = MeasureSpec.getSize(widthMeasureSpec);if (mode == MeasureSpec.EXACTLY) {width = size;} else if (mode == MeasureSpec.AT_MOST) {if (null != mTmpBackground) {width = mTmpBackground.getMinimumWidth();} else {width = 400;}}return width;
}/*** 创建默认是圆形的背景** @param radius 半径* @return 背景图*/
private Bitmap autoCreateBitmap(int radius) {Bitmap bitmap = Bitmap.createBitmap(2 * radius, 2 * radius, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);Paint p = new Paint(Paint.ANTI_ALIAS_FLAG);p.setColor(mWaveBackgroundColor);p.setStyle(Paint.Style.FILL);canvas.drawCircle(radius, radius, radius, p);return bitmap;
}/*** 从drawable中获取bitmap*/
private Bitmap getBitmapFromDrawable(Drawable drawable) {if (null == drawable) {return null;}if (drawable instanceof BitmapDrawable) {return ((BitmapDrawable) drawable).getBitmap();}try {Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());drawable.draw(canvas);return bitmap;} catch (OutOfMemoryError e) {return null;}
}/*** 计算波纹数目** @param width     波纹图宽度* @param waveWidth 每条波纹的宽度* @return 波纹数目*/
private int calWaveCount(int width, float waveWidth) {int count;if (width % waveWidth == 0) {count = (int) (width / waveWidth + 1);} else {count = (int) (width / waveWidth + 2);}return count;
}

测量

测量这里,我们先测量整个控件的尺寸,写法也很固定,就是根据给的×××MeasureSpec获得模式与尺寸(比如widthMeasureSpec,其中高2位封装了其模式,后面的则是其尺寸),如果是使用EXACTLY指定了尺寸,则为指定尺寸,否则如果有背景则使用背景尺寸,否则指定一个固定值。

确定背景图

然后,再根据是否有背景来决定使用的是背景还是自己绘制的一个圆。这里mTmpBackground就是背景图片。在初始化时候已经把背景图片获取到,并且重置背景为透明的,这样就防止了重复背景的出现(而且背景会变形,丑逼)。如果没有背景,就使用autoCreateBitmap(radius)方法绘制一个圆形,这个是我项目里的一个样式,所以,我就把它作为默认的效果了,就是效果图中第一个那样的。如果有背景图,就把背景Drawable通过getBitmapFromDrawable(drawable)方法转换为Bitmap即可。

计算波纹属性

在最后呢,就是计算波纹的数量了。我根据波纹宽度与背景图片宽度来计算波纹的个数,这里要强调一下,实际的波纹数量一定要比背景图片能容纳的波纹数量多一个,否则在移动波纹时,会很僵硬。因此,上面会根据是否能整除宽度而指定不同的数量,如果正好能显示整数个,则再加1,否则,要算上不能整除的1个再加1。

2. 绘制波纹与背景图

代码:

/*** 绘制重叠的bitmap,注意:没有背景则默认是圆形的背景,有则是背景** @param width  背景高* @param height 背景宽* @return 带波纹的图*/
private Bitmap createWaveBitmap(int width, int height) {Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);// 计算波浪位置int mCurY = (int) (height * (mMaxProgress - mProgress) / mMaxProgress);// 画pathmPath.reset();mPath.moveTo(-mDistance, mCurY);for (int i = 0; i < mWaveCount; i++) {mPath.quadTo(i * mWaveWidth + mHalfWaveWidth - mDistance, mCurY - mWaveHeight,i * mWaveWidth + mHalfWaveWidth * 2 - mDistance, mCurY);    // 起mPath.quadTo(i * mWaveWidth + mHalfWaveWidth * 3 - mDistance, mCurY + mWaveHeight,i * mWaveWidth + mHalfWaveWidth * 4 - mDistance, mCurY);    // 伏}mPath.lineTo(width, height);mPath.lineTo(0, height);mPath.close();canvas.drawPath(mPath, mWavePaint);mDistance += mSpeed;mDistance %= mWaveWidth;mWavePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));canvas.drawBitmap(mBackground, 0, 0, mWavePaint);return bitmap;
}

波纹

这块代码首先创建绘制波纹的canvas,之后计算波纹此时按进度百分比的起始位置y值,之后使用Path类来完成波纹的绘制。绘制完成偏移量会增加。

注意,这里使用到二阶贝塞尔曲线来绘制波纹,正如上面的for循环来绘制曲线,由于一个波纹宽度是一个起伏的宽度,是两个曲线(起、伏),所以要绘制两次,而上面的mHalfWaveWidth变量其实是1/4的波纹宽。如果大家不理解贝塞尔曲线,可以去搜一下。

背景图

背景图则很简单了,在测量时我们已经确定了背景图,只需要绘制出来即可。但在这之前一定要设置好xfermode。

3. 绘制文字与其他

文字

图片创建完,就要绘制到View上了,同时还要绘制上文本。

@Override
protected void onDraw(Canvas canvas) {Bitmap bitmap = createWaveBitmap(mWidth, mHeight);if (mIsAutoBack) {  // 如果没有背景,就画默认背景if (null == mStrokePaint) {mStrokePaint = new Paint(Paint.ANTI_ALIAS_FLAG);mStrokePaint.setColor(mStrokeColor);mStrokePaint.setStrokeWidth(mStrokeWidth);mStrokePaint.setStyle(Paint.Style.STROKE);}// 默认背景下先画个边框float radius = Math.min(getMeasuredWidth() / 2, getMeasuredHeight() / 2);canvas.drawCircle(getMeasuredWidth() / 2, getMeasuredHeight() / 2, radius - mStrokeWidth / 2, mStrokePaint);float left = getMeasuredWidth() / 2 - mWidth / 2;float top = getMeasuredHeight() / 2 - mHeight / 2;canvas.drawBitmap(bitmap, left, top, null);} else {canvas.drawBitmap(bitmap, 0, 0, null);}// 画文字if (!TextUtils.isEmpty(mText)) {mTextPaint.setColor(mTextColor);mTextPaint.setTextSize(mTextSize);mTextPaint.getTextBounds(mText, 0, mText.length() - 1, mTextRect);float textLength = mTextPaint.measureText(mText);Paint.FontMetrics metrics = mTextPaint.getFontMetrics();float baseLine = mTextRect.height() / 2 + (metrics.descent - metrics.ascent) / 2 - metrics.descent;canvas.drawText(mText, getMeasuredWidth() / 2 - textLength / 2,getMeasuredHeight() / 2 + baseLine, mTextPaint);}postInvalidateDelayed(10);
}

在这里前面的mIsAutoBack判断是我们的开发需求(就是效果图中第一个圆形的进度),这个只是在圆外面画了个圈。也挺好看的,我就没有删掉。之后就是绘制文字,这里文字处理要计算其宽高,就不细说了。之后调用postInvalidateDelayed(10)方法进行重绘,形成动画效果。

要注意这里计算文字绘制基线baseline的方法。

4. 补充

上述只是实现的各个步骤,还有自定义属性、初始化和公共方法没有写出来。放在后面的代码下载里。
自定义属性有:

<!-- 波纹进度条 -->
<declare-styleable name="WaveProgressView"><attr name="progress_max" format="integer" /><attr name="progress" format="integer" /><attr name="speed" format="float" /><attr name="wave_width" format="float" /><attr name="wave_height" format="float" /><attr name="wave_color" format="color" /><attr name="wave_bg_color" format="color" /><attr name="stroke_color" format="color" /><attr name="main_text" format="string" /><attr name="main_text_color" format="color" /><attr name="main_text_size" format="dimension" /><attr name="hint_text" format="string" /><attr name="hint_color" format="color" /><attr name="hint_size" format="dimension" /><attr name="text_space" format="dimension" />
</declare-styleable>

具体我也不细说了,看名称应该就知道啥意思了。
公共方法则有setMax(max)设置最大进度、setProgress(progress)设置进度、setWaveColor(color)设置波纹颜色等,不一一列举了,大家到代码里去看吧。

三、总结

这样一个波纹进度条,可以方便的帮大家实现以后各种不规则波纹进度条的需求,只需要换换图片以及波纹颜色即可。

上面带着大家了解该波纹进度条的实现步骤,从中我们不难发现,其实就是一个自定义View的实现顺序,只要你了解了需求,熟悉相关的实现原理以及api,自定义View也很简单。

项目地址:github

Android波纹进度条 轻松地让它浪起来相关推荐

  1. Android中进度条控件使用

    android中进度条控件使用 ProgressBar pb = findViewById(R.id.pb);pb.setMax(100);pb.setProgress(33); 转载于:https: ...

  2. android 自定义 进度条 旋转,Android_Android ProgressBar进度条使用详解,ProgressBar进度条,分为旋转进 - phpStudy...

    Android ProgressBar进度条使用详解 ProgressBar进度条,分为旋转进度条和水平进度条,进度条的样式根据需要自定义,之前一直不明白进度条如何在实际项目中使用,网上演示进度条的案 ...

  3. android中进度条的使用,android的进度条使用

    android的进度条 1.实现的效果 2.布局代码 先写一个my_browser.xml文件 存放WebView android:layout_width="fill_parent&quo ...

  4. android音乐进度条设计代码,【Android】Android开发实现进度条效果,SeekBar的简单使用。音量,音乐播放进度,视频播放进度等...

    作者:程序员小冰,GitHub主页:https://github.com/QQ986945193 新浪微博:http://weibo.com/mcxiaobing 首先给大家看一下我们今天这个最终实现 ...

  5. android自定义进度条_Android中的自定义进度栏

    android自定义进度条 Custom progress bar in android application gives it a personal touch. In this tutorial ...

  6. Android反向进度条(ProgressBar)的实现——从右到左的进度条

    Android反向进度条(ProgressBar)--从右到左的进度条 前言: 最近在项目中需要使用到反向进度条,在网上查了些资料,感觉对自己作用不大,于是自定义样式,实现了反向进度条. 1. 第一步 ...

  7. android 动态改变进度条,Android条纹进度条的实现(调整view宽度仿进度条)

    Android条纹进度条的实现(调整view宽度仿进度条) 发布时间:2020-10-03 16:14:24 来源:脚本之家 阅读:89 作者:RustFisher 前言 本文主要给大家介绍了关于An ...

  8. android自定义圆角进度条,Android自定义进度条的圆角横向进度条实例详解

    1.本文将向你介绍自定义进度条的写法,比较简单,但还是有些知识点是需要注意的: invalidate()方法 RectF方法的应用 onMeasure方法的应用 2.原理 画3层圆角矩形,底层为黑色, ...

  9. Android的进度条(ProgressBar)、拖动条(SeekBar)

    Android的进度条与拖动条 一.ProgressBar(进度条) 进度条,ProgressBar,分为环形和水平条行, 首先看下效果图,Progress进度加载. 进度条XML中的属性       ...

最新文章

  1. 【快速入门系列】简述 for...in 和 for...of 区别
  2. SQLi-LABS(1~10关详解)
  3. 访问服务器 request.getheader(origin)为null_服务器磁盘不足,1分钟快速搞定!
  4. Amcharts 柱状图和线形图
  5. 一个网站自动化测试程序的设计与实现
  6. MySQL中的+作用,进行字符串的拼接用concat
  7. main的方法是Java_Java中的main()方法
  8. 01-Windows下安装Node.js及环境配置
  9. python django项目实例_【Django】项目实例
  10. 从事计算机工作的应该,未来想从事计算机方面的工作,现在应该学习些什么东西?...
  11. 讯飞输入法pad版x86_讯飞输入法Pad版软件下载_讯飞输入法Pad版app下载_讯飞输入法Pad版安卓下载_亲亲宝贝网...
  12. word用宏设置图片的大小
  13. java替换图片文字_Java 替换PPT文档中的文本和图片
  14. 思科模拟器 Cisco Packet Tracer 8.2免登录以及汉化步骤
  15. STM8S103调试PWM的踩坑过程
  16. layui+croppers完成图片剪切上传
  17. python小游戏——跑酷小恐龙代码开源
  18. 【Grasshopper基础8】电池的序列化与反序列化 Serilization of Grasshopper Component
  19. SiteGround主机使用PayPal支付教程【2022最新】
  20. 荣耀3手机android 5.0吗,华为荣耀3的手机系统是什么?华为荣耀3能升级安卓4.3吗?...

热门文章

  1. 串口助手发送数据到单片机并显示在液晶上
  2. [Ubuntu]vim中文乱码
  3. Android 下简单的 MP3 播放(代码分析)
  4. App上传GooglePlay后,微信登录及Facebook登录异常
  5. 怎么用MindManager将导图导出为HTML5交互式导图
  6. O(N*logN)的排序算法
  7. 单片机p0口接8个LED c51语言,51 单片机:在 P0 口接上 8 个 LED,实现每次亮两个灯的流水灯...
  8. HTML 高度不祥的情况下自动水平垂直居中
  9. 导入mysql文件报错:ERROR: ASCII ‘\0‘ appeared in the statement, but this is not allowed unless option --bin
  10. 谁有粉?就爬谁!他粉多,就爬他!Python 多线程采集 260000+ 粉丝数据