1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Android 自定义 View 实现转盘功能

Android 自定义 View 实现转盘功能

时间:2022-07-21 20:24:04

相关推荐

Android 自定义 View 实现转盘功能

先上效果图。

转盘绘制

通过观察,我们看到转盘主要由圆和扇形组成,如下图

我们只需按照我们需要的数量画出扇形即可。

第一步:画大圆

private fun drawLargeCircle(canvas: Canvas?) {val centerX = measuredWidth / 2Fval centerY = measuredHeight / 2Fval radius = measuredWidth / 2FmPaint.color = bgColorcanvas?.drawCircle(centerX, centerY, radius - 10, mPaint)mPaint.color = centerStrokeColormPaint.style = Paint.Style.STROKEfor (i in 0 until 10) {mPaint.alpha = 5 * (i + 1)canvas?.drawCircle(centerX, centerY, radius - i, mPaint)}}

第二步:画扇形(小圆只是为了确定扇形圆心的位置,并不用真正画出来)

private fun drawSector(canvas: Canvas?) {val sectorRadius = (measuredWidth / 2) * sectorProportionval sectorCenterRadius = (measuredWidth / 2) * 0.07fval centerX = measuredWidth / 2Fval centerY = measuredHeight / 2FmPaint.color = sectorColormPaint.style = Paint.Style.FILLvar angle = 360F / selectList.sizefor (i in 0 until selectList.size) {if ((i + 2) % selectList.size == selectedIndex) {mPaint.color = startColor} else {mPaint.color = sectorColor}val cx = centerX + cos(angleToRadian(angle * i)).toFloat() * sectorCenterRadiusval cy = centerY + sin(angleToRadian(angle * i)).toFloat() * sectorCenterRadiusval oval = RectF(cx - sectorRadius, cy - sectorRadius, cx + sectorRadius, cy + sectorRadius)canvas?.drawArc(oval, (angle * i) - (angle / 2), angle, true, mPaint)}mPaint.color = sectorColormPaint.alpha = 50val littleCircleCount = selectList.size * 2angle = 360F / littleCircleCountfor (i in 0 until littleCircleCount) {val littleCenterX = centerX + cos(angleToRadian(angle * i)).toFloat() * (((measuredWidth / 2) + sectorRadius + sectorCenterRadius) / 2F - 5)val littleCenterY = centerY + sin(angleToRadian(angle * i)).toFloat() * (((measuredWidth / 2) + sectorRadius + sectorCenterRadius) / 2F - 5)canvas?.drawCircle(littleCenterX, littleCenterY, (measuredWidth / 2) * littleCircleProportion, mPaint)}

第三步:绘制其他元素

private fun drawCenter(canvas: Canvas?) {val centerX = measuredWidth / 2Fval centerY = measuredHeight / 2FmPaint.color = bgColormPaint.alpha = 255canvas?.drawCircle(centerX, centerY, (measuredWidth / 2) * centerProportion, mPaint)mPaint.style = Paint.Style.STROKEmPaint.color = centerStrokeColormPaint.strokeWidth = 1Ffor (i in 0..(centerX * centerDistanceProportion).toInt()) {mPaint.alpha = 5 * icanvas?.drawCircle(centerX, centerY, centerX * centerProportion - i, mPaint)}mPaint.color = bgColormPaint.style = Paint.Style.FILLcanvas?.drawCircle(centerX, centerY, (centerX * centerProportion) - (centerX * centerDistanceProportion), mPaint)mPaint.color = centerBottomColorcanvas?.drawCircle(centerX, centerY, (centerX * centerProportion) - ((centerX * centerDistanceProportion) * 1.5F), mPaint)if (isClickable) {mPaint.color = startColor} else {mPaint.color = bgColor}canvas?.drawCircle(centerX, centerY, (centerX * centerProportion) - ((centerX * centerDistanceProportion) * 2), mPaint)}private fun drawText(canvas: Canvas?) {val radius = measuredWidth / 3Fval centerX = measuredWidth / 2Fval centerY = measuredHeight / 2Fval angle = 360F / selectList.sizemPaint.color = sectorTextColormPaint.textSize = centerX * sectorTextSizeProportionfor (i in 0 until selectList.size) {if (selectedIndex == (i + 2) % selectList.size) {mPaint.color = Color.WHITE} else {mPaint.color = sectorTextColor}val cx = centerX + cos(angleToRadian(angle * i)).toFloat() * radius - ((measuredWidth / 2) * sectorTextSizeProportion * 0.42F)val cy = centerY + sin(angleToRadian(angle * i)).toFloat() * radius + ((measuredWidth / 2) * sectorTextSizeProportion * 0.42F)canvas?.drawText(selectList[(i + 2) % selectList.size], cx, cy, mPaint)}if (isClickable) {mPaint.color = startTextColormPaint.textSize = centerX * sectorTextSizeProportion * 0.8ftopText = "开始"canvas?.drawText(topText, centerX * (1 - 0.8F * sectorTextSizeProportion), centerY * (1 + 0.3F * sectorTextSizeProportion), mPaint)} else {mPaint.color = sectorTextColormPaint.textSize = centerX * centerTextProportioncanvas?.drawText(topText, centerX * (1 - 0.3F * centerTextProportion), centerY * (1 + 0.3F * centerTextProportion), mPaint)}}

让转盘动起来

这里的让转盘动起来的方式,是通过依次改变扇形的背景颜色,这个是产品需求,你也可以通过动画让他真正的转起来。

我们需要考虑的一点是,我们必须让转盘可控,即转盘的最终结果在开始转之前我们就已经知道,所以我们需要让转盘转到我们指定的结果。

我们可以给转盘一个初始速度和一个结束速度(即美妙转动的次数),理论上结束速度肯定是小于初始速度的,我们每一秒都使速度减小一次,我们通过控制中间减少多少来控制最终转盘转到哪个。

// 确保最后可以选中要选择的private fun computeSpeed() {if (selectedIndex < 0) {selectedIndex = 0}selectedIndex %= selectList.sizevar distance = if (willSelectIndex >= selectedIndex) {willSelectIndex - selectedIndex} else {selectList.size - selectedIndex + willSelectIndex}distance %= selectList.sizeval speedDistance = (endSpeed - startSpeed) / (selectList.size - 2)speedList.clear()speedList.add(startSpeed)for (i in 1 until selectList.size - 1) {speedList.add(startSpeed + (i * speedDistance))}speedList.add(endSpeed)for (i in 0 until speedList.size) {var count = (1000 / speedList[i])if (count < selectList.size) {count = selectList.size} else {count -= (count % selectList.size)}speedList[i] = 1000 / count}for (i in 0 until distance) {val count = (1000 / speedList[i]) + 1speedList[i] = 1000 / count}}

完整代码

粗略记录,有问题可评论或私信,看到就回。

<declare-styleable name="TurntableView"><attr name="background_color" format="color" /><attr name="sector_color" format="color" /><attr name="center_stroke_color" format="color" /><attr name="center_bottom_color" format="color" /><attr name="sector_text_color" format="color" /><attr name="start_btn_color" format="color" /><attr name="start_text_color" format="color" /></declare-styleable>

import android.content.Contextimport android.graphics.Canvasimport android.graphics.Colorimport android.graphics.Paintimport android.graphics.RectFimport android.util.AttributeSetimport android.view.MotionEventimport android.view.Viewimport androidx.core.content.ContextCompatimport com.baijiayun.liveuibase.Rimport io.reactivex.Observableimport io.reactivex.android.schedulers.AndroidSchedulersimport io.reactivex.disposables.Disposableimport java.util.concurrent.TimeUnitimport kotlin.math.PIimport kotlin.math.cosimport kotlin.math.minimport kotlin.math.sin/*** 转盘工具* @author 陈鹏伟* @date /6/14*/class TurntableView: View {constructor(context: Context) : super(context) {init(null)}constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {init(attrs)}constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {init(attrs)}constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {init(attrs)}// 默认尺寸private val defaultSize = 150// 扇形半径比例private val sectorProportion = 0.84F// 外圈小圆半径比例private val littleCircleProportion = 0.02F// 中心圆比例private val centerProportion = 0.35F// 中心圆间隔比例private val centerDistanceProportion = 0.05F// 转盘文字比例private val sectorTextSizeProportion = 0.2F// 中心文字比例private val centerTextProportion = 0.3F// 背景颜色(大圆)颜色private var bgColor = 0// 扇形颜色private var sectorColor = 0// 中心圆圈边框颜色private var centerStrokeColor = 0// 中心圆圈底层颜色private var centerBottomColor = 0// 开始按钮背景颜色 = 扇形选中背景颜色private var startColor = 0// 扇形文字颜色private var sectorTextColor = 0// 开始按钮文字颜色 = 扇形选中文字颜色private var startTextColor = 0// 选项列表private var selectList = arrayListOf("1", "2", "3", "4", "5", "6")// 当前选中的选项下标private var selectedIndex = -1// 即将选中的选项下标private var willSelectIndex = 4// 转盘转动的时间 secondsprivate var turnTime = 5// 转盘旋转private var disposableOfTurn: Disposable? = null// 转盘按钮文字private var topText = ""// 初始速度 40ms 一个private var startSpeed = 40// 结束速度 200ms 一个private var endSpeed = 200// 速度列表 1s 一个速度private var speedList = mutableListOf<Int>()// 当前速度private var currentSpeed = 1// 初始是否可点击private var initialClickable = falseprivate val mPaint by lazy {Paint()}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {val width = getRealSize(widthMeasureSpec)val height = getRealSize(heightMeasureSpec)val final = min(width, height)setMeasuredDimension(final, final)}override fun onDraw(canvas: Canvas?) {super.onDraw(canvas)mPaint.isAntiAlias = truedrawLargeCircle(canvas)drawSector(canvas)drawCenter(canvas)drawText(canvas)}override fun onTouchEvent(event: MotionEvent?): Boolean {return when (event?.action) {MotionEvent.ACTION_DOWN -> {if (isClickCenter(event.x, event.y)) {performClick()}return isClickCenter(event.x, event.y)}MotionEvent.ACTION_MOVE -> {return false}MotionEvent.ACTION_UP -> {return false}else -> super.onTouchEvent(event)}}override fun setClickable(clickable: Boolean) {super.setClickable(clickable)invalidate()}override fun performClick(): Boolean {return super.performClick()}/*** 设置选项列表* @param list 选项列表,size >= 3*/fun setSelectList(list: ArrayList<String>) {if (list.size < 3) {return}selectList = list}/*** 设置要选中的选项下标* @param index 选项下标*/private fun setWillSelectIndex(index: Int) {if (willSelectIndex != index) {willSelectIndex = index}if (willSelectIndex >= selectList.size) {willSelectIndex = selectList.size - 1}if (willSelectIndex < 0) {willSelectIndex = 0}willSelectIndex = (willSelectIndex + 3) % selectList.size}/*** 设置转盘转动时间* @param time 转盘转动时间 s*/private fun setTurnTime(time: Int) {turnTime = time}/*** 设置背景颜色*/override fun setBackgroundColor(color: Int) {this.bgColor = colorinvalidate()}/*** 设置扇形颜色*/fun setSectorColor(color: Int) {this.sectorColor = colorinvalidate()}/*** 设置阴影颜色*/fun setStrokeColor(color: Int) {this.centerStrokeColor = colorinvalidate()}/*** 设置中心圆底层颜色*/fun setCenterBottomColor(color: Int) {this.centerBottomColor = colorinvalidate()}/*** 设置开始按钮背景颜色和扇形选中颜色*/fun setStartBackgroundColor(color: Int) {this.startColor = colorinvalidate()}/*** 设置扇形文字颜色*/fun setSectorTextColor(color: Int) {this.sectorTextColor = colorinvalidate()}/*** 设置开始按钮文字颜色*/fun setStartTextColor(color: Int) {this.startTextColor = colorinvalidate()}/*** 开始转动转盘* @param selectIndex 最终要选中的值* @param time 转盘转动时间*/fun startTurn(selectIndex: String, time: Int) {setWillSelectIndex(selectList.indexOf(selectIndex))release()setTurnTime(time)topText = turnTime.toString()invalidate()computeSpeed()currentSpeed = speedList[0]initialClickable = isClickableisClickable = falsedisposableOfTurn = Observable.interval(0, 1, TimeUnit.MILLISECONDS).observeOn(AndroidSchedulers.mainThread()).subscribe {if (it >= currentSpeed && it % currentSpeed == 0L) {selectedIndex ++selectedIndex %= selectList.sizeinvalidate()}if (it >= 1000 && it % 1000 == 0L) {currentSpeed = speedList[(it / 1000).toInt()]topText = (turnTime - it / 1000).toString()invalidate()}if (it == turnTime * 1000L + 500) {if (initialClickable) {isClickable = truetopText = "开始"} else {topText = ""}disposableOfTurn?.dispose()invalidate()}}}/*** 转盘是否正在转动*/fun isTurning() = disposableOfTurn != null && !disposableOfTurn!!.isDisposed/*** 释放资源,父容器销毁时须调用*/fun release() {disposableOfTurn?.dispose()}private fun init(attrs: AttributeSet?) {val typedArray = context.obtainStyledAttributes(attrs, R.styleable.TurntableView)bgColor = typedArray.getColor(R.styleable.TurntableView_background_color, ContextCompat.getColor(context, R.color.bjy_base_turntable_default_background_color))sectorColor = typedArray.getColor(R.styleable.TurntableView_sector_color, ContextCompat.getColor(context, R.color.bjy_base_turntable_default_sector_color))centerStrokeColor = typedArray.getColor(R.styleable.TurntableView_center_stroke_color, ContextCompat.getColor(context, R.color.bjy_base_turntable_default_center_stroke_color))centerBottomColor = typedArray.getColor(R.styleable.TurntableView_center_bottom_color, ContextCompat.getColor(context, R.color.bjy_base_turntable_default_center_bottom_color))sectorTextColor = typedArray.getColor(R.styleable.TurntableView_sector_text_color, ContextCompat.getColor(context, R.color.bjy_base_turntable_default_sector_text_color))startColor = typedArray.getColor(R.styleable.TurntableView_start_btn_color, ContextCompat.getColor(context, R.color.bjy_base_turntable_default_start_btn_color))startTextColor = typedArray.getColor(R.styleable.TurntableView_start_text_color, ContextCompat.getColor(context, R.color.bjy_base_turntable_default_start_text_color))typedArray.recycle()}private fun getRealSize(measureSpec: Int): Int {val mode = MeasureSpec.getMode(measureSpec)val size = MeasureSpec.getSize(measureSpec)return if (mode == MeasureSpec.EXACTLY) {size} else {defaultSize}}private fun drawLargeCircle(canvas: Canvas?) {val centerX = measuredWidth / 2Fval centerY = measuredHeight / 2Fval radius = measuredWidth / 2FmPaint.color = bgColorcanvas?.drawCircle(centerX, centerY, radius - 10, mPaint)mPaint.color = centerStrokeColormPaint.style = Paint.Style.STROKEfor (i in 0 until 10) {mPaint.alpha = 5 * (i + 1)canvas?.drawCircle(centerX, centerY, radius - i, mPaint)}}private fun drawSector(canvas: Canvas?) {val sectorRadius = (measuredWidth / 2) * sectorProportionval sectorCenterRadius = (measuredWidth / 2) * 0.07fval centerX = measuredWidth / 2Fval centerY = measuredHeight / 2FmPaint.color = sectorColormPaint.style = Paint.Style.FILLvar angle = 360F / selectList.sizefor (i in 0 until selectList.size) {if ((i + 2) % selectList.size == selectedIndex) {mPaint.color = startColor} else {mPaint.color = sectorColor}val cx = centerX + cos(angleToRadian(angle * i)).toFloat() * sectorCenterRadiusval cy = centerY + sin(angleToRadian(angle * i)).toFloat() * sectorCenterRadiusval oval = RectF(cx - sectorRadius, cy - sectorRadius, cx + sectorRadius, cy + sectorRadius)canvas?.drawArc(oval, (angle * i) - (angle / 2), angle, true, mPaint)}mPaint.color = sectorColormPaint.alpha = 50val littleCircleCount = selectList.size * 2angle = 360F / littleCircleCountfor (i in 0 until littleCircleCount) {val littleCenterX = centerX + cos(angleToRadian(angle * i)).toFloat() * (((measuredWidth / 2) + sectorRadius + sectorCenterRadius) / 2F - 5)val littleCenterY = centerY + sin(angleToRadian(angle * i)).toFloat() * (((measuredWidth / 2) + sectorRadius + sectorCenterRadius) / 2F - 5)canvas?.drawCircle(littleCenterX, littleCenterY, (measuredWidth / 2) * littleCircleProportion, mPaint)}}private fun drawCenter(canvas: Canvas?) {val centerX = measuredWidth / 2Fval centerY = measuredHeight / 2FmPaint.color = bgColormPaint.alpha = 255canvas?.drawCircle(centerX, centerY, (measuredWidth / 2) * centerProportion, mPaint)mPaint.style = Paint.Style.STROKEmPaint.color = centerStrokeColormPaint.strokeWidth = 1Ffor (i in 0..(centerX * centerDistanceProportion).toInt()) {mPaint.alpha = 5 * icanvas?.drawCircle(centerX, centerY, centerX * centerProportion - i, mPaint)}mPaint.color = bgColormPaint.style = Paint.Style.FILLcanvas?.drawCircle(centerX, centerY, (centerX * centerProportion) - (centerX * centerDistanceProportion), mPaint)mPaint.color = centerBottomColorcanvas?.drawCircle(centerX, centerY, (centerX * centerProportion) - ((centerX * centerDistanceProportion) * 1.5F), mPaint)if (isClickable) {mPaint.color = startColor} else {mPaint.color = bgColor}canvas?.drawCircle(centerX, centerY, (centerX * centerProportion) - ((centerX * centerDistanceProportion) * 2), mPaint)}private fun drawText(canvas: Canvas?) {val radius = measuredWidth / 3Fval centerX = measuredWidth / 2Fval centerY = measuredHeight / 2Fval angle = 360F / selectList.sizemPaint.color = sectorTextColormPaint.textSize = centerX * sectorTextSizeProportionfor (i in 0 until selectList.size) {if (selectedIndex == (i + 2) % selectList.size) {mPaint.color = Color.WHITE} else {mPaint.color = sectorTextColor}val cx = centerX + cos(angleToRadian(angle * i)).toFloat() * radius - ((measuredWidth / 2) * sectorTextSizeProportion * 0.42F)val cy = centerY + sin(angleToRadian(angle * i)).toFloat() * radius + ((measuredWidth / 2) * sectorTextSizeProportion * 0.42F)canvas?.drawText(selectList[(i + 2) % selectList.size], cx, cy, mPaint)}if (isClickable) {mPaint.color = startTextColormPaint.textSize = centerX * sectorTextSizeProportion * 0.8ftopText = "开始"canvas?.drawText(topText, centerX * (1 - 0.8F * sectorTextSizeProportion), centerY * (1 + 0.3F * sectorTextSizeProportion), mPaint)} else {mPaint.color = sectorTextColormPaint.textSize = centerX * centerTextProportioncanvas?.drawText(topText, centerX * (1 - 0.3F * centerTextProportion), centerY * (1 + 0.3F * centerTextProportion), mPaint)}}private fun angleToRadian(angle: Float) = angle * PI / 180private fun isClickCenter(x: Float, y: Float): Boolean {val centerX = measuredWidth / 2val centerY = measuredHeight / 2val radius = (measuredWidth / 2) * centerProportionif (x < centerX - radius || x > centerX + radius) {return false}if (y > centerY + radius || y < centerY - radius) {return false}return true}// 确保最后可以选中要选择的private fun computeSpeed() {if (selectedIndex < 0) {selectedIndex = 0}selectedIndex %= selectList.sizevar distance = if (willSelectIndex >= selectedIndex) {willSelectIndex - selectedIndex} else {selectList.size - selectedIndex + willSelectIndex}distance %= selectList.sizeval speedDistance = (endSpeed - startSpeed) / (selectList.size - 2)speedList.clear()speedList.add(startSpeed)for (i in 1 until selectList.size - 1) {speedList.add(startSpeed + (i * speedDistance))}speedList.add(endSpeed)for (i in 0 until speedList.size) {var count = (1000 / speedList[i])if (count < selectList.size) {count = selectList.size} else {count -= (count % selectList.size)}speedList[i] = 1000 / count}for (i in 0 until distance) {val count = (1000 / speedList[i]) + 1speedList[i] = 1000 / count}}}

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