安卓 车轮视图 WheelView kotlin-CSDN博客

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

安卓 车轮视图 WheelView kotlin


在这里插入图片描述

前言

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


一、代码解析

参数

    /**
     * 控件宽度
     */
    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的自带方法onMeasureintint必须调用此方法来存储测量的宽度和测量的高度。否则将在测量时触发异常。
参数

  • 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相关能力。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: android