安卓 车轮视图 WheelView kotlin

news2024/10/5 20:27:05

安卓 车轮视图 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)
        }
    }

先用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 = 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相关能力。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1184367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

com.genuitec.eclipse.springframework.springnature

com.genuitec.eclipse.springframework.springnature

计算机毕业论文内容参考|基于spingboot的金融投资顾问推荐系统

文章目录 导文文章重点摘要前言绪论课题背景:国内外现状与趋势:课题内容:相关技术与方法介绍系统分析系统设计系统实现总结与展望1本文总结2后续工作展望导文 计算机毕业论文内容参考|基于spingboot的金融投资顾问推荐系统 文章重点 摘要 基于SpingBoot的金融投资顾问推荐…

大功率电源的应用场景有哪些(高压功率放大器)

大功率电源的应用非常广泛&#xff0c;其应用场景包括但不限于以下几个方面&#xff1a; 工业生产&#xff1a;大功率电源广泛应用于各种工业生产领域&#xff0c;如机床、冶金、化工、电力等。例如&#xff0c;用于驱动液压系统、加热炉、电焊机等设备。 实验室研究&#xff1…

百分点科技受邀参加“第五届治理现代化论坛”

11月4日&#xff0c;由北京大学政府管理学院主办的“面向新时代的人才培养——第五届治理现代化论坛”举行&#xff0c;北京大学校党委常委、副校长、教务长王博&#xff0c;政府管理学院院长燕继荣参加开幕式并致辞&#xff0c;百分点科技董事长兼CEO苏萌受邀出席论坛&#xf…

Leetcode2918. 数组的最小相等和

Every day a Leetcode 题目来源&#xff1a;2918. 数组的最小相等和 解法1&#xff1a;贪心 设数组 nums1 的元素总和为 sum1&#xff0c;其中 0 的个数为 countZero1&#xff1b;数组 nums2 的元素总和为 sum2&#xff0c;其中 0 的个数为 countZero2。 题目要求我们必须…

selenium元素定位 —— 提高篇 CSS定位元素

CSS (Cascading Style Sheets) 是一种用于渲染 HTML 或者 XML 文档的语言&#xff0c;CSS 利用其选择器可以将样式属性绑定到文档中的指定元素。理论上说无论一个元素定位有多复杂都能够定位到元素。 因为不同的浏览器 XPath 引擎不同甚至没有自己的 Xpath 引擎&#xff0c;这…

SpringBoot整合Mybatis-plus代码生成器

整合代码生成器过程中,发现好多博主提供的无法使用,自己整合了一套,没有花里胡哨,直接可用 备注:常规的依赖自己导入,提供的这套,默认已经导入了mybatis-plus,srpingboot等依赖了. 1.maven依赖导入,版本号要与自己的版本号想同 <!--代码生成器依赖--><dependency>…

手撕 视觉slam14讲 ch13 代码 总结

运行效果 &#xff08;Kitti 00&#xff09;4倍速 一、代码 GitHub - tzy0228/Easy-VO-SLAM: VSLAM-CH13工程代码注释版本 二、编译过程 踩坑 视觉SLAM 十四讲第二版 ch13 编译及运行问题_全日制一起混的博客-CSDN博客 三、代码解读 手撕 视觉slam14讲 ch13 代码&#xff0…

共话医疗数据安全,美创科技@2023南湖HIT论坛,11月11日见

11月11日浙江嘉兴 2023南湖HIT论坛 如约而来 深入数据驱动运营管理、运营数据中心建设、数据治理和数据安全、数据资产“入表”等热点、前沿话题 医疗数据安全、数字化转型深耕者—— 美创科技再次深入参与 全新发布&#xff1a;医疗数据安全白皮书 深度探讨&#xff1a;数字…

7天入门python系列之准备工作

寄语 编者打算开一个python 初学主题的系列文章&#xff0c;用于指导想要学习python的同学。关于文章有任何疑问都可以私信作者。对于初学者想在7天内入门Python&#xff0c;这是一个紧凑的学习计划。但并不是不可完成的。 7天的安排 如果你想在7天内入门Python&#xff0c;…

QT开发的摄像头电子地图Demo(采用百度地图),提供源码下载

一、前言 本软件的工程是在QT-5.8 32位下开发&#xff0c;可以支持其他qtcreator 32位版本&#xff08;用32位是因为视频播放的码流库是32位&#xff09;。工程采用的地图是百度地图&#xff0c;需要在百度地图开发者网站上注册账号&#xff0c;并获取到密钥。本工程数据库采用…

【node+JS】前端使用nodemailer发送邮件

前言邮箱配置完整代码 前言 最近需要实现客户提交表单后&#xff0c;把表单的内容作为邮件发送到对应的邮箱&#xff0c;不通过后端服务&#xff0c;前端直接进行发送。嘶——&#xff0c;说干就干&#xff01; 一通搜索下来&#xff0c;get到方法有很多种&#xff0c;但是。。…

redis主从复制玩法全过程笔记(redis7+版本)

目录标题 环境目的实操一主多仆服务器和本地主机配置环境docker 环境配置 薪火相传反客为主 主从复制的流程主从复制的特性主从复制的缺点本篇结语 环境 我的环境介绍window环境VM虚拟机一台并安装centos7&#xff0c;一台阿里云Linux服务器&#xff0c;另一台Linux系统主机并…

【STM32】TIM2的PWM:脉冲宽度调制

PWM是一种周期固定&#xff0c;脉宽可调整的输出波形。 0.通用寄存器输出 1.捕获/比较通道1的主电路--中间部分 2.捕获/比较通道的输出部分--输出 3.通用定时器输出PWM原理 PWM波周期或者频率由ARR&#xff08;就是要进递增/递减的值&#xff09;决定&#xff0c;PWM波占空比由…

卫浴服务信息展示预约小程序的作用如何

卫浴产品多种多样&#xff0c;尤其对经销商来说&#xff0c;各种品牌规格的产品都有&#xff0c;品牌商也一样&#xff0c;该产品在市场中并不缺客户&#xff0c;但想要获取却绝非易事&#xff0c;那么卫浴商家面临哪些痛点&#xff0c;又该如何解决呢&#xff1f; 1、品牌传播…

Ionic header content footer toolbar UI实例

1 ionic的button图标 <ion-header [translucent]"true"><ion-toolbar><ion-buttons slot"start"><ion-back-button default-href"/tabs/tab1" text"back" icon"caret-back"></ion-back-button&…

Python实现JS逆向解密采集网站数据

嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 环境使用: Python 3.8 Pycharm nodejs 模块使用: import requests --> pip install requests import execjs --> pip install pyexecjs import json 实…

告诉大家4个常见的免费备份方法!

​什么是不花费一分钱的数据备份方式&#xff1f;对于那些有特殊兴趣爱好的用户来说&#xff0c;他们的常用存储设备里往往会充满各种各样丰富的数据。举个例子&#xff1a;对于那些热衷于探索四方的旅行者&#xff0c;随着他们足迹的延展&#xff0c;需要对数量众多的美丽景色…

每日汇评:黄金正在期待鲍威尔的讲话以获取新的方向动力

周三早盘&#xff0c;金价盘整了连续两天的跌势&#xff0c;等待鲍威尔发言&#xff1b; 美元在不同的美联储评论和风险偏好减弱的情况下寻求方向&#xff1b; 黄金价格确认了上升三角形的突破&#xff0c;但美债收益率较低可能会限制跌幅&#xff1b; 周二早些时候&#xf…

零基础Linux_26(多线程)线程池代码+单例模式+线程安全

目录 1. 线程池 1.1 前期代码 thread.hpp 1.2 加上锁的代码 lockGuard.hpp 1.3 加上任务的代码 1.4 加上日志的代码 log.hpp Task.hpp 2. 单例模式的线程安全 2.1 线程池的懒汉模式 threadPool.hpp testMain.cc 3. STL和智能指针的线程安全 4. 笔试题 答案及解…