安卓 车轮视图 WheelView kotlin

安卓 车轮视图 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.rhkb.cn/news/185558.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

idea使用lombok编译问题

idea编译报错问题如下&#xff1a; java: You arent using a compiler supported by lombok, so lombok will not work and has been disabled.Your processor is: com.sun.proxy.$Proxy26Lombok supports: OpenJDK javac, ECJ解决方案 1.先将jdk替换为openjdk,随后将项目配置…

体制内人,知道这个工具,写什么都有底气

体制内&#xff0c;每天都在写各种材料&#xff01;&#xff01; 用词、结构、形式什么的都要严谨&#xff0c;但有时候写完又不行&#xff0c;还要改改改&#xff0c;家人们谁懂啊&#xff01;&#xff01;&#xff01; 这个工具&#xff0c;输入要求就可以快速生成文案&…

基于SSM框架的共享单车管理系统小程序系统的设计和实现

基于SSM框架的共享单车管理系统小程序系统的设计和实现 源码传送入口前言主要技术系统设计功能截图Lun文目录订阅经典源码专栏Java项目精品实战案例《500套》 源码获取 源码传送入口 前言 随着科学技术的飞速发展&#xff0c;各行各业都在努力与现代先进技术接轨&#xff0c;…

SpectralDiff论文阅读笔记

高光谱图像分类是遥感领域的一个重要问题&#xff0c;在地球科学中有着广泛的应用。近年来&#xff0c;人们提出了大量基于深度学习的HSI分类方法。然而&#xff0c;现有方法处理高维、高冗余和复杂数据的能力有限&#xff0c;这使得捕获数据的光谱空间分布和样本之间的关系具有…

调试 Mahony 滤波算法的思考 10

调试 Mahony 滤波算法的思考 1. 说在前面的2.Mahony滤波算法的核心思想3. 易懂的理解 Mahony 滤波算法的过程4. 其他的一些思考5. 民间 9轴评估板 1. 说在前面的 之前调试基于QMI8658 6轴姿态解算的时候&#xff0c;我对Mahony滤波的认识还比较浅薄。初次的学习和代码的移植让…

平凯星辰 TiDB 携手广发银行荣膺第十四届金融科技创新奖

近日&#xff0c;由《金融电子化》杂志社、苏州市金融科技协会共同主办的“第十四届金融科技创新奖颁奖典礼”在苏州隆重举行。 会上&#xff0c;由平凯星辰&#xff08;北京&#xff09;科技有限公司&#xff08;简称&#xff1a; 平凯星辰&#xff09;和广发银行共同申报的 “…

[unity]深色模式/浅色模式

这里用的是Windows版的unity&#xff0c;具体版本号如下&#xff1a; 选项的路径如下&#xff1a;Edit—Preferences—General—Editor Theme 然后就可以选是dark还是light了&#xff1a;

国产小体积超低成本电动车仪表智能刷卡解锁13.56M非接触式读写芯片CI522兼容替代RC522

Ci522电动车仪表一键启动芯片 Ci522是一个高度集成的&#xff0c;工作在13.56MHz的非接触式读写器芯片&#xff0c;阅读器支持ISO/IEC 14443 A/MIFARE。 无需外围其他电路&#xff0c;Ci522的内部发送器可驱动读写器天线与ISO/IEC 14443 A/MIFARE卡和应答机通信。接收器模块提…

JWT简介 JWT结构 JWT示例 前端添加JWT令牌功能 后端程序

目录 1. JWT简述 1.1 什么是JWT 1.2 为什么使用JWT 1.3 JWT结构 1.4 验证过程 2. JWT示例 2.1 后台程序 2.2 前台加入jwt令牌功能 1. JWT简述 1.1 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准&#xff08;(RFC 7…

通过创建自定义标签来扩展HTML

使用HTML时&#xff0c;例如&#xff0c;使用<b>标记显示粗体文本。 如果需要列表&#xff0c;则对每个列表项使用<ul>标记及其子标记<li> 。 标签由浏览器解释&#xff0c;并与CSS一起确定网页内容的显示方式以及部分内容的行为。 有时&#xff0c;仅使用一…

Power Apps-“编辑“窗体组件

插入一个“编辑”窗体 连接数据源 在该组件的Item函数中编辑筛选符合条件的唯一记录 LookUp(表名,列名值) LookUp参考文档&#xff1a;Filter、Search 和 LookUp 函数&#xff08;包含视频&#xff09; - Power Platform | Microsoft Learn 数据表里的数据就一一对应出现在了组…

【Redis】set常用命令集合间操作内部编码使用场景

文章目录 前置知识常见命令SADDSMEMBERSSISMEMBERSCARDSPOPSMOVESREM 集合间操作SINTERSINTERSTORESUNIONSUNIONSTORESDIFFSDIFFSTORE 命令小结内部编码测试内部编码 使用场景 前置知识 集合类型也是保存多个字符串类型的元素的&#xff0c;但和列表类型不同的是&#xff0c;在…

每日一题 2258. 逃离火灾(手撕困难!!!)

火会扩散&#xff0c;但是我们可以看作火不会扩散到已经着火的格子&#xff0c;这样我们就可以记录每一个为草地的格子的着火时间在代码中&#xff0c;因为数字 2 已经表示墙了&#xff0c;所以我们把当时间为 0 时着火的格子在 gird 中的值设为 3&#xff0c;时间为 1 时着火的…

公众号标签

公众号标签 本章节&#xff0c;讲解公众号标签的相关内容&#xff0c;支持对标签进行创建、查询、修改、删除等操作&#xff0c;也可以对用户进行打标签、取消标签等操作&#xff0c;对应 《微信公众号官方文档 —— 用户标签管理》 (opens new window)文档。 #1. 表结构 公众…

URI 和 URL 的区别

URI包括URL和URN两个类别&#xff0c;URL是URI的子集&#xff0c;所以URL一定是URI&#xff0c;而URI不一定是URL URI Universal Resource Identifier 统一资源标志符&#xff0c;用来标识抽象或物理资源的一个紧凑字符串。 通过使用位置&#xff0c;名称或两者来标识Interne…

5G毫米波通信中的关键技术

随着5G技术的快速发展&#xff0c;毫米波通信作为其中的一项重要技术&#xff0c;在高速数据传输、低延迟通信和大规模连接等方面具有显著的优势。本文将探讨5G毫米波通信中的关键技术&#xff0c;包括毫米波频段的选择、信号处理技术和MIMO技术等。 一、毫米波频段的选择 毫米…

pytest + yaml 框架 -58.运行报告总结summary.json

前言 用例运行结束后&#xff0c;在本地生成summary.json 文件&#xff0c;总结运行结果。 v1.5.1版本更新内容&#xff1a; 1.解决参数化&#xff0c;中文在控制台输出问题 2.保存用例结果summary.json 保存用例结果summary.json 命令行执行用例 pytest运行结束&#xff0…

使用OkHttp库爬取百度云视频详细步骤

目录 摘要 一、OkHttp库简介 二、爬虫基本概念 三、使用OkHttp库爬取百度云视频 1、发送HTTP请求 2、处理响应 3、下载文件 四、可能遇到的问题及解决方案 五、注意事项 总结与建议 摘要 本文将详细介绍如何使用OkHttp库爬取百度云视频。文章首先简要介绍OkHttp库和…

一分钟带你了解易货:来龙去脉,古往今来

原始社会&#xff0c;没有货币&#xff0c;人类没有货币&#xff0c;用捕猎来的兽皮换取需要的野果&#xff0c;用一头羊换几只鸡&#xff0c;等等之类&#xff0c;都属于最早的交换。那个时候&#xff0c;人类没有价值的定义&#xff0c;属于最原始的交换。 直到私有制的形成&…

算法秘籍-王一博 | 数据结构与算法

⭐简单说两句⭐ 作者&#xff1a;后端小知识 CSDN个人主页&#xff1a;后端小知识 &#x1f50e;GZH&#xff1a;后端小知识 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 数据结构和算法是计算机科学的基石&#xff0c;是计算机的灵魂&…