需求
UI同学出了一张图,其间一个ui是半圆环进度条,在2秒内从0%加载到100%。
相似这种,请不要介意它的难看,色彩能够调整,重要的是功能,功能,功能。
思路
说到进度条,首要想到的是progressBar,不过progressBar只有水平进度条和圆环进度条这2种,不满足需求。
在网上找了一圈,有完成计划的,但感觉有点复杂,修改成自己的有点费事,遂决定自己写一个圆环,顺便重新学习一下canvas
的运用。
Canvas
canvas的常用办法如下:
drawRect
public void drawRect(@android.annotation.NonNull android.graphics.RectF rect, @android.annotation.NonNull android.graphics.Paint paint)
public void drawRect(@android.annotation.NonNull android.graphics.Rect r, @android.annotation.NonNull android.graphics.Paint paint)
public void drawRect(float left, float top, float right, float bottom, @android.annotation.NonNull android.graphics.Paint paint)
制作一个矩形区域,前面的参数是矩形的坐标,后边的参数是画笔。
drawPath
public void drawPath(@android.annotation.NonNull android.graphics.Path path, @android.annotation.NonNull android.graphics.Paint paint)
依据path路线制作一条线,这个办法是灵活性最高的,但同时也是比较费事的,因为path是由一个个固定的要害点决定的。
drawLine
public void drawLine(float startX, float startY, float stopX, float stopY, @android.annotation.NonNull android.graphics.Paint paint)
public void drawLines(@android.annotation.NonNull float[] pts, int offset, int count, @android.annotation.NonNull android.graphics.Paint paint)
public void drawLines(@android.annotation.NonNull float[] pts, @android.annotation.NonNull android.graphics.Paint paint)
制作一条线,灵活性同上
drawPoint
public void drawPoint(float x, float y, @android.annotation.NonNull android.graphics.Paint paint)
public void drawPoints(float[] pts, int offset, int count, @android.annotation.NonNull android.graphics.Paint paint)
public void drawPoints(@android.annotation.NonNull float[] pts, @android.annotation.NonNull android.graphics.Paint paint)
依据坐标制作点,和上面的不同的是,点是孤立的,并非衔接在一起的。
drawText
制作文字,其间drawText有四种重载办法,和三种drawTextRun同名办法,2种drawTextOnPath。
drawOver
public void drawOval(@android.annotation.NonNull android.graphics.RectF oval, @android.annotation.NonNull android.graphics.Paint paint)
public void drawOval(float left, float top, float right, float bottom, @android.annotation.NonNull android.graphics.Paint paint)
依据rect的巨细制作椭圆
drawCircle
制作圆形
public void drawCircle(float cx, float cy, float radius, @android.annotation.NonNull android.graphics.Paint paint)
依据圆心方位和半径制作圆形
drawArc
public void drawArc(@android.annotation.NonNull android.graphics.RectF oval, float startAngle, float sweepAngle, boolean useCenter, @android.annotation.NonNull android.graphics.Paint paint)
public void drawArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean useCenter, @android.annotation.NonNull android.graphics.Paint paint)
依据区域、圆心坐标、半径制作弧度,startAngle是圆弧的开始视点,sweepAngle是扫描的弧度。
1、计划一
半圆环,能够最初是两个两个圆弧拼接成的封闭区域,半径较大的圆弧的布景是圆弧的色彩,较小的布景是白色,这样就能够完成一个圆环。
2、计划二
计划一不引荐,因为完成带布景的半圆环进度条,总共需求画三个圆弧。一个圆弧,布景为灰色,一个绿色圆弧,巨细同灰色圆弧,最终一个白色布景的小圆弧,计算起来比较费事。并且相比较两个圆环拼接,不如直接将paint的strokWidth设置成大圆弧和小圆弧的差。这样只需求制作两个圆弧就行了,并且两个圆弧的巨细一样,只是画笔的色彩不一样就行了。
完成
第一步 先完成一个半圆环
//目前先写死
private var ringWidth = 55f
private val paint by lazy {
Paint().apply {
isAntiAlias = true
color = Color.GREEN
style = Paint.Style.STROKE
strokeWidth = ringWidth
}
}
//this is ring‘s background
private val greyPaint by lazy {
Paint().apply {
isAntiAlias = true
color = Color.parseColor("#999999")
style = Paint.Style.STROKE
strokeWidth = ringWidth
}
}
private var rectF: RectF = RectF()
用ringWidth表明圆环的宽度,paint是半圆环的色彩,greyPaint是布景色,rectF是制作的区域。
然后在onMeasure中设置制作区域的巨细,在onDraw中制作半圆环。
//在onMeasure中设置制作区域
rectF.set(
ringWidth,
ringWidth,
measuredWidth.toFloat() - ringWidth,
(measuredHeight - ringWidth) * 2
)
//在onDraw中制作半圆环进度条
//draw a ring background
canvas.drawArc(rectF, 180f, 180f, false, greyPaint)
//draw a ring
canvas.drawArc(rectF, 180f, 90f, false, paint)
这姿态就能完成一个间断的半圆环进度条了。 那么,怎么能让这个进度条动起来呢? drawArc办法中,sweepAngle是圆弧滑过的视点,只要在制作的过程中不断更新这个值(假定时刻是两秒,那么,在两秒的时刻内,不断变化sweepAngle的值,然后更新ui)。
第二步 让半圆环进度条动起来
//圆弧的视点
private var sweepAngle = 0f
//动画时刻
private var animatorDuration = 2000L
增加两个变量,然后增加动画
private val animator by lazy {
ValueAnimator.ofFloat(0f, 180f).apply {
addUpdateListener {
sweepAngle = it.animatedValue as Float
invalidate()
}
duration = animatorDuration
}
}
在结构函数中运转动画animator.start()
这样,一个会动的半圆环进度条就完成了。
第三步 定制化
让圆环进度条丰富起来。
改动进度条的色彩
这个进度条有两个色彩的值,一个是进度条的布景色,一个是进度条色彩。想要改动两个的值,有两种办法,一个是直接为其增加特点,另一个是参加两个露出外部的办法。
增加特点
进度条在xml下的代码如下:
<com.testdemo.CustomHalfCircleProgress
android:id="@+id/progress"
android:layout_width="346dp"
android:layout_height="173dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
此刻,在attrs文件下增加特点(或许values文件夹下面没有这个文件,那么也能够写在其他文件里,如themes或者在values文件夹下面创建一个文件attrs.xml)
<declare-styleable name="CustomHalfCircleProgress">
<attr name="bgColor" format="color" />
<attr name="progressColor" format="color" />
</declare-styleable>
这样,回到xml布局,就能够为progress增加这两个特点了
<com.light_mountain.testdemo.CustomHalfCircleProgress
android:id="@+id/progress"
android:layout_width="346dp"
android:layout_height="173dp"
app:bgColor="#3FB6F7"
app:progressColor="#006da8"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
回到CustomHalfCircleProgress自界说View中,在结构函数constructor(context: Context, attrs: AttributeSet)
中获取界说的特点
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomHalfCircleProgress)
bgColor = typedArray.getColor(R.styleable.CustomHalfCircleProgress_bgColor, Color.RED)
progressColor =
typedArray.getColor(R.styleable.CustomHalfCircleProgress_progressColor, Color.BLUE)
typedArray.recycle()
paint.color = progressColor
bgPaint.color = bgColor
这样就能够完成一个以浅蓝色为布景,深蓝色为进度条色彩的半圆环进度条了。同理,其他的一些特点,如最大视点(最大值)、动画时刻、进度条宽度等特点都能直接在xml文件中设置,方便快捷。
但它也有一个缺点。
因为是自界说特点,会占用特点名,而特点名是不能重复的,假如进度条运用了bgColor
这个特点名,其他的办法就不能在运用这个特点名了。
增加办法
fun setBgColor(color: Int): CustomHalfCircleProgress {
bgColor = color
bgPaint.color = bgColor
return this
}
fun setProgressColor(color: Int): CustomHalfCircleProgress {
progressColor = color
paint.color = progressColor
return this
}
然后在onCreate中获取View,并设置色彩
val progress = findViewById<CustomHalfCircleProgress>(R.id.progress)
progress
.setProgressColor(Color.parseColor("#006DA8"))
.setBgColor(Color.parseColor("#3FB6F7"))
这样就能够随时更改进度条和布景的色彩。
补全其他的办法,如设置进度条的宽度,设置动画时刻,发动动画和间断动画
fun setDuration(time: Long): CustomHalfCircleProgress {
this.animatorDuration = time
animator.duration = animatorDuration
return this
}
fun setRingWidth(value:Float) :CustomHalfCircleProgress{
this.ringWidth = value
paint.strokeWidth = ringWidth
bgPaint.strokeWidth = ringWidth
return this
}
fun start():CustomHalfCircleProgress {
animator.start()
return this
}
fun stop():CustomHalfCircleProgress{
animator.cancel()
return this
}
给进度条设置宽度为22f,动画时刻为5秒,作用如下图(2秒的时候截的图)
额外需求
是不是很完美了,基本上能够做到进度条客制化了,剩余的大多都是细节问题,如
1、显现进度条的百分比
2、进度条完毕了需求告诉外部
3、自界说进度条的开始百分比和完毕百分比
4、进度条突变色
百分比
这两个也简单,增加一个变量percent
表明当前的进度,再参加一个paint作为百分比的画笔,
private val textPaint = Paint().apply {
isAntiAlias = true
color = Color.parseColor("#006da8")
textSize = 24f
textAlign = Paint.Align.CENTER
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
...省掉代码...
//draw percent
canvas.drawText("${percent}%", percentX, percentY, textPaint)
}
percentX和percentY即在onMeasure中设置,我这边把文字的方位设置在水平居中,2/3高度的方位。
注:在canvas的画布中,x轴是向右为正,y轴是向下为正。
注2:刚开始制作文字的时候,canvas的最终一个参数是直接运用进度条的paint,结果发现制作出来的文字堆挤在一起,还以为是画布没有刷新,文字‘1%’和‘2%’重叠在一起导致的,所以运用canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
更新画布,结果导致视图的布景变黑,但百分比仍是拥挤在一块。
百分比堆挤在一起
正常的百分比显现
进度告诉
这个简单,接口回调
interface Listener {
fun success()
fun fail()
}
界说一个接口,两个办法,success表明进度条正常完毕回调,fail表明进度条动画被cancel。
private val animator by lazy {
ValueAnimator.ofFloat(0f, 180f).apply {
...省掉代码...
addListener(object : AnimatorListener {
override fun onAnimationStart(p0: Animator?) {
isCancel = false
}
override fun onAnimationEnd(p0: Animator?) {
if (isCancel) listener?.fail() else listener?.success()
}
override fun onAnimationCancel(p0: Animator?) {
isCancel = true
}
override fun onAnimationRepeat(p0: Animator?) {
}
})
}
}
在progress控件绑定后,增加接口回调的办法
progress.setListener(object : CustomHalfCircleProgress.Listener{
override fun fail() {
//动画履行失利,进度条半道间断
}
override fun success() {
//进度条百分百,成功履行完毕
}
})
自界说开始百分比和完毕百分比
这个更加简单了
//开始百分比
private var startAngle = 0f
//完毕百分比
private var endAngle = 180f
...省掉代码...
/**
* 这儿要留意的是,startPercent<=endPercent,且数值在0-100的范围内,这儿就不多做判别了
*/
fun setStartPercent(percent: Int): CustomHalfCircleProgress {
startAngle = percent * 180f / 100f
animator.setFloatValues(startAngle, endAngle)
return this
}
/**
* 这儿需求留意的一点是,动画完毕时,调用的仍然是listener?.success,即便此刻未到100%,假如有需求的话,能够在onAnimationEnd里多做一层判别
*/
fun setEndPercent(percent: Int) :CustomHalfCircleProgress {
endAngle = percent * 180f / 100f
animator.setFloatValues(startAngle, endAngle)
return this
}
进度条突变色
这个更加简略了,paint+突变色直接能够搜得到答案 # Android绘图之LinearGradient线性突变 就不提供相关代码了。
完好代码
以下是自界说的半圆环进度条的完好代码
class CustomHalfCircleProgress : View {
private var percent: Int = 0
private var percentX: Float = 0f
private var percentY: Float = 0f
private var ringWidth = 55f
//圆弧的视点
private var sweepAngle = 0f
private var startAngle = 0f
private var endAngle = 180f
//动画时刻
private var animatorDuration = 2000L
private var bgColor = Color.parseColor("#999999")
private var progressColor = Color.GREEN
private var listener: Listener? = null
private var isCancel = false
private val paint =
Paint().apply {
isAntiAlias = true
color = Color.parseColor("#006da8")
style = Paint.Style.STROKE
strokeWidth = ringWidth
}
//this is ring‘s background
private val bgPaint =
Paint().apply {
isAntiAlias = true
color = Color.parseColor("#999999")
style = Paint.Style.STROKE
strokeWidth = ringWidth
}
private val textPaint = Paint().apply {
isAntiAlias = true
color = Color.parseColor("#006da8")
textSize = 24f
textAlign = Paint.Align.CENTER
}
private val animator by lazy {
ValueAnimator.ofFloat(startAngle, endAngle).apply {
addUpdateListener {
sweepAngle = it.animatedValue as Float
percent = (sweepAngle * 100 / 180f).toInt()
postInvalidate()
}
duration = animatorDuration
addListener(object : AnimatorListener {
override fun onAnimationStart(p0: Animator?) {
isCancel = false
}
override fun onAnimationEnd(p0: Animator?) {
if (isCancel) listener?.fail() else listener?.success()
}
override fun onAnimationCancel(p0: Animator?) {
isCancel = true
}
override fun onAnimationRepeat(p0: Animator?) {
}
})
}
}
private var rectF: RectF = RectF()//区域
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
initView(context, attrs)
}
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
private fun initView(context: Context, attrs: AttributeSet) {
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//draw a ring background
canvas.drawArc(rectF, 180f, 180f, false, bgPaint)
//draw a ring
canvas.drawArc(rectF, 180f, sweepAngle, false, paint)
//draw percent
canvas.drawText("${percent}%", percentX, percentY, textPaint)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
rectF.set(
ringWidth,
ringWidth,
measuredWidth.toFloat() - ringWidth,
(measuredHeight - ringWidth) * 2
)
percentX = measuredWidth / 2.0f
percentY = measuredHeight / 3.0f
}
fun setBgColor(color: Int): CustomHalfCircleProgress {
bgColor = color
bgPaint.color = bgColor
return this
}
fun setProgressColor(color: Int): CustomHalfCircleProgress {
progressColor = color
paint.color = progressColor
return this
}
fun setDuration(time: Long): CustomHalfCircleProgress {
this.animatorDuration = time
animator.duration = animatorDuration
return this
}
fun setRingWidth(value: Float): CustomHalfCircleProgress {
this.ringWidth = value
paint.strokeWidth = ringWidth
bgPaint.strokeWidth = ringWidth
return this
}
fun start(): CustomHalfCircleProgress {
animator.start()
return this
}
fun stop(): CustomHalfCircleProgress {
animator.cancel()
return this
}
fun setListener(listener: Listener): CustomHalfCircleProgress {
this.listener = listener
return this
}
/**
* 这儿要留意的是,startPercent<=endPercent,且数值在0-100的范围内
*/
fun setStartPercent(percent: Int): CustomHalfCircleProgress {
startAngle = percent * 180f / 100f
animator.setFloatValues(startAngle, endAngle)
return this
}
/**
* 这儿需求留意的一点是,动画完毕时,调用的仍然是listener?.success,即便此刻未到100%,假如有需求的话,能够在onAnimationEnd里多做一层判别
*/
fun setEndPercent(percent: Int) :CustomHalfCircleProgress {
endAngle = percent * 180f / 100f
animator.setFloatValues(startAngle, endAngle)
return this
}
interface Listener {
fun success()
fun fail()
}
}
以上便是本人完成一个简略的进度条的计划,能用,但用途不大,不过抵挡一般简略简单的需求很好满足。