Android自定义控件入门 08 自定义字母索引列表

182 阅读2分钟

1.效果实现分析

交互控件,onTouch()事件

1.1 实现默认效果

用画笔绘制[A-Z],自定义View

/**  
* 根据ASCII 表动态生成A~Z  
* */  
private val charArray by lazy {  
    mutableListOf<String>().apply {  
        val start = 65  
        val end = 65 + (26)  
        for (i in start until end) {  
            add(i.toChar().toString())  
        }  
        add("#")  
    }  
}

复写onMeasure()方法:指定宽高

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {  
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)  
    //计算指定宽度=左右padding+字母宽度(取决于画笔)  
    val textWidth = paint.measureText("A")  
    val weight = (paddingLeft + textWidth + paddingRight).toInt()  
    //高度可以直接获取  
    val height = MeasureSpec.getSize(heightMeasureSpec)  
    setMeasuredDimension(weight, height)  
}

字母不居中

//画26个字母 x绘制在最中间=宽度/2-文字/2  
val x=width/2-paint.measureText(content)/2

1.2 处理交互手指在上面触摸的效果

效果是: 手指触摸高亮当前位置

override fun onTouchEvent(e: MotionEvent): Boolean {  
    when (e.action) {  
        MotionEvent.ACTION_MOVE, MotionEvent.ACTION_DOWN -> {  
            //计算出当前触摸字母=>获取当前的位置  
            val currentMoveY = e.y  
            //位置=currentMoveY/字母高度,通过位置获取字母  
            val currentPosition = (currentMoveY / itemHeight).toInt()  
            currentTouchLetter = when {  
                (currentPosition < 0 )-> 0  
                (currentPosition > charArray.size - 1) -> charArray.size - 1  
                else -> currentPosition  
            }  
            //重新绘制  
            invalidate()  
        }  
    }  
    return true  
}

中间显示当前选择的字母

2.完整代码

<declare-styleable name="LetterSideBar">  
    <attr name="letterColor" format="color"/>  
    <attr name="letterSize" format="dimension"/>  
</declare-styleable>
class LetterSideBar @JvmOverloads constructor(  
    context: Context,  
    attr: AttributeSet? = null,  
    defStyle: Int = 0  
) : View(context, attr, defStyle) {  
    private val letterColor: Int  
    private val letterSize: Int  
    private val normalPaint: Paint  
    private val highlightPaint: Paint  
    lateinit var currentTextCallBack:TouchLetterListener  

    /**  
    * 当前触摸位置的字母  
    * */  
    private var currentTouchLetter = 0  

    init {  
        context.obtainStyledAttributes(attr, R.styleable.LetterSideBar).apply {  
            val defaultColor = Color.BLUE  
            val defaultTextSize = context.sp2px(15)  
            letterColor = getColor(R.styleable.LetterSideBar_letterColor, defaultColor)  
            letterSize = getDimensionPixelSize(R.styleable.LetterSideBar_letterSize, defaultTextSize)  
            normalPaint = producePaint(letterSize, letterColor)  
            highlightPaint = producePaint(letterSize, Color.RED)  
            recycle()  
        }  
    }  

    private fun producePaint(size: Int, paintColor: Int) =  
        with(Paint()) {  
            isAntiAlias = true  
            color = paintColor  
            textSize = size.toFloat()  
            this  
        }  


    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {  
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)  
        //计算指定宽度=左右padding+字母宽度(取决于画笔)  
        val textWidth = normalPaint.measureText("A")  
        val weight = (paddingLeft + textWidth + paddingRight).toInt()  
        //高度可以直接获取  
        val height = MeasureSpec.getSize(heightMeasureSpec)  
        setMeasuredDimension(weight, height)  
    }  

    override fun onTouchEvent(e: MotionEvent): Boolean {  
        when (e.action) {  
            MotionEvent.ACTION_MOVE-> {  
                //计算出当前触摸字母=>获取当前的位置  
                val currentMoveY = e.y  
                //位置=currentMoveY/字母高度,通过位置获取字母  
                val currentPosition = (currentMoveY / itemHeight).toInt()  
                val safePosition=when {  
                    (currentPosition < 0 )-> 0  
                    (currentPosition > charArray.size - 1) -> charArray.size - 1  
                    else -> currentPosition  
                }  
                if (safePosition==currentPosition){  
                    return true  
                }  
                currentTouchLetter = safePosition  
                if (::currentTextCallBack.isInitialized){  
                    currentTextCallBack.showLetter(charArray[currentTouchLetter])  
                    currentTextCallBack.isShowText(true)  
                }  
                //重新绘制  
                invalidate()  
            }  
            MotionEvent.ACTION_UP->currentTextCallBack.isShowText(false)  
        }  
        return true  
    }  

    /**  
    * 字母的高度  
    * */  
    private val itemHeight by lazy {  
        (height - paddingTop - paddingBottom) / charArray.size  
    }  

    /**  
    * 知道每个字母的中心位置  
    *  
    * 1.字母的高度一半  
    *  
    * 2.字母高度一般+前面字符的高度  
    *  
    * 基线,基于中心位置  
    * */  
    override fun onDraw(canvas: Canvas) {  
        super.onDraw(canvas)  
        for (i in charArray.indices) {  
            val content = charArray[i]  
            //画26个字母 x绘制在最中间=宽度/2-文字/2  
            val x = width / 2 - normalPaint.measureText(content) / 2  
            val letterCenterY = i * itemHeight + itemHeight / 2 + paddingTop  
            val dy = with(normalPaint.fontMetricsInt) {  
                (bottom - top) / 2 - bottom  
            }  
            val currentPaint = (if (currentTouchLetter == i) highlightPaint else normalPaint)  
            val baseLine = (letterCenterY + dy).toFloat()  
            canvas.drawText(content, x, baseLine, currentPaint)  
        }  
    }  


    /**  
    * 根据ASCII 表动态生成A~Z  
    * */  
    private val charArray by lazy {  
        mutableListOf<String>().apply {  
            val start = 65  
            val end = 65 + (26)  
            for (i in start until end) {  
                add(i.toChar().toString())  
            }  
            add("#")  
        }  
    }  

    interface TouchLetterListener{  
        fun isShowText(flag:Boolean)  
        fun showLetter(letter:String)  
    }  
  
}