欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义View之圆形进度条式按钮

程序员文章站 2024-03-07 17:12:15
介绍 今天上班的时候有个哥们问我怎么去实现一个按钮式的进度条,先来看看他需要实现的效果图。 和普通的圆形进度条类似,只是中间的地方有两个状态表示,未开始,暂停状态。...

介绍

今天上班的时候有个哥们问我怎么去实现一个按钮式的进度条,先来看看他需要实现的效果图。

Android自定义View之圆形进度条式按钮

和普通的圆形进度条类似,只是中间的地方有两个状态表示,未开始,暂停状态。而且他说圆形进度的功能已经实现了。那么我们只需要对中间的两个状态做处理就行了。

先来看看实现的效果图:

Android自定义View之圆形进度条式按钮

上面说了我们只需要处理中间状态的变化就可以了,对于进度的处理直接使用了弘洋文章中实现:
http://blog.csdn.net/lmj623565791/article/details/43371299

下面开始具体实现。

具体实现

自定义的实现还是按照官方提供的步骤来,对于自定义view的步骤之前我也写过一篇文章,感兴趣的朋友可以看一下:android自定义view的官方套路。

为了完整讲解,下面还是会提到圆形进度条的自定义,知道进度的实现可以直接跳过部分步骤。

1、创建view

观察要实现的外圈进度条,有两个进度:一个用来表示默认的圆形,另一个表示进度的颜色。所以这里涉及到两个进度条颜色宽高的定义。要绘制圆肯定需要半径了。

创建view有三小步

(1)、定义属性

<declare-styleable name="buttoncircleprogressbar">
<!--无进度时的颜色-->
<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="radius" format="dimension" />
</declare-styleable>

(1)、定义属性变量以及构造方法中获取属性

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;
/**
* the status of this view currently;
*/
private status mstatus = status.end;
/**
* painter of all drawing things
*/
protected paint mpaint = new paint();
/**
* 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);
/**
* the length of triangle
*/
private int trianglelength;
/**
* use path to draw triangle
*/
private path mpath;
/**
* mradius of view
*/
private int mradius = dp2px(30);
public buttoncircleprogressbar(context context) {
this(context,null);
}
public buttoncircleprogressbar(context context, attributeset attrs) {
this(context, attrs,0);
}
public buttoncircleprogressbar(context context, attributeset attrs, int defstyleattr) {
super(context, attrs, defstyleattr);
// init values from custom attributes
final typedarray attributes = getcontext().obtainstyledattributes(
attrs, r.styleable.buttoncircleprogressbar);
mreachedbarcolor = attributes
.getcolor(
r.styleable.buttoncircleprogressbar_progress_reached_color,
color.blue);
munreachedbarcolor = attributes
.getcolor(
r.styleable.buttoncircleprogressbar_progress_unreached_color,
default_color_unreached_color);
mreachedprogressbarheight = (int) attributes
.getdimension(
r.styleable.buttoncircleprogressbar_progress_reached_bar_height,
mreachedprogressbarheight);
munreachedprogressbarheight = (int) attributes
.getdimension(
r.styleable.buttoncircleprogressbar_progress_unreached_bar_height,
munreachedprogressbarheight);
mradius = (int) attributes.getdimension(
r.styleable.buttoncircleprogressbar_radius, mradius);
trianglelength = mradius;
attributes.recycle();
mpaint.setstyle(paint.style.stroke);
mpaint.setantialias(true);
mpaint.setdither(true);
mpaint.setstrokecap(paint.cap.round);
mpath = new path();//need path to draw triangle
}
public status getstatus() {
return mstatus;
}
public void setstatus(status status) {
mstatus = status;
invalidate();
}
public enum status{
end,
starting
}

获取基础的一些属性,这里mstatus用来表示当前view的状态:end代码结束,starting正在进行。我们用这两个状态来判定怎么去draw去合适的效果。提供了setstatus为staus设置状态。

mpath用来进行绘制未开始时候的三角形。

2、处理view的布局

这一步主要是onmeasure方法中测量出合适的宽高。

@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(widthmeasurespec, heightmeasurespec);
}

只需要处理宽高没有精确指定的情况,通过padding加上整个圆以及paint的宽度计算出具体的值。

接下来就是第三步,绘制效果。

3、绘制view

为了更加清晰一点,这里先说绘制圆的进度,再说圆中间的状态。

(1)、绘制圆

@override
protected synchronized void ondraw(canvas canvas) {
super.ondraw(canvas);
canvas.save();
canvas.translate(getpaddingleft(), getpaddingtop());
mpaint.setstyle(paint.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);

通过 canvas.drawcircle(mradius, mradius, mradius, mpaint);绘制默认状态下的圆。之后改变画笔的颜色,根据进度绘制圆弧。

(2)、绘制中间的状态。

第一种是未开始的情况,中间是一个三角形。我们使用path来绘制三角形,主要通过 moveto(float x, float y)来设置第一个点,然后通过lineto(float x, float y)来连接一个三角形。再设置paint为填充。

第一个点这里设置为三角形的左上角的顶点。

那么第一个点怎么算?

我们这里绘制一个等边三角形,设置边长等于半径。

第一个点的x坐标就应该是圆的直径减去三角形的高之后除以2,即:

float leftx = (float) ((2*mradius-math.sqrt(3.0)/2*trianglelength)/2); 

y坐标就为:mradius-(trianglelength/2)

第二个点这里选三角形的左下角,x坐标不变,y值为半径加上边长的一半:mradius+(trianglelength/2)

第三个点选右边的点,x点的坐标显然就是第一个点的x坐标加上三角形的高
即:(float) (leftx+math.sqrt(3.0)/2*trianglelength),y点坐标就是半径mradius。

最后再回到第一个点就连接成三角形了。

mpath设置的完整代码如下

public class buttoncircleprogressbar extends progressbar {
.........
mpath = new path();//need path to draw triangle
trianglelength = mradius;
float leftx = (float) ((2*mradius-math.sqrt(3.0)/2*trianglelength)/2);
float realx = (float) (leftx+leftx*0.2);
mpath.moveto(realx,mradius-(trianglelength/2));
mpath.lineto(realx,mradius+(trianglelength/2));
mpath.lineto((float) (realx+math.sqrt(3.0)/2*trianglelength),mradius);
mpath.lineto(realx,mradius-(trianglelength/2));
}

这里用了realx设置成了leftx的两倍,是因为我感觉三角形设置在中间的效果不太好,所以让他在原有基础上增加0.2倍的距离。

有了mpath变量之后就可以在ondraw中绘制未开始状态的三角形了,看代码

@override
protected synchronized void ondraw(canvas canvas) {
super.ondraw(canvas);
canvas.save();
canvas.translate(getpaddingleft(), getpaddingtop());
....
if (mstatus==status.end){//未开始状态,画笔填充
mpaint.setstyle(paint.style.fill);
canvas.drawpath(mpath,mpaint);//直接drawpath
}else{
mpaint.setstyle(paint.style.stroke);
mpaint.setstrokewidth(dp2px(5));
canvas.drawline(mradius*2/3,mradius*2/3,mradius*2/3,2*mradius*2/3,mpaint);
canvas.drawline(2*mradius-(mradius*2/3),mradius*2/3,2*mradius-(mradius*2/3),2*mradius*2/3,mpaint);
}
canvas.restore();
}

进行中的状态就是画两条线,第一条线x直接设为半径的2/3倍,起始y点为2/3倍,结束为开始y点的2/3倍

对与另外一条线,x点直径减去mradius*2/3,y点坐标的变化和上一条线一样。

这样就完成了ondraw方法。

4、处理用户交互

由于对于下载更新进度的情况来说,该控件只做状态显示,所以这一步不需要了,要使用的话自己设置点击事件就可以了。

使用

<?xml version="1.0" encoding="utf-8"?>
<relativelayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingbottom="@dimen/activity_vertical_margin"
android:paddingleft="@dimen/activity_horizontal_margin"
android:paddingright="@dimen/activity_horizontal_margin"
android:paddingtop="@dimen/activity_vertical_margin"
tools:context="com.qiangyu.test.buttoncircleprogress.mainactivity">
<com.qiangyu.test.buttoncircleprogress.view.buttoncircleprogressbar
android:id="@+id/progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margintop="50dip"
android:padding="5dp"
android:progress="30" />
</relativelayout>

activity里设置点击事件修改状态,具体根据自己逻辑处理。

public class mainactivity extends appcompatactivity {
private buttoncircleprogressbar mprogressbar;
private static final int msg_progress_update = 0x110;
private int progress;
private handler mhandler = new handler() {
public void handlemessage(android.os.message msg) {
progress = mprogressbar.getprogress();
mprogressbar.setprogress(++progress);
if (progress >= 100) {
mhandler.removemessages(msg_progress_update);
progress = 0;
mprogressbar.setstatus(buttoncircleprogressbar.status.end);
mprogressbar.setprogress(0);
}else{
mhandler.sendemptymessagedelayed(msg_progress_update, 100);
}
};
};
@override
protected void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
setcontentview(r.layout.activity_main);
mprogressbar = (buttoncircleprogressbar) findviewbyid(r.id.progressbar);
mprogressbar.setonclicklistener(new view.onclicklistener() {
@override
public void onclick(view view) {
if (mprogressbar.getstatus()== buttoncircleprogressbar.status.starting){
mprogressbar.setstatus(buttoncircleprogressbar.status.end);
mhandler.removemessages(msg_progress_update);
}else{
mhandler.sendemptymessage(msg_progress_update);
mprogressbar.setstatus(buttoncircleprogressbar.status.starting);
}
}
});
}
}

好了,到这里一个圆形进度条式按钮就实现了.希望对大家有所帮助