Android应用中炫酷的横向和环形进度条的实例分享
一、概述
最近需要用进度条,秉着不重复造*的原则,上github上搜索了一番,看了几个觉得比较好看的progressbar,比如:daimajia的等。简单看了下代码,基本都是继承自view,彻彻底底的自定义了一个进度条。盯着那绚丽滚动条,忽然觉得,为什么要通过view去写一个滚动条,系统已经提供了progressbar以及属于它的特性,我们没必要重新去构建一个,但是系统的又比较丑,不同版本变现还不一定一样。那么得出我们的目标:改变系统progressbar的样子。
对没错,我们没有必要去从0打造一个progressbar,人家虽然长的不好看,但是特性以及稳定性还是刚刚的,我们只需要为其整下容就ok了。
接下来,我们贴下效果图:
1、横向的进度条
2、圆形的进度条
没错,这就是我们的进度条效果,横向的模仿了daimajia的进度条样子。不过我们继承子progressbar,简单的为其整个容,代码清晰易懂 。为什么说,易懂呢?
横向那个进度条,大家会drawline()和drawtext()吧,那么通过getwidth()拿到控件的宽度,再通过getprogress()拿到进度,按比例控制绘制线的长短,字的位置还不是分分钟的事。
二、实现
横向的滚动条绘制肯定需要一些属性,比如已/未到达进度的颜色、宽度,文本的颜色、大小等。
本来呢,我是想通过系统progressbar的progressdrawable,从里面提取一些属性完成绘制需要的参数的。但是,最终呢,反而让代码变得复杂。所以最终还是改用自定义属性。 说道自定义属性,大家应该已经不陌生了。
1、horizontalprogressbarwithnumber
(1)自定义属性
values/attr_progress_bar.xml:
<?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="horizontalprogressbarwithnumber"> <attr name="progress_unreached_color" format="color" /> <attr name="progress_reached_color" format="color" /> <attr name="progress_reached_bar_height" format="dimension" /> <attr name="progress_unreached_bar_height" format="dimension" /> <attr name="progress_text_size" format="dimension" /> <attr name="progress_text_color" format="color" /> <attr name="progress_text_offset" format="dimension" /> <attr name="progress_text_visibility" format="enum"> <enum name="visible" value="0" /> <enum name="invisible" value="1" /> </attr> </declare-styleable> <declare-styleable name="roundprogressbarwidthnumber"> <attr name="radius" format="dimension" /> </declare-styleable> </resources>
(2)构造中获取
public class horizontalprogressbarwithnumber extends progressbar { private static final int default_text_size = 10; private static final int default_text_color = 0xfffc00d1; private static final int default_color_unreached_color = 0xffd3d6da; private static final int default_height_reached_progress_bar = 2; private static final int default_height_unreached_progress_bar = 2; private static final int default_size_text_offset = 10; /** * painter of all drawing things */ protected paint mpaint = new paint(); /** * color of progress number */ protected int mtextcolor = default_text_color; /** * size of text (sp) */ protected int mtextsize = sp2px(default_text_size); /** * offset of draw progress */ protected int mtextoffset = dp2px(default_size_text_offset); /** * height of reached progress bar */ protected int mreachedprogressbarheight = dp2px(default_height_reached_progress_bar); /** * color of reached bar */ protected int mreachedbarcolor = default_text_color; /** * color of unreached bar */ protected int munreachedbarcolor = default_color_unreached_color; /** * height of unreached progress bar */ protected int munreachedprogressbarheight = dp2px(default_height_unreached_progress_bar); /** * view width except padding */ protected int mrealwidth; protected boolean mifdrawtext = true; protected static final int visible = 0; public horizontalprogressbarwithnumber(context context, attributeset attrs) { this(context, attrs, 0); } public horizontalprogressbarwithnumber(context context, attributeset attrs, int defstyle) { super(context, attrs, defstyle); sethorizontalscrollbarenabled(true); obtainstyledattributes(attrs); mpaint.settextsize(mtextsize); mpaint.setcolor(mtextcolor); } /** * get the styled attributes * * @param attrs */ private void obtainstyledattributes(attributeset attrs) { // init values from custom attributes final typedarray attributes = getcontext().obtainstyledattributes( attrs, r.styleable.horizontalprogressbarwithnumber); mtextcolor = attributes .getcolor( r.styleable.horizontalprogressbarwithnumber_progress_text_color, default_text_color); mtextsize = (int) attributes.getdimension( r.styleable.horizontalprogressbarwithnumber_progress_text_size, mtextsize); mreachedbarcolor = attributes .getcolor( r.styleable.horizontalprogressbarwithnumber_progress_reached_color, mtextcolor); munreachedbarcolor = attributes .getcolor( r.styleable.horizontalprogressbarwithnumber_progress_unreached_color, default_color_unreached_color); mreachedprogressbarheight = (int) attributes .getdimension( r.styleable.horizontalprogressbarwithnumber_progress_reached_bar_height, mreachedprogressbarheight); munreachedprogressbarheight = (int) attributes .getdimension( r.styleable.horizontalprogressbarwithnumber_progress_unreached_bar_height, munreachedprogressbarheight); mtextoffset = (int) attributes .getdimension( r.styleable.horizontalprogressbarwithnumber_progress_text_offset, mtextoffset); int textvisible = attributes .getint(r.styleable.horizontalprogressbarwithnumber_progress_text_visibility, visible); if (textvisible != visible) { mifdrawtext = false; } attributes.recycle(); }
嗯,看起来代码挺长,其实都是在获取自定义属性,没什么技术含量。
(3)onmeasure
刚才不是出ondraw里面写写就行了么,为什么要改onmeasure呢,主要是因为我们所有的属性比如进度条宽度让用户自定义了,所以我们的测量也得稍微变下。
@override protected synchronized void onmeasure(int widthmeasurespec, int heightmeasurespec) { int heightmode = measurespec.getmode(heightmeasurespec); if (heightmode != measurespec.exactly) { float textheight = (mpaint.descent() + mpaint.ascent()); int exceptheight = (int) (getpaddingtop() + getpaddingbottom() + math .max(math.max(mreachedprogressbarheight, munreachedprogressbarheight), math.abs(textheight))); heightmeasurespec = measurespec.makemeasurespec(exceptheight, measurespec.exactly); } super.onmeasure(widthmeasurespec, heightmeasurespec); }
宽度我们不变,所以的自定义属性不涉及宽度,高度呢,只考虑不是exactly的情况(用户明确指定了,我们就不管了),根据padding和进度条宽度算出自己想要的,如果非exactly下,我们进行exceptheight封装,传入给控件进行测量高度。
测量完,就到我们的ondraw了~~~
(4)ondraw
@override protected synchronized void ondraw(canvas canvas) { canvas.save(); //画笔平移到指定paddingleft, getheight() / 2位置,注意以后坐标都为以此为0,0 canvas.translate(getpaddingleft(), getheight() / 2); boolean noneedbg = false; //当前进度和总值的比例 float radio = getprogress() * 1.0f / getmax(); //已到达的宽度 float progressposx = (int) (mrealwidth * radio); //绘制的文本 string text = getprogress() + "%"; //拿到字体的宽度和高度 float textwidth = mpaint.measuretext(text); float textheight = (mpaint.descent() + mpaint.ascent()) / 2; //如果到达最后,则未到达的进度条不需要绘制 if (progressposx + textwidth > mrealwidth) { progressposx = mrealwidth - textwidth; noneedbg = true; } // 绘制已到达的进度 float endx = progressposx - mtextoffset / 2; if (endx > 0) { mpaint.setcolor(mreachedbarcolor); mpaint.setstrokewidth(mreachedprogressbarheight); canvas.drawline(0, 0, endx, 0, mpaint); } // 绘制文本 if (mifdrawtext) { mpaint.setcolor(mtextcolor); canvas.drawtext(text, progressposx, -textheight, mpaint); } // 绘制未到达的进度条 if (!noneedbg) { float start = progressposx + mtextoffset / 2 + textwidth; mpaint.setcolor(munreachedbarcolor); mpaint.setstrokewidth(munreachedprogressbarheight); canvas.drawline(start, 0, mrealwidth, 0, mpaint); } canvas.restore(); } @override protected void onsizechanged(int w, int h, int oldw, int oldh) { super.onsizechanged(w, h, oldw, oldh); mrealwidth = w - getpaddingright() - getpaddingleft(); }
其实核心方法就是ondraw了,但是呢,ondraw也很简单,绘制线、绘制文本、绘制线,结束。
还有两个简单的辅助方法:
/** * dp 2 px * * @param dpval */ protected int dp2px(int dpval) { return (int) typedvalue.applydimension(typedvalue.complex_unit_dip, dpval, getresources().getdisplaymetrics()); } /** * sp 2 px * * @param spval * @return */ protected int sp2px(int spval) { return (int) typedvalue.applydimension(typedvalue.complex_unit_sp, spval, getresources().getdisplaymetrics()); }
好了,到此我们的横向进度就结束了,是不是很简单~~如果你是自定义view,你还得考虑progress的更新,考虑状态的销毁与恢复等等复杂的东西。
接下来看我们的roundprogressbarwidthnumber圆形的进度条。
2、roundprogressbarwidthnumber
圆形的进度条和横向的进度条基本变量都是一致的,于是我就让roundprogressbarwidthnumber extends horizontalprogressbarwithnumber 了。
然后需要改变的就是测量和ondraw了:
完整代码:
package com.zhy.view; import android.content.context; import android.content.res.typedarray; import android.graphics.canvas; import android.graphics.paint.cap; import android.graphics.paint.style; import android.graphics.rectf; import android.util.attributeset; import com.zhy.library.view.r; public class roundprogressbarwidthnumber extends horizontalprogressbarwithnumber { /** * mradius of view */ private int mradius = dp2px(30); public roundprogressbarwidthnumber(context context) { this(context, null); } public roundprogressbarwidthnumber(context context, attributeset attrs) { super(context, attrs); mreachedprogressbarheight = (int) (munreachedprogressbarheight * 2.5f); typedarray ta = context.obtainstyledattributes(attrs, r.styleable.roundprogressbarwidthnumber); mradius = (int) ta.getdimension( r.styleable.roundprogressbarwidthnumber_radius, mradius); ta.recycle(); mtextsize = sp2px(14); mpaint.setstyle(style.stroke); mpaint.setantialias(true); mpaint.setdither(true); mpaint.setstrokecap(cap.round); } @override protected synchronized void onmeasure(int widthmeasurespec, int heightmeasurespec) { int heightmode = measurespec.getmode(heightmeasurespec); int widthmode = measurespec.getmode(widthmeasurespec); int paintwidth = math.max(mreachedprogressbarheight, munreachedprogressbarheight); if (heightmode != measurespec.exactly) { int exceptheight = (int) (getpaddingtop() + getpaddingbottom() + mradius * 2 + paintwidth); heightmeasurespec = measurespec.makemeasurespec(exceptheight, measurespec.exactly); } if (widthmode != measurespec.exactly) { int exceptwidth = (int) (getpaddingleft() + getpaddingright() + mradius * 2 + paintwidth); widthmeasurespec = measurespec.makemeasurespec(exceptwidth, measurespec.exactly); } super.onmeasure(heightmeasurespec, heightmeasurespec); } @override protected synchronized void ondraw(canvas canvas) { string text = getprogress() + "%"; // mpaint.gettextbounds(text, 0, text.length(), mtextbound); float textwidth = mpaint.measuretext(text); float textheight = (mpaint.descent() + mpaint.ascent()) / 2; canvas.save(); canvas.translate(getpaddingleft(), getpaddingtop()); mpaint.setstyle(style.stroke); // draw unreaded bar mpaint.setcolor(munreachedbarcolor); mpaint.setstrokewidth(munreachedprogressbarheight); canvas.drawcircle(mradius, mradius, mradius, mpaint); // draw reached bar mpaint.setcolor(mreachedbarcolor); mpaint.setstrokewidth(mreachedprogressbarheight); float sweepangle = getprogress() * 1.0f / getmax() * 360; canvas.drawarc(new rectf(0, 0, mradius * 2, mradius * 2), 0, sweepangle, false, mpaint); // draw text mpaint.setstyle(style.fill); canvas.drawtext(text, mradius - textwidth / 2, mradius - textheight, mpaint); canvas.restore(); } }
首先获取它的专有属性mradius,然后根据此属性去测量,测量完成绘制;
绘制的过程呢?
先绘制一个细一点的圆,然后绘制一个粗一点的弧度,二者叠在一起就行。文本呢,绘制在中间~~~总体,没什么代码量。
好了,两个进度条就到这了,是不是发现简单很多。总体设计上,存在些问题,如果抽取一个baseprogressbar用于获取公共的属性;然后不同样子的进度条继承分别实现自己的测量和样子,这样结构可能会清晰些~~~
三、使用
布局文件
<scrollview xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:zhy="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" > <linearlayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="25dp" > <com.zhy.view.horizontalprogressbarwithnumber android:id="@+id/id_progressbar01" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="50dip" android:padding="5dp" /> <com.zhy.view.horizontalprogressbarwithnumber android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="50dip" android:padding="5dp" android:progress="50" zhy:progress_text_color="#fff53b03" zhy:progress_unreached_color="#fff7c6b7" /> <com.zhy.view.roundprogressbarwidthnumber android:id="@+id/id_progress02" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="50dip" android:padding="5dp" android:progress="30" /> <com.zhy.view.roundprogressbarwidthnumber android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margintop="50dip" android:padding="5dp" android:progress="50" zhy:progress_reached_bar_height="20dp" zhy:progress_text_color="#fff53b03" zhy:radius="60dp" /> </linearlayout> </scrollview>
mainactivity
package com.zhy.sample.progressbar; import android.app.activity; import android.os.bundle; import android.os.handler; import com.zhy.annotation.log; import com.zhy.view.horizontalprogressbarwithnumber; public class mainactivity extends activity { private horizontalprogressbarwithnumber mprogressbar; private static final int msg_progress_update = 0x110; private handler mhandler = new handler() { @log public void handlemessage(android.os.message msg) { int progress = mprogressbar.getprogress(); mprogressbar.setprogress(++progress); if (progress >= 100) { mhandler.removemessages(msg_progress_update); } mhandler.sendemptymessagedelayed(msg_progress_update, 100); }; }; @log @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); mprogressbar = (horizontalprogressbarwithnumber) findviewbyid(r.id.id_progressbar01); mhandler.sendemptymessage(msg_progress_update); } }