写点什么

Kotlin 的自定义 View,实现带弧形的进度条

用户头像
Android架构
关注
发布于: 5 小时前

导入你的项目中


dependencies {


implementation 'com.tanjiajun.widget:CircularArcProgressView:1.0.2'


}


布局文件


<com.tanjiajun.widget.CircularArcProgressView


android:id="@+id/capv_first"


android:layout_width="0dp"


android:layout_height="30dp"


android:layout_marginStart="16dp"


android:layout_marginTop="16dp"


android:layout_marginEnd="16dp"


app:capv_background_color="@color/circular_arc_progress_view_first_background_color"


app:capv_is_show_progress_text="true"


app:capv_percent="0.8"


app:capv_progress_color="@color/circular_arc_progress_view_first_progress_color"


app:layout_constraintEnd_toEndOf="parent"


app:layout_constraintStart_toStartOf="parent"


app:layout_constraintTop_toTopOf="parent" />


Kotlin


findViewById<CircularArcProgressView>(R.id.capv_first).startAnimator(duration = 2000)


Java


((CircularArcProgressView) findViewById(R.id.capv_first)).startAnimator(2000);


/ 源码分析 /




定义自定义属性,据此写出对应的获取自定义属性的代码,并且暴露一些需要用户设置的方法,代码如下:


/**


  • Set percent to show the progress.


*/


var percent: Float = 0f


set(value) {


var percent = value


if (percent < 0f) {


percent = 0f


} else if (percent > 1f) {


percent = 1f


}


if (percent != field) {


field = percent


invalidate()


}


}


init {


attrs?.let { set ->


context.obtainStyledAttributes(set, R.styleable.CircularArcProgressView).apply {


bgColor =


getColor(R.styleable.CircularArcProgressView_capv_background_color, Color.BLACK)


progressColor =


getColor(R.styleable.CircularArcProgressView_capv_progress_color, Color.RED)


progressTextColor =


getColor(


R.styleable.CircularArcProgressView_capv_progress_text_color,


Color.WHITE


)


getFloat(R.styleable.CircularArcProgressView_capv_percent, 0f).let {


percent = it


}


isShowProgressText =


getBoolean(


R.styleable.CircularArcProgressView_capv_is_show_progress_text,


false


)


recycle()


}


}


}


根据用户设置的宽高去绘制一个半径为高度一半的圆角矩形,注意要对 padding 属性进行处理,这部分就是背景,代码如下:


val halfHeight = height / 2f


val saveCount = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null)


// Draw background.


backgroundRectF.left = paddingStart.toFloat()


backgroundRectF.top = paddingTop.toFloat()


backgroundRectF.right = width - paddingEnd.toFloat()


backgroundRectF.bottom = height - paddingBottom.toFloat()


canvas.drawRoundRect(backgroundRectF, halfHeight, halfHeight, backgroundPaint)


在背景圆角矩形的左边绘制另外一个半径为高度一半的圆角矩形,宽高和背景圆角矩形一样,但是左右坐标会随着 percent 的增加而增加,绘制完毕后的表现就是往右移动,然后利用 PorterDuffXfermode 处理重叠部分,这部分就是进度,代码如下:


private val progressTextPaint by lazy {


TextPaint().apply {


isAntiAlias = true


isDither = true


style = Paint.Style.FILL


color = progressTextColor


}


}


// Draw progress.


progressRectF.left = -backgroundRectF.width() + percent * width


progressRectF.top = backgroundRectF.top


progressRectF.right = progressRectF.left + backgroundRectF.width()


progressRectF.bottom = backgroundRectF


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


.bottom


canvas.drawRoundRect(progressRectF, halfHeight, halfHeight, progressPaint)


canvas.restoreToCount(saveCount)


根据用户需要绘制一个百分比文本,左右坐标也是随着 percent 增加而增加,绘制完毕后的表现也是向右移动,不过是位于进度条弧形的左边,注意要准确测量文字的宽高,代码如下:


if (isShowProgressText && percent >= 0.1f) {


progressTextPaint.run {


textSize = halfHeight


fontMetrics.let {


val progressText = (percent * 100).toInt().toString() + "%"


canvas.drawText(


progressText,


percent * width - progressTextPaint.measureText(progressText) - height / 5f,


halfHeight - it.descent + (it.descent - it.ascent) / 2f,


progressTextPaint


)


}


}


}


暴露一个设置动画的方法。


/**


  • Start animator.

  • @param timeInterpolator the interpolator to be used by this animation. The default value is

  • android.view.animation.AccelerateInterpolator.

  • @param duration the length of the animation.


*/


@JvmOverloads


fun startAnimator(


timeInterpolator: TimeInterpolator? = AccelerateInterpolator(),


duration: Long


) =


with(ObjectAnimator.ofFloat(this, "percent", 0f, percent)) {


interpolator = timeInterpolator


this.duration = duration


start()


}


/ PorterDuff.Mode /



来源

为什么叫 PorterDuff 呢?其实是两个人名来的,一个叫 Thomas Porter,另一个叫 Tom Duff,他们在 1984 年 7 月发表了 Compositing Digital Images,描述了 12 个合成运算符。它们控制着要渲染的图像和渲染目标的内容组成的颜色,然后这个类还提供了除了那 12 种的其他几种混合模式,但是这些不是由这两人定义的,只是为了方便才在此类中,所以总共有 18 种。

源码

我们可以看下 PorterDuff 这个类,里面有个枚举 Mode,代码如下:


public enum Mode {


CLEAR (0),


SRC (1),


DST (2),


SRC_OVER (3),


DST_OVER (4),


SRC_IN (5),


DST_IN (6),


SRC_OUT (7),


DST_OUT (8),


SRC_ATOP (9),


DST_ATOP (10),


XOR (11),


DARKEN (16),


LIGHTEN (17),


MULTIPLY (13),


SCREEN (14),


ADD (12),


OVERLAY (15);


Mode(int nativeInt) {


this.nativeInt = nativeInt;


}


/**


  • @hide


*/


@UnsupportedAppUsage


public final int nativeInt;


}


PorterDuff 总共有 18 种模式,以下展示了这些模式对应的名字、图片和描述,可以点开图片查看,图片如下:



/ 延迟属性 Lazy /




这个控件的代码也用上了延迟属性 Lazy,代码如下:


private val progressTextPaint by lazy {


TextPaint().apply {


isAntiAlias = true


isDither = true


style = Paint.Style.FILL


color = progressTextColor


}


}


我们可以看到,lazy 函数是接受一个 Lambda 表达式,如果函数最后一个参数是 Lambda 表达式的话,可以提到小括号外边,并且小括号也可以省略;调用延迟属性有这样的特征,第一次拿到属性的值(调用 get 方法)会执行已传递给函数的 Lambda 表达式并且记录结果,后续调用 get()只是返回记录的结果。我们可以看下源码,提供了三个函数。

lazy(initializer: () -> T)

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)


这个函数接受一个 Lambda 表达式,并且返回 Lazy,并且调用 SynchronizedLazyImpl 函数,而且我们可以得知多个线程去调用这个 lazy 函数是安全的,代码如下:


, Serializable {


private var initializer: (() -> T)? = initializer


@Volatile private var _value: Any? = UNINITIALIZED_VALUE


// final field is required to enable safe publication of constructed instance


private val lock = lock ?: this


override val value: T


get() {


val _v1 = _value


if (_v1 !== UNINITIALIZED_VALUE) {


@Suppress("UNCHECKED_CAST")


return _v1 as T

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Kotlin的自定义View,实现带弧形的进度条