Android自定義View-卷軸

weixin_34370347發表於2018-05-13

0.

今天是母親節,謹以此文獻和此程式給我的母親,節日快樂。

話不多說,先看效果

附上git地址 Github

1.

想好再動筆,整個卷軸有左右卷軸柄,紙和文字組成,預設卷軸關閉,點選是張開,張開後點選就合上,開啟時速度由快到慢。功能分析到這,思路也就很明確了,那就動筆吧。

2.

private var dis = 0 //每個卷軸到中心的距離

private var textColor = Color.BLACK //文字顏色

private var reelColor = Color.RED //卷軸柄的顏色

private var paperColor = Color.WHITE //紙的顏色

private var textSize = 20f //文字顏色

private var text = "" //卷軸上的文字

private var reelWidth = 40f //卷軸柄寬度

private var duration = 3000 //卷軸開啟所需時間

private lateinit var disAnimator: ValueAnimator //操控捲軸開啟進度

private var isExpand = false  //卷軸是否處於開啟狀態

private var reelTopBarHeight = 20f //卷軸柄上端小木塊的高度

private var lineOffset = 10f //紙上分割線的距離
複製程式碼

這裡要說到的是dis變數用來控制每個卷軸柄到中心的距離,也是控制紙寬度的引數,而disAnimator是用來控制dis的改變,同時為了控制卷軸開啟速度和總時間的。

private fun initAnimator()
{
    disAnimator = ValueAnimator.ofInt(0, duration / 1000)
    disAnimator.duration = duration.toLong()
    disAnimator.interpolator = AccelerateDecelerateInterpolator()
    disAnimator.addUpdateListener {

        dis = (it.animatedFraction * (width / 2 - reelWidth)).toInt()
        postInvalidate()
    }
}
複製程式碼

disAnimator設定了的值的變化範圍是從0到總時間/1000, 由dis = (it.animatedFraction * (width / 2 - reelWidth)).toInt()可知 0<=dis<=width/2-reelWidth

3.

實現文字隨著紙張的展開逐漸顯露出來的效果,這是這個控制元件的重點。首先看drawText方法

private fun drawText(canvas: Canvas)
{
    textSize = Math.min(textSize, (height - reelTopBarHeight * 2 - lineOffset * 2))
    paint.isFakeBoldText = true
    paint.textSize=textSize
    val centerX = width.toFloat() / 2
    val centerY = height.toFloat() / 2
    val rect = Rect()
    paint.getTextBounds(text, 0, text.length, rect)
    canvas.drawText(text, centerX - rect.width() / 2, centerY + rect.height() / 2, paint)
}
複製程式碼

很簡單,就是講文字繪製在中心位置。

再看darwPaper方法

private fun drawPaper(canvas: Canvas)
{
    val centerX = width.toFloat() / 2
    val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
    val canvasTmp = Canvas(bitmap)
    paint.color = paperColor
    canvasTmp.drawRect(centerX - dis, reelTopBarHeight, centerX + dis, height - reelTopBarHeight, paint)
    paint.color = Color.BLACK
    paint.strokeWidth = 2f
    canvas.drawBitmap(bitmap, 0f, 0f, paint)
}
複製程式碼

繪製寬度隨dis改變的紙。

要想實現文字只在已有寬度的紙上顯示,那麼我們就需要接住xfermode了,關於xfermode我就不仔細說了,不懂的請自行百度,這裡我們採用DST_ATOP來實現這個功能,大家可能發現為什麼drawPaper的時候生成了一個bitmap,和一個tempCanvas,最終繪製的是一個bitmap,那是因為為了控制它的透明區域不要太小,要讓它足夠覆蓋到要和它結合繪製的文字內容,否則得到的結果很可能不是你想要的 看onDraw方法

override fun onDraw(canvas: Canvas?)
{
    super.onDraw(canvas)
    canvas?.let {

        val count = it.saveLayer(null, null, Canvas.ALL_SAVE_FLAG) //設定離屏緩衝,不設定的話會有問題
        paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_ATOP) //設定xfermode
        drawText(it) //繪製文字
        drawPaper(it) //繪製紙
        paint.xfermode = null
        drawPaerLines(it) //繪製紙上的線
        it.restoreToCount(count) //恢復
        drawReels(it) //繪製卷軸柄
    }

}
複製程式碼

4.

根據狀態來判斷點選時卷軸是開啟還是收縮回去

override fun onTouchEvent(event: MotionEvent?): Boolean
{
    when (event?.action)
    {
        MotionEvent.ACTION_DOWN ->
        {
            if (!disAnimator.isRunning)
            {
                if (!isExpand)
                {
                    val centerX = width / 2.toFloat()
                    if (event.x >= centerX - reelWidth && event.x <= centerX + reelWidth)
                    {
                        startAnimator()
                        isExpand = true
                    }
                }
                else
                {
                    disAnimator.reverse()
                    isExpand = false
                }
            }
            return true
        }
    }
    return false
}
複製程式碼

5.

因為使用了Anmator,為了避免記憶體洩漏,注意新增以下程式碼

override fun onDetachedFromWindow()
{
    super.onDetachedFromWindow()
    disAnimator.cancel()
}
複製程式碼

公眾號:滑板上的老砒霜,定期分享技術原創文章。

相關文章