安卓 车轮视图 WheelView kotlin

news/2024/11/30 18:33:02/

安卓 车轮视图 WheelView kotlin

  • 前言
  • 一、代码解析
    • 1.初始化
    • 2.初始化数据
    • 3.onMeasure
    • 4.onDraw
    • 5.onTouchEvent
    • 6.其他
  • 6.ItemObject
  • 二、完整代码
  • 总结


在这里插入图片描述

前言

有个需求涉及到类似这个视图,于是在网上找了个轮子,自己改吧改吧用,拿来主义当然后,但做事不仅要知其然,还要知其所以然,所以拿来用的同时还要理解。于是就有了本文。


一、代码解析

参数

    /*** 控件宽度*/private var controlWidth = 0f/*** 控件高度*/private var controlHeight = 0f/*** 是否正在滑动** @return*//*** 是否滑动中*/var isScrolling = false/*** 选择的内容*/private val itemList: MutableList<ItemObject>? = mutableListOf()/*** 设置数据*/private var dataList = mutableListOf<String>()/*** 按下的坐标*/private var downY = 0/*** 按下的时间*/private var downTime: Long = 0/*** 短促移动*/private val goonTime: Long = 200/*** 短促移动距离*/private val goonDistance = 100/*** 画线画笔*/private var linePaint: Paint? = null/*** 线的默认颜色*/private var lineColor = -0x1000000/*** 线的默认宽度*/private var lineHeight = 2f/*** 默认字体*/private var normalFont = 14.0f/*** 选中的时候字体*/private var selectedFont = 22.0f/*** 单元格高度*/private var unitHeight = 50/*** 显示多少个内容*/private var itemNumber = 7/*** 默认字体颜色*/private var normalColor = -0x1000000/*** 选中时候的字体颜色*/private var selectedColor = -0x10000/*** 蒙板高度*/private var maskHeight = 48.0f/*** 选择监听*/private var onSelectListener: OnSelectListener? = null/*** 设置是否可用** @param isEnable*/var isEnable = true/*** 是否允许选空*/private var noEmpty = true/*** 正在修改数据,避免ConcurrentModificationException异常*/private var isClearing = false

1.初始化

    /*** 初始化,获取设置的属性** @param context* @param attrs*/private fun init(context: Context, attrs: AttributeSet?) {val attribute = context.obtainStyledAttributes(attrs, R.styleable.WheelView)unitHeight =attribute.getDimension(R.styleable.WheelView_unitHeight, unitHeight.toFloat()).toInt()itemNumber = attribute.getInt(R.styleable.WheelView_itemNumber, itemNumber)normalFont = attribute.getDimension(R.styleable.WheelView_normalTextSize, normalFont)selectedFont = attribute.getDimension(R.styleable.WheelView_selectedTextSize, selectedFont)normalColor = attribute.getColor(R.styleable.WheelView_normalTextColor, normalColor)selectedColor = attribute.getColor(R.styleable.WheelView_selectedTextColor, selectedColor)lineColor = attribute.getColor(R.styleable.WheelView_lineColor, lineColor)lineHeight = attribute.getDimension(R.styleable.WheelView_lineHeight, lineHeight)maskHeight = attribute.getDimension(R.styleable.WheelView_maskHeight, maskHeight)noEmpty = attribute.getBoolean(R.styleable.WheelView_noEmpty, true)isEnable = attribute.getBoolean(R.styleable.WheelView_isEnable, true)attribute.recycle()controlHeight = (itemNumber * unitHeight).toFloat()}

上面的代码在构造函数中调用,通过 context.obtainStyledAttributes(attrs, R.styleable.WheelView) 方法拿到我们在attrs.xml文件中自定义样式的一些参数。这些参数是可以在xml中配置的。这些都是自定义view的一些基础,属于老生常谈了。

attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="WheelView"><attr name="unitHeight" format="dimension" /><attr name="itemNumber" format="integer"/><attr name="normalTextColor" format="color" /><attr name="normalTextSize" format="dimension" /><attr name="selectedTextColor" format="color" /><attr name="selectedTextSize" format="dimension" /><attr name="lineColor" format="color" /><attr name="lineHeight" format="dimension" /><attr name="maskHeight" format="dimension"/><attr name="noEmpty" format="boolean"/><attr name="isEnable" format="boolean"/></declare-styleable></resources>

2.初始化数据

    /*** 初始化数据*/private fun initData() {isClearing = trueitemList!!.clear()for (i in dataList.indices) {val itemObject = ItemObject()itemObject.id = iitemObject.itemText = dataList[i]itemObject.x = 0itemObject.y = i * unitHeightitemList.add(itemObject)}isClearing = false}

这里就是初始化item数据,ItemObject是单个数据的绘制,后面再说。而isClearing 是为了避免 ConcurrentModificationException,在drawList()方法中有体现。

    @Synchronizedprivate fun drawList(canvas: Canvas) {if (isClearing) returntry {for (itemObject in itemList!!) {itemObject.drawSelf(canvas, measuredWidth)}} catch (e: Exception) {Log.e("WheelView", "drawList: $e")}}

3.onMeasure

自定义view的三件套之一

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)controlWidth = measuredWidth.toFloat()if (controlWidth != 0f) {setMeasuredDimension(measuredWidth, itemNumber * unitHeight)}}

先用measuredWidthcontrolWidth 赋值 ,然后当宽度不为0的时候调用setMeasuredDimension方法给具体的测量值。我们来看看setMeasuredDimension方法

在这里插入图片描述
这是一个view的自带方法,onMeasure(int,int)必须调用此方法来存储测量的宽度和测量的高度。否则将在测量时触发异常。
参数:

  • measuredWidth–此视图的测量宽度。可以是MEASURED_SIZE_mask和MEASURED_STATE_TOO_SMALL定义的复杂位掩码。
  • measuredHeight–此视图的测量高度。可以是MEASURED_SIZE_mask和MEASURED_STATE_TOO_SMALL定义的复杂位掩码。

4.onDraw

    override fun onDraw(canvas: Canvas) {super.onDraw(canvas)drawLine(canvas)drawList(canvas)drawMask(canvas)}

在其中绘制了三个东西,一个是绘制选中的两个线条

    /*** 绘制线条** @param canvas*/private fun drawLine(canvas: Canvas) {if (linePaint == null) {linePaint = Paint()linePaint!!.color = lineColorlinePaint!!.isAntiAlias = truelinePaint!!.strokeWidth = lineHeight}canvas.drawLine(0f, controlHeight / 2 - unitHeight / 2 + lineHeight,controlWidth, controlHeight / 2 - unitHeight / 2 + lineHeight, linePaint!!)canvas.drawLine(0f, controlHeight / 2 + unitHeight / 2 - lineHeight,controlWidth, controlHeight / 2 + unitHeight / 2 - lineHeight, linePaint!!)}

一个是绘制列表,上面已经说过了,还有一个就是绘制蒙层,我这边是有一个渐变的蒙层,也是我做过改动的地方

    /*** 绘制遮盖板** @param canvas*/private fun drawMask(canvas: Canvas) {val colorArray = intArrayOf(0x0042C8FF, 0x3D42C8FF, 0x0042C8FF)val positionArray = floatArrayOf(0f, 0.5f, 1f)val lg3 = LinearGradient(0f, 0f,controlWidth, 0f, colorArray, positionArray, TileMode.MIRROR)val paint3 = Paint()paint3.shader = lg3canvas.drawRect(0f, controlHeight / 2 - unitHeight / 2 + lineHeight, controlWidth,controlHeight / 2 + unitHeight / 2 - lineHeight, paint3)}

5.onTouchEvent

触摸事件

    override fun onTouchEvent(event: MotionEvent): Boolean {if (!isEnable) return trueval y = event.y.toInt()when (event.action) {MotionEvent.ACTION_DOWN -> {isScrolling = truedownY = event.y.toInt()downTime = System.currentTimeMillis()}MotionEvent.ACTION_MOVE -> {actionMove(y - downY)onSelectListener()}MotionEvent.ACTION_UP -> {val move = Math.abs(y - downY)// 判断这段时间移动的距离if (System.currentTimeMillis() - downTime < goonTime && move > goonDistance) {goonMove(y - downY)} else {actionUp(y - downY)noEmpty()isScrolling = false}}else -> {}}return true}

代码解析:
isEnable是控制是否能滑动的,不用过多关注
在手势为ACTION_DOWN 的时候,记录开始滑动的y坐标和时间,在手势为**ACTION_MOVE **的时候开始移动,并调用actionMove方法设置移动的坐标,然后调用invalidate方法进行重绘。onSelectListener是一个滑动时候的选中监听

    /*** 移动的时候** @param move*/private fun actionMove(move: Int) {for (item in itemList!!) {item.move(move)}invalidate()}

最后在手势为ACTION_UP 的时候,判断在ACTION_DOWN这段时间移动的距离,如果当前移动的时间小于短促移动的时间,当前移动的距离却大于短促移动的距离,那么我们就调用goonMove方法多移动一点距离以达到一个惯性移动的体验。

    /*** 继续移动一定距离*/@Synchronizedprivate fun goonMove(move: Int) {Thread {var distance = 0while (distance < unitHeight * MOVE_NUMBER) {try {Thread.sleep(5)} catch (e: InterruptedException) {e.printStackTrace()}actionThreadMove(if (move > 0) distance else distance * -1)distance += 10}actionUp(if (move > 0) distance - 10 else distance * -1 + 10)noEmpty()}.start()}/*** 移动,线程中调用** @param move*/private fun actionThreadMove(move: Int) {for (item in itemList!!) {item.move(move)}val rMessage = Message()rMessage.what = REFRESH_VIEWmHandler.sendMessage(rMessage)}

否则就直接用actionUp和noEmpty直接移动

    /*** 松开的时候** @param move*/private fun actionUp(move: Int) {var newMove = 0if (move > 0) {for (i in itemList!!.indices) {if (itemList[i].isSelected) {newMove = itemList[i].moveToSelected().toInt()if (onSelectListener != null) onSelectListener!!.endSelect(itemList[i].id,itemList[i].itemText)break}}} else {for (i in itemList!!.indices.reversed()) {if (itemList[i].isSelected) {newMove = itemList[i].moveToSelected().toInt()if (onSelectListener != null) onSelectListener!!.endSelect(itemList[i].id,itemList[i].itemText)break}}}for (item in itemList) {item.newY(move + 0)}slowMove(newMove)val rMessage = Message()rMessage.what = REFRESH_VIEWmHandler.sendMessage(rMessage)}/*** 不能为空,必须有选项*/private fun noEmpty() {if (!noEmpty) returnfor (item in itemList!!) {if (item.isSelected) return}val move = itemList[0].moveToSelected().toInt()if (move < 0) {defaultMove(move)} else {defaultMove(itemList[itemList.size - 1].moveToSelected().toInt())}for (item in itemList) {if (item.isSelected) {if (onSelectListener != null) onSelectListener!!.endSelect(item.id, item.itemText)break}}}

6.其他

    /*** 缓慢移动** @param move*/@Synchronizedprivate fun slowMove(move: Int) {Thread { // 判断正负var m = if (move > 0) move else move * -1val i = if (move > 0) 1 else -1// 移动速度val speed = 1while (true) {m -= speedif (m <= 0) {for (item in itemList!!) {item.newY(m * i)}val rMessage = Message()rMessage.what = REFRESH_VIEWmHandler.sendMessage(rMessage)try {Thread.sleep(2)} catch (e: InterruptedException) {e.printStackTrace()}break}for (item in itemList!!) {item.newY(speed * i)}val rMessage = Message()rMessage.what = REFRESH_VIEWmHandler.sendMessage(rMessage)try {Thread.sleep(2)} catch (e: InterruptedException) {e.printStackTrace()}}if (itemList != null) {for (item in itemList) {if (item.isSelected) {if (onSelectListener != null) onSelectListener!!.endSelect(item.id,item.itemText)break}}}}.start()}/*** 移动到默认位置** @param move*/private fun defaultMove(move: Int) {for (item in itemList!!) {item.newY(move)}val rMessage = Message()rMessage.what = REFRESH_VIEWmHandler.sendMessage(rMessage)}

一些移动相关的方法。


6.ItemObject

单个绘制里面值得说的就是drawSelf方法了,注释都写的比较清晰了。

  /*** 绘制自身** @param canvas         画板* @param containerWidth 容器宽度*/fun drawSelf(canvas: Canvas, containerWidth: Int) {if (textPaint == null) {textPaint = TextPaint()textPaint!!.isAntiAlias = true}if (textRect == null) textRect = Rect()// 判断是否被选择if (isSelected) {textPaint!!.color = selectedColor// 获取距离标准位置的距离var moveToSelect = moveToSelected()moveToSelect = if (moveToSelect > 0) moveToSelect else moveToSelect * -1// 计算当前字体大小val textSize =normalFont + (selectedFont - normalFont) * (1.0f - moveToSelect / unitHeight.toFloat())textPaint!!.textSize = textSize} else {textPaint!!.color = normalColortextPaint!!.textSize = normalFont}// 判断是否可视if (!isInView) return//判断是一行还是两行,两行数据用,分割if (itemText.indexOf(",") != -1) {var (text1, text2) = itemText.split(",")// 返回包围整个字符串的最小的一个Rect区域text1 = TextUtils.ellipsize(text1,textPaint,containerWidth.toFloat(),TextUtils.TruncateAt.END) as StringtextPaint!!.getTextBounds(text1, 0, text1.length, textRect)//双排文字一canvas.drawText(text1,x + controlWidth / 2 - textRect!!.width() / 2,(y + move + unitHeight / 5 * 2 + textRect!!.height() / 5 * 2).toFloat(),textPaint!!)// 返回包围整个字符串的最小的一个Rect区域text2 = TextUtils.ellipsize(text2,textPaint,containerWidth.toFloat(),TextUtils.TruncateAt.END) as StringtextPaint!!.getTextBounds(text2, 0, text2.length, textRect)//双排文字2canvas.drawText(text2,x + controlWidth / 2 - textRect!!.width() / 2,(y + move + unitHeight / 5 * 3 + textRect!!.height() / 5 * 3).toFloat(),textPaint!!)} else {// 返回包围整个字符串的最小的一个Rect区域itemText = TextUtils.ellipsize(itemText,textPaint,containerWidth.toFloat(),TextUtils.TruncateAt.END) as StringtextPaint!!.getTextBounds(itemText, 0, itemText.length, textRect)// 绘制内容canvas.drawText(itemText, x + controlWidth / 2 - textRect!!.width() / 2,(y + move + unitHeight / 2 + textRect!!.height() / 2).toFloat(), textPaint!!)}}

二、完整代码

import android.content.Context
import android.graphics.Canvas
import android.graphics.LinearGradient
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.Shader.TileMode
import android.os.Handler
import android.os.Message
import android.text.TextPaint
import android.text.TextUtils
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.Viewclass WheelView : View {/*** 控件宽度*/private var controlWidth = 0f/*** 控件高度*/private var controlHeight = 0f/*** 是否正在滑动** @return*//*** 是否滑动中*/var isScrolling = false/*** 选择的内容*/private val itemList: MutableList<ItemObject>? = mutableListOf()/*** 设置数据*/private var dataList = mutableListOf<String>()/*** 按下的坐标*/private var downY = 0/*** 按下的时间*/private var downTime: Long = 0/*** 短促移动*/private val goonTime: Long = 200/*** 短促移动距离*/private val goonDistance = 100/*** 画线画笔*/private var linePaint: Paint? = null/*** 线的默认颜色*/private var lineColor = -0x1000000/*** 线的默认宽度*/private var lineHeight = 2f/*** 默认字体*/private var normalFont = 14.0f/*** 选中的时候字体*/private var selectedFont = 22.0f/*** 单元格高度*/private var unitHeight = 50/*** 显示多少个内容*/private var itemNumber = 7/*** 默认字体颜色*/private var normalColor = -0x1000000/*** 选中时候的字体颜色*/private var selectedColor = -0x10000/*** 蒙板高度*/private var maskHeight = 48.0f/*** 选择监听*/private var onSelectListener: OnSelectListener? = null/*** 设置是否可用** @param isEnable*/var isEnable = true/*** 是否允许选空*/private var noEmpty = true/*** 正在修改数据,避免ConcurrentModificationException异常*/private var isClearing = falseconstructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context,attrs,defStyle) {init(context, attrs)initData()}constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {init(context, attrs)initData()}constructor(context: Context?) : super(context) {initData()}/*** 初始化,获取设置的属性** @param context* @param attrs*/private fun init(context: Context, attrs: AttributeSet?) {val attribute = context.obtainStyledAttributes(attrs, R.styleable.WheelView)unitHeight =attribute.getDimension(R.styleable.WheelView_unitHeight, unitHeight.toFloat()).toInt()itemNumber = attribute.getInt(R.styleable.WheelView_itemNumber, itemNumber)normalFont = attribute.getDimension(R.styleable.WheelView_normalTextSize, normalFont)selectedFont = attribute.getDimension(R.styleable.WheelView_selectedTextSize, selectedFont)normalColor = attribute.getColor(R.styleable.WheelView_normalTextColor, normalColor)selectedColor = attribute.getColor(R.styleable.WheelView_selectedTextColor, selectedColor)lineColor = attribute.getColor(R.styleable.WheelView_lineColor, lineColor)lineHeight = attribute.getDimension(R.styleable.WheelView_lineHeight, lineHeight)maskHeight = attribute.getDimension(R.styleable.WheelView_maskHeight, maskHeight)noEmpty = attribute.getBoolean(R.styleable.WheelView_noEmpty, true)isEnable = attribute.getBoolean(R.styleable.WheelView_isEnable, true)attribute.recycle()controlHeight = (itemNumber * unitHeight).toFloat()}/*** 初始化数据*/private fun initData() {isClearing = trueitemList!!.clear()for (i in dataList.indices) {val itemObject = ItemObject()itemObject.id = iitemObject.itemText = dataList[i]itemObject.x = 0itemObject.y = i * unitHeightitemList.add(itemObject)}isClearing = false}override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {super.onMeasure(widthMeasureSpec, heightMeasureSpec)controlWidth = measuredWidth.toFloat()if (controlWidth != 0f) {setMeasuredDimension(measuredWidth, itemNumber * unitHeight)}}override fun onDraw(canvas: Canvas) {super.onDraw(canvas)drawLine(canvas)drawList(canvas)drawMask(canvas)}/*** 绘制线条** @param canvas*/private fun drawLine(canvas: Canvas) {if (linePaint == null) {linePaint = Paint()linePaint!!.color = lineColorlinePaint!!.isAntiAlias = truelinePaint!!.strokeWidth = lineHeight}canvas.drawLine(0f, controlHeight / 2 - unitHeight / 2 + lineHeight,controlWidth, controlHeight / 2 - unitHeight / 2 + lineHeight, linePaint!!)canvas.drawLine(0f, controlHeight / 2 + unitHeight / 2 - lineHeight,controlWidth, controlHeight / 2 + unitHeight / 2 - lineHeight, linePaint!!)}@Synchronizedprivate fun drawList(canvas: Canvas) {if (isClearing) returntry {for (itemObject in itemList!!) {itemObject.drawSelf(canvas, measuredWidth)}} catch (e: Exception) {Log.e("WheelView", "drawList: $e")}}/*** 绘制遮盖板** @param canvas*/private fun drawMask(canvas: Canvas) {val colorArray = intArrayOf(0x0042C8FF, 0x3D42C8FF, 0x0042C8FF)val positionArray = floatArrayOf(0f, 0.5f, 1f)val lg3 = LinearGradient(0f, 0f,controlWidth, 0f, colorArray, positionArray, TileMode.MIRROR)val paint3 = Paint()paint3.shader = lg3canvas.drawRect(0f, controlHeight / 2 - unitHeight / 2 + lineHeight, controlWidth,controlHeight / 2 + unitHeight / 2 - lineHeight, paint3)}override fun onTouchEvent(event: MotionEvent): Boolean {if (!isEnable) return trueval y = event.y.toInt()when (event.action) {MotionEvent.ACTION_DOWN -> {isScrolling = truedownY = event.y.toInt()downTime = System.currentTimeMillis()}MotionEvent.ACTION_MOVE -> {actionMove(y - downY)onSelectListener()}MotionEvent.ACTION_UP -> {val move = Math.abs(y - downY)// 判断这段时间移动的距离if (System.currentTimeMillis() - downTime < goonTime && move > goonDistance) {goonMove(y - downY)} else {actionUp(y - downY)noEmpty()isScrolling = false}}else -> {}}return true}/*** 继续移动一定距离*/@Synchronizedprivate fun goonMove(move: Int) {Thread {var distance = 0while (distance < unitHeight * MOVE_NUMBER) {try {Thread.sleep(5)} catch (e: InterruptedException) {e.printStackTrace()}actionThreadMove(if (move > 0) distance else distance * -1)distance += 10}actionUp(if (move > 0) distance - 10 else distance * -1 + 10)noEmpty()}.start()}/*** 不能为空,必须有选项*/private fun noEmpty() {if (!noEmpty) returnfor (item in itemList!!) {if (item.isSelected) return}val move = itemList[0].moveToSelected().toInt()if (move < 0) {defaultMove(move)} else {defaultMove(itemList[itemList.size - 1].moveToSelected().toInt())}for (item in itemList) {if (item.isSelected) {if (onSelectListener != null) onSelectListener!!.endSelect(item.id, item.itemText)break}}}/*** 移动的时候** @param move*/private fun actionMove(move: Int) {for (item in itemList!!) {item.move(move)}invalidate()}/*** 移动,线程中调用** @param move*/private fun actionThreadMove(move: Int) {for (item in itemList!!) {item.move(move)}val rMessage = Message()rMessage.what = REFRESH_VIEWmHandler.sendMessage(rMessage)}/*** 松开的时候** @param move*/private fun actionUp(move: Int) {var newMove = 0if (move > 0) {for (i in itemList!!.indices) {if (itemList[i].isSelected) {newMove = itemList[i].moveToSelected().toInt()if (onSelectListener != null) onSelectListener!!.endSelect(itemList[i].id,itemList[i].itemText)break}}} else {for (i in itemList!!.indices.reversed()) {if (itemList[i].isSelected) {newMove = itemList[i].moveToSelected().toInt()if (onSelectListener != null) onSelectListener!!.endSelect(itemList[i].id,itemList[i].itemText)break}}}for (item in itemList) {item.newY(move + 0)}slowMove(newMove)val rMessage = Message()rMessage.what = REFRESH_VIEWmHandler.sendMessage(rMessage)}/*** 缓慢移动** @param move*/@Synchronizedprivate fun slowMove(move: Int) {Thread { // 判断正负var m = if (move > 0) move else move * -1val i = if (move > 0) 1 else -1// 移动速度val speed = 1while (true) {m -= speedif (m <= 0) {for (item in itemList!!) {item.newY(m * i)}val rMessage = Message()rMessage.what = REFRESH_VIEWmHandler.sendMessage(rMessage)try {Thread.sleep(2)} catch (e: InterruptedException) {e.printStackTrace()}break}for (item in itemList!!) {item.newY(speed * i)}val rMessage = Message()rMessage.what = REFRESH_VIEWmHandler.sendMessage(rMessage)try {Thread.sleep(2)} catch (e: InterruptedException) {e.printStackTrace()}}if (itemList != null) {for (item in itemList) {if (item.isSelected) {if (onSelectListener != null) onSelectListener!!.endSelect(item.id,item.itemText)break}}}}.start()}/*** 移动到默认位置** @param move*/private fun defaultMove(move: Int) {for (item in itemList!!) {item.newY(move)}val rMessage = Message()rMessage.what = REFRESH_VIEWmHandler.sendMessage(rMessage)}/*** 滑动监听*/private fun onSelectListener() {if (onSelectListener == null) returnfor (item in itemList!!) {if (item.isSelected) {onSelectListener!!.selecting(item.id, item.itemText)}}}/*** 设置数据 (第一次)** @param data*/fun setData(data: MutableList<String>) {dataList = datainitData()}/*** 重置数据** @param data*/fun refreshData(data: MutableList<String>) {setData(data)invalidate()}/*** 获取返回项 id** @return*/val selected: Intget() {for (item in itemList!!) {if (item.isSelected) return item.id}return -1}/*** 获取返回的内容** @return*/val selectedText: Stringget() {for (item in itemList!!) {if (item.isSelected) return item.itemText}return ""}/*** 设置默认选项** @param index*/fun setDefault(index: Int) {if (index > itemList!!.size - 1) returnval move = itemList[index].moveToSelected()defaultMove(move.toInt())}/*** 获取列表大小** @return*/val listSize: Intget() = itemList?.size ?: 0/*** 获取某项的内容** @param index* @return*/fun getItemText(index: Int): String {return itemList?.get(index)?.itemText ?: ""}/*** 监听** @param onSelectListener*/fun setOnSelectListener(onSelectListener: OnSelectListener?) {this.onSelectListener = onSelectListener}var mHandler: Handler =object : Handler() {override fun handleMessage(msg: Message) {super.handleMessage(msg)when (msg.what) {REFRESH_VIEW -> invalidate()else -> {}}}}/*** 单条内容*/private inner class ItemObject {/*** id*/var id = 0/*** 内容*/var itemText = ""/*** x坐标*/var x = 0/*** y坐标*/var y = 0/*** 移动距离*/var move = 0/*** 字体画笔*/private var textPaint: TextPaint? = null/*** 字体范围矩形*/private var textRect: Rect? = null/*** 绘制自身** @param canvas         画板* @param containerWidth 容器宽度*/fun drawSelf(canvas: Canvas, containerWidth: Int) {if (textPaint == null) {textPaint = TextPaint()textPaint!!.isAntiAlias = true}if (textRect == null) textRect = Rect()// 判断是否被选择if (isSelected) {textPaint!!.color = selectedColor// 获取距离标准位置的距离var moveToSelect = moveToSelected()moveToSelect = if (moveToSelect > 0) moveToSelect else moveToSelect * -1// 计算当前字体大小val textSize =normalFont + (selectedFont - normalFont) * (1.0f - moveToSelect / unitHeight.toFloat())textPaint!!.textSize = textSize} else {textPaint!!.color = normalColortextPaint!!.textSize = normalFont}// 判断是否可视if (!isInView) return//判断是一行还是两行,两行数据用,分割if (itemText.indexOf(",") != -1) {var (text1, text2) = itemText.split(",")// 返回包围整个字符串的最小的一个Rect区域text1 = TextUtils.ellipsize(text1,textPaint,containerWidth.toFloat(),TextUtils.TruncateAt.END) as StringtextPaint!!.getTextBounds(text1, 0, text1.length, textRect)//双排文字一canvas.drawText(text1,x + controlWidth / 2 - textRect!!.width() / 2,(y + move + unitHeight / 5 * 2 + textRect!!.height() / 5 * 2).toFloat(),textPaint!!)// 返回包围整个字符串的最小的一个Rect区域text2 = TextUtils.ellipsize(text2,textPaint,containerWidth.toFloat(),TextUtils.TruncateAt.END) as StringtextPaint!!.getTextBounds(text2, 0, text2.length, textRect)//双排文字2canvas.drawText(text2,x + controlWidth / 2 - textRect!!.width() / 2,(y + move + unitHeight / 5 * 3 + textRect!!.height() / 5 * 3).toFloat(),textPaint!!)} else {// 返回包围整个字符串的最小的一个Rect区域itemText = TextUtils.ellipsize(itemText,textPaint,containerWidth.toFloat(),TextUtils.TruncateAt.END) as StringtextPaint!!.getTextBounds(itemText, 0, itemText.length, textRect)// 绘制内容canvas.drawText(itemText, x + controlWidth / 2 - textRect!!.width() / 2,(y + move + unitHeight / 2 + textRect!!.height() / 2).toFloat(), textPaint!!)}}/*** 是否在可视界面内** @return*/val isInView: Booleanget() = if (y + move > controlHeight || y + move + unitHeight / 2 + textRect!!.height() / 2 < 0) false else true/*** 移动距离** @param _move*/fun move(_move: Int) {move = _move}/*** 设置新的坐标** @param _move*/fun newY(_move: Int) {move = 0y = y + _move}/*** 判断是否在选择区域内** @return*/val isSelected: Booleanget() {if (y + move + unitHeight >= controlHeight / 2 - unitHeight / 2 + lineHeight&& y + move + unitHeight <= controlHeight / 2 + unitHeight / 2 - lineHeight) {return true}return (y + move <= controlHeight / 2 - unitHeight / 2 + lineHeight&& y + move + unitHeight >= controlHeight / 2 + unitHeight / 2 - lineHeight)}/*** 获取移动到标准位置需要的距离*/fun moveToSelected(): Float {return controlHeight / 2 - unitHeight / 2 - (y + move)}}/*** 选择监听** @author JiangPing*/interface OnSelectListener {/*** 结束选择** @param id* @param text*/fun endSelect(id: Int, text: String?)/*** 选中的内容** @param id* @param text*/fun selecting(id: Int, text: String?)}companion object {/*** 刷新界面*/private const val REFRESH_VIEW = 0x001/*** 移动距离*/private const val MOVE_NUMBER = 5}
}

总结

主要还是考查自定义view相关能力。


http://www.ppmy.cn/news/1205497.html

相关文章

遇到java.security.AccessControlException:access denied怎么办?

今天工作中遇到了如下报错&#xff0c;记录一下解决方案。 目录 问题 分析 结论 问题 这个问题出现在openjdk8启动网页端Java应用。 Java Exception:java.security.AccessControlException:access denied("java.net.SocketPermission""22.188.130.11:9000…

React进阶之路(一)-- JSX基础、组件基础

文章目录 React介绍React开发环境搭建项目目录说明以及相关调整 JSX基础JSX介绍JSX中使用js表达式JSX列表渲染JSX条件渲染JSX样式处理JSX注意事项 组件基础组件的概念函数组件类组件事件绑定如何绑定事件获取事件对象传递额外参数 组件状态状态不可变表单处理受控表单组件非受控…

震裕转债上市价格预测

震裕转债-123228 基本信息 转债名称&#xff1a;震裕转债&#xff0c;评级&#xff1a;AA-&#xff0c;发行规模&#xff1a;11.95亿元。 正股名称&#xff1a;震裕科技&#xff0c;今日收盘价&#xff1a;58.85元&#xff0c;转股价格&#xff1a;61.57元。 当前转股价值 转债…

vue下使用Echarts5绘制基础图表

项目使用Vue3加Echarts5绘制的基本图表&#xff0c;图表自适应浏览器窗口大小 先上图&#xff0c;大屏小屏都可完美展示&#xff0c;纯属练手 一 先上图 1.任意缩放窗口的大小 2.平板 3.电脑 4.饼图 5.折线图 二 后上代码 <script lang"ts"> import {d…

【Springboot】Springboot引入JWT实现登录校验以及常见的错误解决方案

文章目录 前言一、JWT简单介绍二、token校验设计思路三、使用步骤Springboot部署JWT引入依赖&#xff1a;创建登录实体类后端&#xff1a;LoginController.java路由守卫函数 四、问题 前言 项目版本&#xff1a; 后端&#xff1a; Springboot 2.7、 Mybatis-plus、Maven 3.8.1…

【使用python写一段代码将pdf文件转换为word文件】

突然有一个需求 就是将一份老板发的PDF文件&#xff0c;转换为Word文档&#xff0c;发现要么收费&#xff0c;要么就是有水印&#xff0c;更有甚者需要将转换收费&#xff08;美其名曰就是需要开会员&#xff09;&#xff0c;那能惯着他吗 开整&#xff01; 1.使用python写一段…

python使用pytest接口自动化测试的使用

这篇文章主要介绍了python使用pytest接口自动化测试的使用&#xff0c;文中通过示例代码介绍的非常详细&#xff0c;对大家的学习或者工作具有一定的参考学习价值&#xff0c;需要的朋友们下面随着小编来一起学习学习吧 简单的设计思路 利用pytest对一个接口进行各种场景测试…

opengauss权限需求

创建角色 "u_rts" 并授予对数据库 "rts_opsdb" 的只读权限&#xff1a; CREATE ROLE u_rts LOGIN PASSWORD Cloud1234; GRANT CONNECT ON DATABASE rts_opsdb TO u_rts; GRANT USAGE ON SCHEMA public TO u_rts; GRANT SELECT ON ALL TABLES IN SCHEMA pub…