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()
}