Android自定义View技术人扯技术Android开发

Android自定义View-卷轴

2018-05-13  本文已影响96人  a49f87ef5d4f

0.

今天是母亲节,谨以此文献和此程序给我的母亲,节日快乐。

话不多说,先看效果

image

附上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()
}
上一篇下一篇

猜你喜欢

热点阅读