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) -> 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)
	       }
	   }
	}
	

四、处理滑动事件

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


	/**
	 * 处理拖动事件
	 */
	 @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和文本获取到绘制区域的 topbottom值如下图所示,这就造成为了绘制后不垂直的原因。因为文本绘制基线涉及需要大量篇幅去说明,这里就不详细解释。那要如何处理呢?其实很简单,让文本的基线
垂直于Y轴即可。
Text BaseLine

解决方法:

把绘制文本的topbottom取中间值作为垂直于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开关

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
非常感谢您的提问,我可以为您提供以下 Kotlin 代码,用于创建一个自定义的渐变色进度条: ``` import android.content.Context import android.graphics.* import android.util.AttributeSet import android.view.View class GradientProgressBar(context: Context, attrs: AttributeSet) : View(context, attrs) { private var progress = 0 private var maxProgress = 100 private var progressColor = Color.BLUE private var backgroundColor = Color.GRAY private val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG) private val progressPaint = Paint(Paint.ANTI_ALIAS_FLAG) init { val typedArray = context.obtainStyledAttributes(attrs, R.styleable.GradientProgressBar) progressColor = typedArray.getColor(R.styleable.GradientProgressBar_progressColor, Color.BLUE) backgroundColor = typedArray.getColor(R.styleable.GradientProgressBar_backgroundColor, Color.GRAY) typedArray.recycle() } override fun onDraw(canvas: Canvas) { super.onDraw(canvas) val width = width.toFloat() val height = height.toFloat() // Draw background backgroundPaint.color = backgroundColor canvas.drawRect(0f, 0f, width, height, backgroundPaint) // Draw progress progressPaint.shader = LinearGradient(0f, 0f, width, height, intArrayOf(Color.RED, progressColor, Color.YELLOW), null, Shader.TileMode.CLAMP) val progressWidth = progress * width / maxProgress canvas.drawRect(0f, 0f, progressWidth, height, progressPaint) } fun setProgress(progress: Int) { this.progress = progress invalidate() } fun setMaxProgress(maxProgress: Int) { this.maxProgress = maxProgress invalidate() } fun setProgressColor(progressColor: Int) { this.progressColor = progressColor invalidate() } fun setBackgroundColor(backgroundColor: Int) { this.backgroundColor = backgroundColor invalidate() } } ``` 您可以在布局文件中使用以下代码来添加自定义的进度条: ``` <com.example.GradientProgressBar android:layout_width="match_parent" android:layout_height="10dp" app:progressColor="#FF4081" app:backgroundColor="#E0E0E0"/> ``` 其中,`progressColor` 和 `backgroundColor` 属性可以用来设置进度条的前景色和背景色。您可以根据需要进行修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值