您的位置:首页 > 移动开发 > Android开发

【Android自定义view系列】圆形百分比进度条

2017-02-20 19:48 555 查看
1.前言

       本文由xygy8860原创,首发地址:http://blog.csdn.net/xygy8860/article/details/55101326,转载请注明。

       在工作工程中,自己掌握的Android开发知识已经感觉到了瓶颈,Android初级知识没问题,写业务逻辑也没问题。但是作为一个Android开发工程师,并不能仅仅满足于现状,看到自身缺陷和瓶颈之后,需要对自身进行努力提高,以提升自身技能。

       对于Android开发而言,自定义控件一直是Android开发进阶的一大方向,更何况,如今的APP已经不是简单的功能实现,用户对于APP的体验已经从功能实现转为用户体验,画面是否精美,转场动画是否别出心裁,UI是否美观大方?等等这些内容是必须考虑的。现如今同类APP越来越多,公司为了赢得客户,提高留存率,必然在体验和UI上下功夫。如果这时设计师设计出精美的UI,开发工程师说:I can't !,那么估计老板会说go out 了!

       基于以上,开始了自定义View的学习。同时在学习过程中,记录下来,如果你也对自定义View有兴趣,我们也可以一同成长!

2.圆形百分比进度条UI分析

       2.1为何会选择圆形百分比进度条?

       现在下载进度条等已经高度自定义,传统的progressbar已经样式落后。在使用360手机助手等的过程中,圆形百分比进度条已经普遍而且非常精美,是常规下载组件的一部分。因此,圆形百分比进度条作为第一个学习对象。同时,在学习个过程中,完善一个自定义view的框架,可以很方便的集成各种自定义View。

       在本文的学习过程中,学习参考了这篇文章http://blog.csdn.net/wingichoy/article/details/50334595,对于博主后面的自定义View文章,也准备系统的跟着学习下。在此感谢博主的文章,指出了自定义View学习的道路。

       2.2 废话不说,上效果

      


       录制的gif有点卡顿,不过在真机上还是很流畅的。

      [b]2.3 自定义view知识储备[/b]

       我们都知道,在自定义view的时候一般都要重写三个方法:onMeasure(),onLayout()和onDraw()。

onDraw()必须有,用来绘制View图像
如果要改变View大小,需要重写onMeasure()
如果要改变View在父控件中的位置,需要重写onLayout()
       view还有其他的方法,也一并了解下,对于自定义view有时候非常有用。

>> onFinishInflate(): 这是一个回调方法,当应用从XML布局文件加载该组件并利用它来构建界面之后,该方法将会被回调。

>> onMeasure(int,int):调用该方法来检测View组件及它所包含的所有子组件的大小。

>> onLayout(boolean,int,int,int,int):当该组件需要分配其自组件的位置、大小时,该方法就会被回调。

>> onSizeChanged(int,int,int,int):当该组件的大小被改变时回调该方法。

>> onDraw(Canvas):当该组件需要绘制它的内容时回调该方法进行绘制。

>> onKeyDown(int,KeyEvent):当某个键被按下时触发该方法。

>> onKeyUp(int,KeyEvent):当松开某个按键时触发该方法。

>> onTrackballEvent(MotionEvent):当发生轨迹球事件时触发该方法

>> onTouchEvent(MotionEvent):当发生触摸屏事件时触发该方法

>> onWindowFocusChanged(boolean):当该组件得到、失去焦点时触发该方法。

>> onAttachedToWindow():当把组件放入某个窗口时触发该方法

>> onDetachedFrowWindow():当把组件从某个窗口上分离时触发该方法。

>> onWindowVisibilityChanged(int):当包含该组件的窗口的可见性发生改变时触发该方法。

      [b]2.4 UI分析[/b]

       在看到上面这个UI的时候,首先我们需要分析下UI的组成部分。任何高级UI都是有初级UI元素组成的,把一个高级UI拆解成一个个初级UI的组成部分,那么也就离我们实现它不远了。

       从上面的gif图上我们可以看到,自定义圆形百分比进度条,含有三个元素:(1)一个大圆;(2)一个弧形色带;(3):百分比进度的文字。从上面的拆解我们可以发现,大圆好实现,文字也好实现,但是弧形色带怎么实现呢?

       其实,经过分析我们也可以再次分解,将色带分解为:(1)一个圆弧;(2)一个中心小圆。这样实现是不是更加容易了呢?

       最终,我们将UI拆解为四个部分:

(1)外层大圆;
(2)中间圆弧;
(3)内层小圆;
(4)中心文字。
       其图层显示如下(图片来源于参考博客,感谢博主):

      






3.圆形百分比进度条代码编写

       3.1 继承View并实现构造函数


       自定义view的第一步,必然是继承View或viewgroup,由于我们此文主要实现是控件,而不需要布局,同时我们是完全自绘控件,所以只需要继承View即可,同时实现构造函数。首先我们定义一个CircleProgressBar的自定义类,并实现构造函数。代码如下:

// 将前面二个构造函数都指向第三个
public CircleProgressBar(Context context) {
this(context, null);
}

// XML中使用此构造函数
public CircleProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);

// 默认值,色带宽度,文字大小等
mStripeWidth = PxUtils.dpToPx(30, context);
mCenterTextSize = PxUtils.spToPx(20, context);
mRadius = PxUtils.dpToPx(100, context);

//绘制大圆画笔属性设置
bigCirclePaint = new Paint();
bigCirclePaint.setAntiAlias(true); // 抗锯齿
bigCirclePaint.setColor(mCircleColor); // 设置大圆颜色

//绘制小圆画笔属性设置
smallCirclePaint = new Paint();
smallCirclePaint.setAntiAlias(true);
smallCirclePaint.setColor(mCircleColor);

// 饼状图属性
rect = new RectF();
sectorPaint = new Paint();
sectorPaint.setColor(mAngleColor);
sectorPaint.setAntiAlias(true);

// 文字画笔属性
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
}


       3.2 onMeasure


       由于我们不涉及到改变控件在viewparent的位置问题,所以不需要重写onLayout()。所以我们重写的第一步是onMeasure()。
       在重写onMeasure()的时候,需要了解一些其他知识。我们都知道,在xml中我们需要设置layout_height和layout_width属性,那么,控件在onMeasure()的时候,就需要用到这些属性。根据这篇博客http://blog.csdn.net/lmj623565791/article/details/24252901的解读:

当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。

所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法”:重写之前先了解MeasureSpec的specMode,一共三种类型:

EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
MeasureSpec对象包含了测量的模式和大小。他是一个32位的int值,其中高两位为测量的模式,低30位是测量的大小。采用位运算和运行效率有关。所以可以从一个MeasureSpec对象分别获取模式和值 如:

//获取模式  值为 EXACTLY AT_MOST UNSPECIFIED
int specMode = MeasureSpec.getMode(measureSpec);
//获取测量值
int specSize = MeasureSpec.getSize(measureSpec);
       了解了测量模式之后,我们就可以根据specMode 的值,来判断当前layout_height和layout_width的属性,从而计算出当前控件的实际宽高,此文中也即获得了大圆的半径。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//获取测量模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//获取测量大小
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);

// match_parent
if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
mRadius = widthSize / 2;
x = widthSize / 2;
y = heightSize / 2;
mWidth = widthSize;
mHeight = heightSize;
}

// warp_content
// 如果是自适应,则取默认值
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
mWidth = (int) (mRadius * 2);
mHeight = (int) (mRadius * 2);
x = mRadius;
y = mRadius;
}
setMeasuredDimension(mWidth, mHeight);
}


       3.3 onDraw


       在控件测量完成之后,系统会调用onDraw()方法绘制控件,在此重写,达到绘制圆形百分比进度条的目的。onDraw()主要分四个方面,即上文提到的大圆、圆弧、小圆和文字,逐层进行绘制,顺序不可调换,不然会导致覆盖。

@Override
protected void onDraw(Canvas canvas) {

mEndAngle = (int) (mCurPercent * 3.6);

// 大圆
canvas.drawCircle(x, y, mRadius, bigCirclePaint); // 通过canvas画圆

//饼状图
rect.right = mWidth;
rect.bottom = mHeight;
//参数说明见知识补充
canvas.drawArc(rect, 270, mEndAngle, true, sectorPaint);

// 小圆
canvas.drawCircle(x, y, mRadius - mStripeWidth, smallCirclePaint);

//绘制文本
String text = mCurPercent + "%";
textPaint.setTextSize(mCenterTextSize);
float textLength = textPaint.measureText(text);
canvas.drawText(text, x - textLength / 2, y, textPaint);
}


       3.4 外部调用


       布局方面写完之后,我们还需要向外界暴露一个借口,以控制进度条的显示和文字的改变。

// 外部设置百分比数
public void setPercent(int percent) {
if (percent > 100 || percent < 0) {
throw new IllegalArgumentException("percent must more than 0 and less than 100!");
}
mCurPercent = percent;
invalidate();
}


       3.5 成果


       至此大功告成,我们只需要在布局中引用,即可验证我们的成果。布局中代码也很简单,和普通控件一样即可。

<com.chenghui.customview.widget.CircleProgressBar
android:id="@+id/circleView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />


       然后在代码中findViewById即可得到实例,根据下载进度,持续调用setPercent()方法就能达到动画效果。

      


       3.6 扩展


       在代码写完,功能实现之后,为了更好的扩展性,必须对控件的属性进行接口暴露,使用者能根据自己的意愿对控件进行适当的修改,以和应用的整体风格契合。因此,暴露以下方法:

// 设置圆的颜色
public void setCircleColor(int mCircleColor) {
this.mCircleColor = mCircleColor;
smallCirclePaint.setColor(mCircleColor);
bigCirclePaint.setColor(mCircleColor);
// 此方法是为了使设置生效
invalidate();
}

// 设置圆弧的颜色
public void setAngleColor(int mAngleColor) {
this.mAngleColor = mAngleColor;
sectorPaint.setColor(mAngleColor);
invalidate();
}

// 设置色带宽度
public void setStripeWidth(float mStripeWidth) {
this.mStripeWidth = mStripeWidth;
invalidate();
}

// 设置字体颜色
public void setCenterTextColor(int color) {
textPaint.setColor(color);
invalidate();
}

// 设置字体大小
public void setmCenterTextSize(float mCenterTextSize) {
this.mCenterTextSize = mCenterTextSize;
invalidate();
}

      


4.源码

       本文的源码已经上传github,地址:https://github.com/xygy8860/CustomView求star),请各位自取,下载源码后可以按自己的风格自行修改
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐