赞
踩
1、实现效果
文中实现的效果都是未加抗锯齿
2、View绘制解析
上图自定义View中有文本(大小)
、背景条(灰色)
、进度条(绿色)
、滑动区域(白色内圆)
、外框圆(绿色)
、进度文字等元素,分析清晰元素的属性,代码更容易的去实现。
背景条属性:
起点坐标(startX)、长度(backgroundTotalLen)、颜色(backgroundColor)、线宽(backgroundStrokeW)
进度条属性:
起点坐标(startX)、进度(progress)为0-100、颜色
(progressColor)、线宽(progressStrokeW)
滑块内圆:
半径(handleRadius)
外框圆:
这里设置半径比内圆大2f ,颜色和进度条颜色一致(progressColor)
文本(进度值):
是否显示(showProgressText)
注意:
1、进度条的进度对应的坐标和滑动区域(内圆的坐标中心)、外框圆(坐标中心)一致
2、外框圆的颜色和进度条的颜色一致
3、绘制进度值文本如何与背景条与进度条保持垂直居中?
上面分析了自定义View元素的属性值,下面代码进行实现
一、声明属性文件,在values下新建 attrs.xml文件
新建标签为 declare-styleable
类型的xml,名字SlideView
可自定义,一般和自定义View名保持一致,声明标签和名和对应的类型。
<resources> <declare-styleable name="SlideView"> <!--进度背景颜色--> <attr name="backgroundColor" format="color"/> <!--背景的线宽--> <attr name="backgroundStrokeW" format="float"/> <!--背景总长度--> <attr name="backgroundTotalLen" format="integer"/> <!--起点x坐标--> <attr name="startX" format="integer"/> <!--进度--> <attr name="progress" format="integer"/> <!--进度颜色--> <attr name="progressColor" format="color"/> <!--进度线宽--> <attr name="progressStrokeW" format="float"/> <!--手柄圆半径--> <attr name="handleRadius" format="float"/> <!--是否显示文字进度--> <attr name="showProgressText" format="boolean"/> </declare-styleable> </resources>
二、获取xml文件上的属性值,并给画笔设置属性
attributeSet
为Activity布局文件中声明的属性,R.styleable.SlideView
为attrs.xml
中声明的属性
/** * 进度条监听,回调到外面 */ private lateinit var listener:(progress:Int) -> Unit fun onProgressChange(l:(progress:Int) ->Unit){ this.listener = l } /** * 背景条 */ private val backgroundPaint = Paint().apply { style = Paint.Style.FILL strokeCap = Paint.Cap.ROUND } /** * 进度条画笔 */ private val progressPaint = Paint().apply { style = Paint.Style.FILL strokeCap = Paint.Cap.ROUND } /** * 内实心圆画笔 */ private val innerCirclePaint = Paint().apply { style = Paint.Style.FILL strokeWidth = 10f color = Color.WHITE } /** * 外圆画笔 */ private val outerCirclePaint = Paint().apply { style = Paint.Style.STROKE strokeWidth = 2f } /** * 文字画笔 */ private val textPaint = Paint().apply { style = Paint.Style.FILL textSize = 40f color = Color.BLACK } init{ val ta = context.obtainStyledAttributes(attributeSet,`R.styleable.SlideView`) //获取背景条颜色 backgroundColor = ta.getColor(R.styleable.SlideView_backgroundColor,context.getColor(R.color.colorEC)) //获取背景条线宽 backgroundStrokeW = ta.getFloat(R.styleable.SlideView_backgroundStrokeW,18f) //获取背景条总长 totalLen = ta.getInt(R.styleable.SlideView_backgroundTotalLen,100) //获取进度条颜色 progressColor = ta.getColor(R.styleable.SlideView_progressColor,context.getColor(R.color.colorGrassGreen)) //获取进度条线宽 progressStrokeW = ta.getFloat(R.styleable.SlideView_progressStrokeW,18f) //获取进度条的进度 progress = ta.getInt(R.styleable.SlideView_progress,0) //获取内圆半径 radius = ta.getFloat(R.styleable.SlideView_handleRadius,30.toFloat()) //获取绘制起点 startX = DeviceUtils.dp2px(context,ta.getInt(R.styleable.SlideView_startX,0).toFloat() + DeviceUtils.px2dp(context,radius) + 2f) //是否显示进度值 showProgressText = ta.getBoolean(R.styleable.SlideView_showProgressText,true) ta.recycle() }
三、绘制元素
重写onDraw
方法,绘制背景条
、进度条
、滑动区域(内圆)
、外圆
/** * 绘制元素 */ override fun onDraw(canvas: Canvas) { super.onDraw(canvas) canvas.apply { //绘制背景条 drawLine(startX.toFloat(),endY ,endX.toFloat(),endY,backgroundPaint) //绘制进度条 drawLine(startX.toFloat(),endY, progressValue,endY,progressPaint) //绘制内圆 drawCircle(progressValue,endY, radius,innerCirclePaint) //绘制外圆 drawCircle(progressValue,endY ,radius + 2f,outerCirclePaint) //是否绘制进度值 if(showProgressText){ drawText("${(((progressValue - startX) / totalLen) * 100).toInt()} %", endX + 40f ,radius + 2f - baseLine,textPaint) } } }
四、处理滑动事件
重写View
的OnTouchEvent
方法,需要判断手指按下的区域在外圆
的坐标值内,滑动的范围要限制在startX
和endX
之间
/** * 处理拖动事件 */ @SuppressLint("ClickableViewAccessibility") override fun onTouchEvent(event: MotionEvent): Boolean { var cx = event.x val cy = event.y when(event.action){ MotionEvent.ACTION_DOWN ->{ //判断手指按下区域是否在句柄圆上, 左右和上下有效触摸区域扩大各40f isOnTouch = (cx > progressValue - radius - 20f && cx < progressValue + radius + 20f && cy > -20f && cy < 2 * radius + 20f) } MotionEvent.ACTION_MOVE ->{ if (isOnTouch){ //限制最小值为起点startX if(cx < startX){ cx = startX.toFloat() } //限制最大值为终点endX else if(cx > endX) { cx = endX.toFloat() } progressValue = cx //重新绘制 invalidate() //将进度回调出去 listener.invoke(((progressValue / (endX - startX)) * 100).toInt()) } } MotionEvent.ACTION_UP ->{ isOnTouch = false } } return true }
五、问题点的处理
绘制进度文字时遇到一个问题,就是文字和背景条和进度条无法居中对齐。
if(showProgressText){
drawText("${(((progressValue - startX) / totalLen) * 100).toInt()} %", endX + 40f ,radius + 2f,textPaint)
}
进度值的X坐标在背景条后面,距离为40F
,Y坐标和内圆半径raduis + 外圆半径2F
,按理说应该是和滑动区域是垂直居中的。然后显示起来并没有居中,猜想文本在坐标系中的绘制较其有特殊。
那我们看下文本是怎么绘制在坐标系中的? 在Android中,提供了方法getTextBounds
查看文本的绘制区域。
/** * 文字画笔 */ private val textPaint = Paint().apply { style = Paint.Style.FILL textSize = 40f color = Color.BLACK } rect = Rect() textPaint.getTextBounds("100%",0,"100%".length,rect) Log.d("AAAAAA","left = $left , top = ${rect?.top!!} , right = $right , bottom = ${rect?.bottom}") 输出:left = 0 , top = -29 , right = 0 , bottom = 2
通过打印出来的值,可以发现文本基线
并不是垂直于坐标轴Y轴的,当前Paint
和文本获取到绘制区域的 top
和bottom
值如下图所示,这就造成为了绘制后不垂直的原因。因为文本绘制基线涉及需要大量篇幅去说明,这里就不详细解释。那要如何处理呢?其实很简单,让文本的基线
垂直于Y轴即可。
解决方法:
把绘制文本的top
和bottom
取中间值作为垂直于Y轴的基线
带入计算即可
//文字绘制的基线
va baseLine = rect?.let {
(rect?.top!! + rect?.bottom!!)/ 2
}!!
Log.d("AAAAAA","baseLine = $baseLine")
输出: baseLine = 13
if(showProgressText){
drawText("${(((progressValue - startX) / totalLen) * 100).toInt()} %", endX + 40f ,radius + 2f - baseLine ,textPaint)
}
六、布局文件中使用
Xml中: <com.xn.customview.widget.SlideView android:id="@+id/svAlpha" android:layout_width="@dimen/px_906" android:layout_height="@dimen/px_72" android:layout_gravity="center_vertical" android:layout_marginStart="10dp" app:backgroundColor="@color/colorEC" app:backgroundStrokeW="18" app:backgroundTotalLen="500" app:handleRadius="30" app:progress="50" app:progressColor="@color/colorGrassGreen" app:progressStrokeW="18" app:showProgressText="true" app:startX="0" />
Activity中:
//获取进度回调
mBinding.svAlpha.onProgressChange {
Log.d("AAAAAA","svAlpha progress = $it")
}
mBinding.svSize.onProgressChange {
Log.d("AAAAAA","svSize progress = $it")
}
文章中对文字位置处理不够完善,正确的处理方式请查看文章Android自定义控件(六) Andriod仿iOS控件Switch开关
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。