安卓 车轮视图 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)}}
先用measuredWidth给 controlWidth 赋值 ,然后当宽度不为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相关能力。