安卓 车轮视图 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 = true
itemList!!.clear()
for (i in dataList.indices) {
val itemObject = ItemObject()
itemObject.id = i
itemObject.itemText = dataList[i]
itemObject.x = 0
itemObject.y = i * unitHeight
itemList.add(itemObject)
}
isClearing = false
}
这里就是初始化item数据,ItemObject是单个数据的绘制,后面再说。而isClearing 是为了避免 ConcurrentModificationException,在drawList()方法中有体现。
@Synchronized
private fun drawList(canvas: Canvas) {
if (isClearing) return
try {
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 = lineColor
linePaint!!.isAntiAlias = true
linePaint!!.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 = lg3
canvas.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 true
val y = event.y.toInt()
when (event.action) {
MotionEvent.ACTION_DOWN -> {
isScrolling = true
downY = 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方法多移动一点距离以达到一个惯性移动的体验。
/**
* 继续移动一定距离
*/
@Synchronized
private fun goonMove(move: Int) {
Thread {
var distance = 0
while (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_VIEW
mHandler.sendMessage(rMessage)
}
否则就直接用actionUp和noEmpty直接移动
/**
* 松开的时候
*
* @param move
*/
private fun actionUp(move: Int) {
var newMove = 0
if (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_VIEW
mHandler.sendMessage(rMessage)
}
/**
* 不能为空,必须有选项
*/
private fun noEmpty() {
if (!noEmpty) return
for (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
*/
@Synchronized
private fun slowMove(move: Int) {
Thread { // 判断正负
var m = if (move > 0) move else move * -1
val i = if (move > 0) 1 else -1
// 移动速度
val speed = 1
while (true) {
m -= speed
if (m <= 0) {
for (item in itemList!!) {
item.newY(m * i)
}
val rMessage = Message()
rMessage.what = REFRESH_VIEW
mHandler.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_VIEW
mHandler.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_VIEW
mHandler.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 = normalColor
textPaint!!.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 String
textPaint!!.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 String
textPaint!!.getTextBounds(text2, 0, text2.length, textRect)
//双排文字2
canvas.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 String
textPaint!!.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.View
class 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 = false
constructor(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 = true
itemList!!.clear()
for (i in dataList.indices) {
val itemObject = ItemObject()
itemObject.id = i
itemObject.itemText = dataList[i]
itemObject.x = 0
itemObject.y = i * unitHeight
itemList.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 = lineColor
linePaint!!.isAntiAlias = true
linePaint!!.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!!
)
}
@Synchronized
private fun drawList(canvas: Canvas) {
if (isClearing) return
try {
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 = lg3
canvas.drawRect(
0f, controlHeight / 2 - unitHeight / 2 + lineHeight, controlWidth,
controlHeight / 2 + unitHeight / 2 - lineHeight, paint3
)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
if (!isEnable) return true
val y = event.y.toInt()
when (event.action) {
MotionEvent.ACTION_DOWN -> {
isScrolling = true
downY = 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
}
/**
* 继续移动一定距离
*/
@Synchronized
private fun goonMove(move: Int) {
Thread {
var distance = 0
while (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) return
for (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_VIEW
mHandler.sendMessage(rMessage)
}
/**
* 松开的时候
*
* @param move
*/
private fun actionUp(move: Int) {
var newMove = 0
if (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_VIEW
mHandler.sendMessage(rMessage)
}
/**
* 缓慢移动
*
* @param move
*/
@Synchronized
private fun slowMove(move: Int) {
Thread { // 判断正负
var m = if (move > 0) move else move * -1
val i = if (move > 0) 1 else -1
// 移动速度
val speed = 1
while (true) {
m -= speed
if (m <= 0) {
for (item in itemList!!) {
item.newY(m * i)
}
val rMessage = Message()
rMessage.what = REFRESH_VIEW
mHandler.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_VIEW
mHandler.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_VIEW
mHandler.sendMessage(rMessage)
}
/**
* 滑动监听
*/
private fun onSelectListener() {
if (onSelectListener == null) return
for (item in itemList!!) {
if (item.isSelected) {
onSelectListener!!.selecting(item.id, item.itemText)
}
}
}
/**
* 设置数据 (第一次)
*
* @param data
*/
fun setData(data: MutableList<String>) {
dataList = data
initData()
}
/**
* 重置数据
*
* @param data
*/
fun refreshData(data: MutableList<String>) {
setData(data)
invalidate()
}
/**
* 获取返回项 id
*
* @return
*/
val selected: Int
get() {
for (item in itemList!!) {
if (item.isSelected) return item.id
}
return -1
}
/**
* 获取返回的内容
*
* @return
*/
val selectedText: String
get() {
for (item in itemList!!) {
if (item.isSelected) return item.itemText
}
return ""
}
/**
* 设置默认选项
*
* @param index
*/
fun setDefault(index: Int) {
if (index > itemList!!.size - 1) return
val move = itemList[index].moveToSelected()
defaultMove(move.toInt())
}
/**
* 获取列表大小
*
* @return
*/
val listSize: Int
get() = 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 = normalColor
textPaint!!.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 String
textPaint!!.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 String
textPaint!!.getTextBounds(text2, 0, text2.length, textRect)
//双排文字2
canvas.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 String
textPaint!!.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: Boolean
get() = 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 = 0
y = y + _move
}
/**
* 判断是否在选择区域内
*
* @return
*/
val isSelected: Boolean
get() {
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相关能力。