1、前言
书接上一回 5分钟带你了解Android Progress Bar 2
上一回说到,要自定义ProgressBar,自定义一个ProgressBar,但功能不如原生的完整版!!主要还是用来了解如何写一个进度条,从而引出后面的,功能肯定没有原生的那么齐全啦。
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
系列更新:
看看效果先
2、条形ProgressBar简单仿写
(1)准备好Attribute
attr.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ZProgressBar2">
<attr name="zTextColor" format="color" />
<attr name="zBarHeight" format="dimension"/>
<attr name="zMax" format="float" />
<attr name="zProgress" format="float" />
<attr name="zProgressColor" format="string" />
<attr name="zRemainColor" format="string" />
<attr name="zTextVisibility" format="boolean"/>
</declare-styleable>
</resources>
随便想点属性啦
(2)新建一个ZProgressBar2.kt
class ZProgressBar2 @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {}
名字随意,新建文件 -> 继承自 View -> 由编译器补全代码。
选@JvmOverloads的这条!这样Java也能用了
(3)准备好一些变量声明
声明啥,你自己看着办就行。我这里基本就是准备了,Attribute中属性
/**
* 默认进度条颜色
*/
private val DEFAULT_PROGRESS_COLOR: Int = Color.parseColor("#FFA5E05B")
private val DEFAULT_REMAIN_COLOR: Int = Color.parseColor("#FFFFFFFF")
private val DEFAULT_TEXT_COLOR: Int = Color.parseColor("#FFFFFFFF")
/**
* 默认进度
*/
private val DEFAULT_MAX: Float = 100f
private val DEFAULT_PROGRESS: Float = 0f
/**
* 进度条最大值
*/
var mMax: Float = DEFAULT_MAX
var mMin: Float = DEFAULT_PROGRESS
/**
* 进度条当前进度值
*/
var mProgress: Float = DEFAULT_PROGRESS
/**
* 进度条颜色
* 完成
*/
var mProgressColor = DEFAULT_PROGRESS_COLOR
/**
* 进度条颜色
* 剩余
*/
var mRemainColor = DEFAULT_REMAIN_COLOR
/**
* 进度条高度
*/
var mBarHeight = 30f.dp
/**
* 剩余进度区域
*/
private val mRemainRectF = RectF(0f, 0f, 0f, 0f)
/**
* 已完成进度区域
*/
private val mProgressRectF = RectF(0f, 0f, 0f, 0f)
/**
* ANTI_ALIAS_FLAG 抗锯齿
* isAntiAlias 防抖动
* Paint.Cap.ROUND 笔画凸出成半圆形
*/
private val mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
strokeCap = Paint.Cap.ROUND
isAntiAlias = true
}
/**
* 进度文字颜色
*/
private var mTextColor = Color.WHITE
private var mTextVisibility = false
private var progressFormat = DecimalFormat("#")
(4)准备好读取Attribute的方法
记得在init中调用哈
/**
* 读取自定义的布局属性
*/
private fun initArr(attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ZProgressBar2)
typedArray.run {
mMax = getFloat(R.styleable.ZProgressBar2_zMax, mMax)
mProgress = getFloat(R.styleable.ZProgressBar2_zProgress, mProgress)
mProgressColor =
getColor(R.styleable.ZProgressBar2_zProgressColor, DEFAULT_PROGRESS_COLOR)
mRemainColor =
getColor(R.styleable.ZProgressBar2_zRemainColor, DEFAULT_REMAIN_COLOR)
mBarHeight = getDimension(R.styleable.ZProgressBar2_zBarHeight, 30f.dp)
mTextColor = getColor(
R.styleable.ZProgressBar2_zTextColor,
DEFAULT_REMAIN_COLOR
)
mTextVisibility =
getBoolean(R.styleable.ZProgressBar2_zTextVisibility, true)
}
typedArray.recycle()
}
init{
initArr(attrs)
}
(5)在onDraw中绘制
主要代码就在这了,没多少,看注释!
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
calculateProgressRect()
canvas?.run {
//先画剩余的,要让进度压在上面
mPaint.color = mRemainColor
//进度和剩余进度,就是两个圆角方形叠在一起
drawRoundRect(mRemainRectF, mBarHeight / 2, mBarHeight / 2, mPaint)
//再画进度
mPaint.color = mProgressColor
drawRoundRect(mProgressRectF, mBarHeight / 2, mBarHeight / 2, mPaint)
//然后文字在最上层
mPaint.textSize = mBarHeight * 0.5f
//格式化Progress
var mCurrentDrawText: String = progressFormat.format(mProgress * 100 / mMax)
//画文字的基本操作,先测一下宽度
val mDrawTextWidth = mPaint.measureText(mCurrentDrawText)
//要判断下进度的宽度,够不够画文字出来
if (mTextVisibility && mProgress > 0 && mProgressRectF.right > mDrawTextWidth) {
mPaint.color = mTextColor
drawText(
mCurrentDrawText,
mProgressRectF.right - mDrawTextWidth - mBarHeight * 0.4f,
//descent/ascent 关于文字的高度可以去看看相关文章
(height / 2.0f - (mPaint.descent() + mPaint.ascent()) / 2.0f).toInt().toFloat(),
mPaint
)
}
}
}
/**
* 很常规的计算一个View的四个坐标的方法
*/
private fun calculateProgressRect() {
val ttop = (height - mBarHeight) / 2.0f
val bbottom = (height + mBarHeight) / 2.0f
mProgressRectF.run {
left = paddingLeft.toFloat()
top = ttop
/**
* 计算已完成进度的长度
*/
right = (width - paddingLeft - paddingRight) / (mMax * 1.0f) * mProgress + paddingLeft
bottom = bbottom
}
mRemainRectF.run {
left = paddingLeft.toFloat()
top = ttop
right = (width - paddingRight).toFloat()
bottom = bbottom
}
}
(6)暴露更新方法
重写下set方法就好了,都去调用invalidate()方法。
/**
* 进度条最大值
*/
var mMax: Float = DEFAULT_MAX
set(value) {
field = value
invalidate()
}
var mMin: Float = DEFAULT_PROGRESS
set(value) {
field = value
invalidate()
}
/**
* 进度条当前进度值
*/
@set:Synchronized
var mProgress: Float = DEFAULT_PROGRESS
set(value) {
if (value == field) {
// No change from current.
return
}
//不能超过最大值,最小值啊
field = value.coerceIn(mMin, mMax)
invalidate()
}
/**
* 进度条颜色
* 完成
*/
var mProgressColor = DEFAULT_PROGRESS_COLOR
set(value) {
field = value
invalidate()
}
/**
* 进度条颜色
* 剩余
*/
var mRemainColor = DEFAULT_REMAIN_COLOR
set(value) {
field = value
invalidate()
}
/**
* 进度条高度
*/
var mBarHeight = 30f.dp
set(value) {
field = value
invalidate()
}
没啦,就这么多,一个看起来差不多的就行了,简单版~
(7)完整代码
下面是全部代码,以及调用代码
import android.animation.ObjectAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import com.nf.framework.utillib.ext.dp
import com.nf.module_test.R
import java.text.DecimalFormat
/**
* 仿造ProgressBar
*/
class ZProgressBar2 @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
/**
* 默认进度条颜色
*/
private val DEFAULT_PROGRESS_COLOR: Int = Color.parseColor("#FFA5E05B")
private val DEFAULT_REMAIN_COLOR: Int = Color.parseColor("#FFFFFFFF")
private val DEFAULT_TEXT_COLOR: Int = Color.parseColor("#FFFFFFFF")
/**
* 默认进度
*/
private val DEFAULT_MAX: Float = 100f
private val DEFAULT_PROGRESS: Float = 0f
/**
* 进度条最大值
*/
var mMax: Float = DEFAULT_MAX
set(value) {
field = value
invalidate()
}
var mMin: Float = DEFAULT_PROGRESS
set(value) {
field = value
invalidate()
}
/**
* 进度条当前进度值
*/
@set:Synchronized
var mProgress: Float = DEFAULT_PROGRESS
set(value) {
if (value == field) {
// No change from current.
return
}
field = value.coerceIn(mMin, mMax)
invalidate()
}
/**
* 进度条颜色
* 完成
*/
var mProgressColor = DEFAULT_PROGRESS_COLOR
set(value) {
field = value
invalidate()
}
/**
* 进度条颜色
* 剩余
*/
var mRemainColor = DEFAULT_REMAIN_COLOR
set(value) {
field = value
invalidate()
}
/**
* 进度条高度
*/
var mBarHeight = 30f.dp
set(value) {
field = value
invalidate()
}
/**
* 剩余进度区域
*/
private val mRemainRectF = RectF(0f, 0f, 0f, 0f)
/**
* 已完成进度区域
*/
private val mProgressRectF = RectF(0f, 0f, 0f, 0f)
/**
* ANTI_ALIAS_FLAG 抗锯齿
*/
private val mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
/**
* 笔画凸出成半圆形
*/
strokeCap = Paint.Cap.ROUND
/**
* 防抖动
*/
isAntiAlias = true
}
/**
* 进度文字颜色
*/
private var mTextColor = Color.WHITE
private var mTextVisibility = false
private var progressFormat = DecimalFormat("#")
init {
initArr(attrs)
}
/**
* 读取自定义的布局属性
*/
private fun initArr(attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ZProgressBar2)
typedArray.run {
mMax = getFloat(R.styleable.ZProgressBar2_zMax, mMax)
mProgress = getFloat(R.styleable.ZProgressBar2_zProgress, mProgress)
mProgressColor =
getColor(R.styleable.ZProgressBar2_zProgressColor, DEFAULT_PROGRESS_COLOR)
mRemainColor =
getColor(R.styleable.ZProgressBar2_zRemainColor, DEFAULT_REMAIN_COLOR)
mBarHeight = getDimension(R.styleable.ZProgressBar2_zBarHeight, 30f.dp)
mTextColor = getColor(
R.styleable.ZProgressBar2_zTextColor,
DEFAULT_REMAIN_COLOR
)
mTextVisibility =
getBoolean(R.styleable.ZProgressBar2_zTextVisibility, true)
}
typedArray.recycle()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
calculateProgressRect()
canvas?.run {
mPaint.color = mRemainColor
drawRoundRect(mRemainRectF, mBarHeight / 2, mBarHeight / 2, mPaint)
mPaint.color = mProgressColor
drawRoundRect(mProgressRectF, mBarHeight / 2, mBarHeight / 2, mPaint)
mPaint.textSize = mBarHeight * 0.5f
var mCurrentDrawText: String = progressFormat.format(mProgress * 100 / mMax)
mPaint.color = mTextColor
val mDrawTextWidth = mPaint.measureText(mCurrentDrawText)
if (mTextVisibility && mProgress > 0 && mProgressRectF.right > mDrawTextWidth) {
drawText(
mCurrentDrawText,
mProgressRectF.right - mDrawTextWidth - mBarHeight * 0.4f,
(height / 2.0f - (mPaint.descent() + mPaint.ascent()) / 2.0f).toInt().toFloat(),
mPaint
)
}
}
}
private fun calculateProgressRect() {
val ttop = (height - mBarHeight) / 2.0f
val bbottom = (height + mBarHeight) / 2.0f
mProgressRectF.run {
left = paddingLeft.toFloat()
top = ttop
/**
* 计算已完成进度的长度
*/
right = (width - paddingLeft - paddingRight) / (mMax * 1.0f) * mProgress + paddingLeft
bottom = bbottom
}
mRemainRectF.run {
left = paddingLeft.toFloat()
top = ttop
right = (width - paddingRight).toFloat()
bottom = bbottom
}
}
}
<com.nf.module_test.seekbar.custom.ZProgressBar2
android:id="@+id/zpb_01"
android:layout_width="match_parent"
android:layout_height="40dp"
app:zBarHeight="40dp"
app:zTextColor="#ffffff"
app:zMax="100"
app:zProgress="50" />
效果如下
3、圆形ProgressBar
嘻嘻嘻,直的写出来了,圆的也的有一个吧,交互也得有吧?就在这个基础上,我们再扩展下
(1)依然我们需要准备好Attribute
这里我们直接在原来的基础上加一点吧
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ZProgressBar2">
<attr name="zTextColor" format="color" />
<attr name="zBarHeight" format="dimension" />
<attr name="zMax" format="float" />
<attr name="zProgress" format="float" />
<attr name="zProgressColor" format="string" />
<attr name="zRemainColor" format="string" />
<attr name="zTextVisibility" format="boolean" />
<attr name="zShape" format="integer">
<enum name="line" value="0"/>
<enum name="oval" value="1"/>
</attr>
<attr name="zFullDegree" format="integer" />
<attr name="zAnimationDuration" format="integer" />
<attr name="zDragEnabled" format="boolean" />
</declare-styleable>
</resources>
(2)继续在ZProgressBar2中添加一些变量声明
还有一些变量在后面看吧
/**
* 样式
*/
private var mShape = DEFAULT_SHAPE
/**
* 进度条的角度
*/
private var mMaxDegree: Int = DEFAULT_FULL_DEGREE
set(value) {
field = value.coerceIn(0,360)
}
/**
* Thumb对象
*/
private var mThumbDraw: Drawable? = null
/**
* Thumb对象限制宽高
*/
private var mThumbRadius = DEFAULT_BAR_WIDTH * 1.2f
/**
* TODO
*/
private var mAnimationDuration: Int = 0
/**
* 是否可以拖动
*/
private var mDragEnabled: Boolean = true
/**
* 圆弧的半径
*/
private var mCircleRadius: Float = 0f
/**
* 圆弧圆心位置
*/
private var centerX: Int = 0
/**
* 圆弧圆心位置
*/
private var centerY: Int = 0
private var mWidth: Int = 0
private var mHeight: Int = 0
(3)准备好读取Attribute的方法
这里我们接着之前的写好就行了
private fun initArr(attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ZProgressBar2)
typedArray.run {
mMax = getFloat(R.styleable.ZProgressBar2_zMax, mMax)
mProgress = getFloat(R.styleable.ZProgressBar2_zProgress, mProgress)
mProgressColor = getColor(R.styleable.ZProgressBar2_zProgressColor, DEFAULT_PROGRESS_COLOR)
mRemainColor = getColor(R.styleable.ZProgressBar2_zRemainColor, DEFAULT_REMAIN_COLOR)
mBarHeight = getDimension(R.styleable.ZProgressBar2_zBarHeight, DEFAULT_BAR_WIDTH)
mTextColor = getColor(R.styleable.ZProgressBar2_zTextColor,DEFAULT_REMAIN_COLOR)
mTextVisibility = getBoolean(R.styleable.ZProgressBar2_zTextVisibility, tr
mMaxDegree = getInteger(R.styleable.ZProgressBar2_zFullDegree, DEFAULT_FULL_DEGREE)
mAnimationDuration = getInteger(R.styleable.ZProgressBar2_zAnimationDuration, 500)
mDragEnabled = getBoolean(R.styleable.ZProgressBar2_zDragEnabled, DEFAULT_ENABLE_TOUCH)
mShape = getInteger(R.styleable.ZProgressBar2_zShape, DEFAULT_SHAPE)
mThumbDraw = getDrawable(R.styleable.ZProgressBar2_zThumbDrawable)
mThumbRadius = getDimension(R.styleable.ZProgressBar2_zThumbRadius, DEFAULT_BAR_WIDTH * 1.2f)
}
typedArray.recycle()
}
(4)处理onMeasure
/**
* left:矩形左边线条离 y 轴的距离
* top:矩形上面线条离 x 轴的距离
* right:矩形右边线条离 y 轴的距离
* bottom:矩形底部线条离 x 轴的距离
* 这里我没有专门去处理layout_height、width,只处理固定宽高,交给你自己做啦
*/
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (mWidth == 0 || mHeight == 0) {
mWidth = measuredWidth
mHeight = measuredHeight
//两者取最大
mCircleRadius = mWidth.coerceAtMost(mHeight) / 2f
//给BarHeight一个默认值,如果没有
if (mBarHeight <= 0) mBarHeight = mCircleRadius / 12f
//实际的绘制需要考虑到Thumb可能会超出中心圆一点点,所以减掉,如果没有就去减掉进度条的
mCircleRadius -= if (mThumbDraw == null) {
mBarHeight * 0.6f
} else {
mThumbRadius * 0.6f
}
centerX = mWidth / 2
centerY = mHeight / 2
//进度的弧形就是靠这个
mProgressRectF.run {
left = centerX - mCircleRadius
top = centerY - mCircleRadius
right = centerX + mCircleRadius
bottom = centerY + mCircleRadius
}
}
}
(5)处理下直线和圆的onDraw
这里通过读取的mShape,来判断是显示哪个。onDrawDir就是前面写的直线bar啦
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
if (mShape == 0) {
onDrawDir(canvas)
} else {
onDrawArc(canvas)
}
}
思考下,因为我们是支持指定最大弧度的,所以进度条其实就是一个扇形,然后只画边就行了(否则你也可以用两个圆重叠达到一样的效果)
一个扇形画当前进度,一个画剩余进度,完事。再到中间画一个文字,进度条前端画一个thumb~~~
private fun onDrawArc(canvas: Canvas?) {
canvas?.run {
/**
* 进度条起始点
* shr 1 就是/2的意思
*/
val startAngle: Float = (90 + ((360 - mMaxDegree) shr 1)).toFloat()
//当前进度的百分比
val sweep1: Float = mMaxDegree * (mProgress / mMax)
//剩余进度的百分比
val sweep2: Float = mMaxDegree - sweep1
//因为我们是画进度条的,只需要画周边就行了,所用空心 描边
mBarPaint.style = Paint.Style.STROKE
//画笔的strokeWidth就相当于进度条宽度了
mBarPaint.strokeWidth = mBarHeight
mBarPaint.color = mRemainColor
//很简单,绘制区域我们已经准备好了mProgressRectF
//剩余起点用’当前进度起点+初始起点‘就是剩余进度的起点,终点显然就是sweep2
drawArc(mProgressRectF, startAngle + sweep1, sweep2, false, mBarPaint)
mBarPaint.color = mProgressColor
//当前进度起点当然就是初始起点,终点当然是sweep1
drawArc(mProgressRectF, startAngle, sweep1, false, mBarPaint)
/**
* 根据三角函数来计算出thumb的XY值
* PS:你可能需要一定的数学知识和Android的坐标系知识
* 如果你不会,记住就行,因为都一样~或者留言
*/
val progressRadians =
(((360.0f - mMaxDegree) /2 + sweep1) / 180 * Math.PI).toFloat()
val thumbX: Float =
centerX - mCircleRadius * sin(progressRadians.toDouble()).toFloat()
val thumbY: Float =
centerY + mCircleRadius * cos(progressRadians.toDouble()).toFloat()
/**
* 根据thumb的半径画出drawable对象
*/
mThumbDraw?.let {
it.setBounds(
(thumbX - mThumbRadius / 2).toInt(), (thumbY - mThumbRadius / 2).toInt(),
(thumbX + mThumbRadius / 2).toInt(), (thumbY + mThumbRadius / 2).toInt())
it.draw(canvas)
}
/**
* 文字绘制
*/
if (mTextVisibility) {
mTextPaint.textSize = (mCircleRadius.toInt() shr 1).toFloat()
mTextPaint.color = Color.parseColor("#FFCB47")
val textProgress: String = progressFormat.format(100f * mProgress / mMax)
val textLen: Float = mTextPaint.measureText(textProgress)
mTextPaint.getTextBounds("8", 0, 1, mTextBounds)
val textProgressHeight: Int = mTextBounds.height()
val extra: Float =
if (textProgress.startsWith("1")) -mTextPaint.measureText("1") / 2 else 0F
/**
* 主要是计算文字的左上角坐标
* 让它画在中间
*/
drawText(
textProgress,
centerX - textLen / 2 + extra,
centerY + textProgressHeight / 2f,
mTextPaint
)
/**
* shr 2 就是/4啦
*/
mTextPaint.textSize = (mCircleRadius.toInt() shr 2).toFloat()
drawText(
"%",
centerX + textLen / 2 + extra + 5,
centerY + textProgressHeight / 2f,
mTextPaint
)
}
}
}
到这儿,绘制就基本结束了。接下啦我们要考虑点击事件的问题了
(6)点击事件的处理
我们直接分为两种,一种点击,一种按住滑动。话不多说,全在注释里
private var isDragging = true
private var lastProgress = -1F
/**
* mDragEnabled变量用于控制是否启用拖拽功能
* checkOnArc方法判断点击是否在圆弧上;
* thumbProgress方法根据点击的位置计算进度并将进度更新到控件上;
* isDragging变量用于标识当前是否在拖拽。
* 当用户点击时,如果检测到点击位置在圆弧上,则执行进度计算并设置isDragging变量为true。
* 当用户拖动时,如果当前正在拖拽,则执行进度计算;
* 当用户抬起手指时,将isDragging设置为false。
* 在方法末尾,返回true,表示该方法已经处理了该事件,不需要再向上传递。
*/
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(event: MotionEvent): Boolean {
if (!mDragEnabled) {
return super.onTouchEvent(event)
}
val currentX = event.x
val currentY = event.y
when (event.action) {
MotionEvent.ACTION_DOWN ->
if (checkOnArc(currentX, currentY)) {
thumbProgress(currentX, currentY)
isDragging = true
}
MotionEvent.ACTION_MOVE -> if (isDragging) {
thumbProgress(currentX, currentY)
}
MotionEvent.ACTION_UP -> isDragging = false
}
return true
}
/**
* 确保滑块的拖动不会在进度条的相反方向进行。
* 首先,通过计算当前点相对于中心点的极角
* 并将其转换为当前进度。
* 接着,将该进度与当前最后一次进度(lastProgress)进行比较
* 如果两者差值小于最大值的一半,则更新当前进度(mProgress)为该进度;
* 否则,当前进度不变。
*/
private fun thumbProgress(currentX: Float, currentY: Float) {
var nextProgress = calculateDegree(currentX, currentY) / mMaxDegree * mMax
nextProgress = nextProgress.coerceAtMost(mMax).coerceAtLeast(mMin)
val delta = abs(nextProgress - lastProgress)
if (delta < mMax / 2 || lastProgress == -1F) {
lastProgress = nextProgress
mProgress = nextProgress
}
}
/**
* 检查距离是否在圆弧的内圈和外圈之间,并检查角度是否在圆弧的开始和结束位置的一定范围内
* 首先调用calculateDistance函数计算给定点(currentX,currentY)与(centerX,centerY)之间的距离。
* 然后调用calculateDegree函数计算给定点与圆心之间的角度。
* 最后,该函数通过比较这个距离和角度与圆弧上的特定范围,来判断给定的点是否在圆弧范围内。
* return 给定的点(currentX,currentY)是否在一个弧形范围内
*/
private fun checkOnArc(currentX: Float, currentY: Float): Boolean {
val distance = calculateDistance(currentX, currentY, centerX.toFloat(), centerY.toFloat())
val degree = calculateDegree(currentX, currentY)
return distance > mCircleRadius - mBarHeight * 3 //点击区域弧内
&& distance < mCircleRadius + mBarHeight * 3 //点击区域弧外
&& degree >= -8 && //圆弧开始位置
degree <= mMaxDegree + 8 //圆弧结束位置
}
/**
* 计算两点(x1,y1)和(x2,y2)之间的距离
* 通过公式√((x1-x2)²+(y1-y2)²)计算两点间的欧几里得距离
*/
private fun calculateDistance(x1: Float, y1: Float, x2: Float, y2: Float): Float {
return sqrt(((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2)).toDouble()).toFloat()
}
/**
* 计算当前点(currentX,currentY)与圆心(centerX,centerY)之间的角度
* 首先,通过反正切函数atan计算出角度a1
* 接着,根据当前点的位置与圆心的关系,分三种情况讨论
* 如果当前点在圆心下方,则在a1上加180°
* 如果当前点在圆心上方且在圆心右方,则在a1上加360°
* 最后,减去360°与mMaxDegree的差的一半,并返回结果
* 这样就得到了当前点相对于圆心的角度值,并限制在了mMaxDegree范围内。
*/
private fun calculateDegree(currentX: Float, currentY: Float): Float {
var a1 =
(atan((1.0f * (centerX - currentX) / (currentY - centerY)).toDouble()) / Math.PI * 180).toFloat()
if (currentY < centerY) {
a1 += 180f
} else if (currentY > centerY && currentX > centerX) {
a1 += 360f
}
return a1 - (360 - mMaxDegree) / 2
}
(7)完成!
调用下~
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:layout_height="wrap_content">
<com.nf.module_test.seekbar.custom.ZProgressBar2
android:id="@+id/zpb_01"
android:layout_width="100dp"
android:layout_height="100dp"
app:zBarHeight="5dp"
app:zTextColor="#ffffff"
app:zShape="oval"
app:zFullDegree="90"
app:zThumbDrawable="@drawable/icon_seekbar_thum"
app:zMax="100"
app:zProgress="50" />
<com.nf.module_test.seekbar.custom.ZProgressBar2
android:id="@+id/zpb_02"
android:layout_width="100dp"
android:layout_height="100dp"
app:zBarHeight="5dp"
app:zFullDegree="180"
app:zMax="100"
app:zProgress="50"
app:zShape="oval"
app:zTextColor="#ffffff"
app:zThumbDrawable="@drawable/icon_seekbar_thum" />
<com.nf.module_test.seekbar.custom.ZProgressBar2
android:id="@+id/zpb_03"
android:layout_width="100dp"
android:layout_height="100dp"
app:zBarHeight="5dp"
app:zTextColor="#ffffff"
app:zShape="oval"
app:zFullDegree="360"
app:zThumbDrawable="@drawable/icon_seekbar_thum"
app:zMax="100"
app:zProgress="50" />
</LinearLayout>
看看效果
完整代码ZProgressBar2.kt 和 完整代码attr.xml
还有很多功能,都没有做完,不过这些看完,相信你已经可以自己接着来了,个人认为,自定义View的核心都在如何准确的计算View的坐标,那么这个圆形的相关,基本都在三角函数上啦。
结尾
这个系列已经3篇了,还有很多奇怪的进度条我都没有写,因为写了3篇了我感觉我的脑子里都是进度条,所以先暂停一会啦,当然如果谁真的很需要下一篇,评论、私信、邮箱我,我会速更第四篇,嘻嘻x.x
那么下篇见
“开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 3 天,点击查看活动详情”