1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > Android自定义控件(一) 可滑动的进度条

Android自定义控件(一) 可滑动的进度条

时间:2023-11-05 20:59:50

相关推荐

Android自定义控件(一) 可滑动的进度条

前言

本篇文章记录通过自定义View实现Android下可滑动的进度条学习巩固自定义View知识

说明

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.SlideViewattrs.xml中声明的属性

/*** 进度条监听,回调到外面*/private lateinit var listener:(progress:Int) -> Unitfun onProgressChange(l:(progress:Int) ->Unit){this.listener = l}/*** 背景条*/private val backgroundPaint = Paint().apply {style = Paint.Style.FILLstrokeCap = Paint.Cap.ROUND}/*** 进度条画笔*/private val progressPaint = Paint().apply {style = Paint.Style.FILLstrokeCap = Paint.Cap.ROUND}/*** 内实心圆画笔*/private val innerCirclePaint = Paint().apply {style = Paint.Style.FILLstrokeWidth = 10fcolor = Color.WHITE}/*** 外圆画笔*/private val outerCirclePaint = Paint().apply {style = Paint.Style.STROKEstrokeWidth = 2f}/*** 文字画笔*/private val textPaint = Paint().apply {style = Paint.Style.FILLtextSize = 40fcolor = 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)}}}

四、处理滑动事件

重写ViewOnTouchEvent方法,需要判断手指按下的区域在外圆的坐标值内,滑动的范围要限制在startXendX之间

/*** 处理拖动事件*/@SuppressLint("ClickableViewAccessibility")override fun onTouchEvent(event: MotionEvent): Boolean {var cx = event.xval cy = event.ywhen(event.action){MotionEvent.ACTION_DOWN ->{//判断手指按下区域是否在句柄圆上, 左右和上下有效触摸区域扩大各40fisOnTouch = (cx > progressValue - radius - 20f && cx < progressValue + radius + 20f && cy > -20f && cy < 2 * radius + 20f)}MotionEvent.ACTION_MOVE ->{if (isOnTouch){//限制最小值为起点startXif(cx < startX){cx = startX.toFloat() }//限制最大值为终点endXelse 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.FILLtextSize = 40fcolor = 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和文本获取到绘制区域的topbottom值如下图所示,这就造成为了绘制后不垂直的原因。因为文本绘制基线涉及需要大量篇幅去说明,这里就不详细解释。那要如何处理呢?其实很简单,让文本的基线

垂直于Y轴即可。

解决方法:

把绘制文本的topbottom取中间值作为垂直于Y轴的基线带入计算即可

//文字绘制的基线va baseLine = rect?.let {(rect?.top!! + rect?.bottom!!)/ 2}!!Log.d("AAAAAA","baseLine = $baseLine")输出: baseLine = 13if(showProgressText){drawText("${(((progressValue - startX) / totalLen) * 100).toInt()} %", endX + 40f ,radius + 2f - baseLine ,textPaint)}

六、布局文件中使用

Xml中:<com.xn.customview.widget.SlideViewandroid: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开关

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。