Android波纹进度条 轻松地让它浪起来
来源:互联网 发布:影楼制作软件apk 编辑:程序博客网 时间:2024/05/23 00:09
一、概述
最近项目来个需求,波纹进度条。想起来之前看到的一些实现,也想了一下原理啥的,就自己写个吧。不过为了适配以后更多各种不规则的波纹进度条,因此需要能适配各种不同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与计算
@Overrideprotected 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); // 画path mPath.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上了,同时还要绘制上文本。
@Overrideprotected 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波纹进度条 轻松地让它浪起来
- Android 画波纹进度条
- 让快速排序真正地"快起来"
- 前端.如何让进度条样式动起来
- Android让屏幕亮起来
- android让TextView滚起来
- 修改Word默认设置 让它用起来更顺手
- Unity3D入门:给刚体添加作用力让它运动起来
- 一键淘宝,让你的生意轻松移动起来!
- 双波纹分离字体进度条
- Android动态修改icon--让你的app浪起来
- 写一个浪起来的进度条
- 10个技巧让你更轻松地使用AWS
- android让你的TabHost滑动起来
- android让你的TabHost滑动起来
- android让你的TabHost滑动起来
- android让你的TabHost滑动起来
- android游戏教程:让人物动起来
- 阿里云oss传输文件报错 Unsupported algorithm: HmacSHA1
- Makefile的规则
- apache commons工具类简介
- Hbase 之 HBase 的整体架构
- razor 页面 js int 输出 等
- Android波纹进度条 轻松地让它浪起来
- Leetcode-Climbing Stairs-Python
- 指针大小以及使用必须初始化
- C# Dictionary根据Key排序
- 多线程下载封装类(转http://blog.csdn.net/lmj623565791/article/details/26994463)
- 直接用so加载超大gif图片,不会挂掉。
- ListView源码解析(一) 绘制流程
- Java工具类之Apache的Commons Lang和BeanUtils
- stringbuilder截取最后一个字符