立刻点赞展现
点赞的数字增加和削减并不是整个替换,而是差异化替换。再加上动画作用就看的很舒服。
自己怎么实现这种数字切换呢?
下面用一张图来展现我的思路:
现在只需要根据这张图,写出对应的动画即可。 分为2种场景:
- 数字+1:
- 差异化的数字从3号区域由突变动画(透明度 0- 255) + 偏移动画 (3号区域制作文字的基线,2号区域制作文字的基线),将数字移动到2号方位处
- 差异化的数字从2号区域由突变动画(透明度 255- 0) + 偏移动画(2号区域制作文字的基线,1号区域制作文字的基线),将数字移动到1号方位处
- 数字-1
- 差异化的数字从1号区域由突变动画(透明度 0- 255) + 偏移动画 (1号区域制作文字的基线,2号区域制作文字的基线),将数字移动到2号方位处
- 差异化的数字从2号区域由突变动画(透明度 255- 0) + 偏移动画(2号区域制作文字的基线,3号区域制作文字的基线),将数字移动到3号方位处
公共部分便是: 不变的文字不需要做任何处理,制作在2号区域就行。制作差异化文字时,需要加上不变的文字的宽度就行。
作用展现
源码
class LikeView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private val paint = Paint().also {
it.isAntiAlias = true
it.textSize = 200f
}
private val textRect0 = Rect(300, 100, 800, 300)
private val textRect1 = Rect(300, 300, 800, 500)
private val textRect2 = Rect(300, 500, 800, 700)
private var nextNumberAlpha: Int = 0
set(value) {
field = value
invalidate()
}
private var currentNumberAlpha: Int = 255
set(value) {
field = value
invalidate()
}
private var offsetPercent = 0f
set(value) {
field = value
invalidate()
}
private val fontMetrics: FontMetrics = paint.fontMetrics
private var currentNumber = 99
private var nextNumber = 0
private var motionLess = currentNumber.toString()
private var currentMotion = ""
private var nextMotion = ""
private val animator: ObjectAnimator by lazy {
val nextNumberAlphaAnimator = PropertyValuesHolder.ofInt("nextNumberAlpha", 0, 255)
val offsetPercentAnimator = PropertyValuesHolder.ofFloat("offsetPercent", 0f, 1f)
val currentNumberAlphaAnimator = PropertyValuesHolder.ofInt("currentNumberAlpha", 255, 0)
val animator = ObjectAnimator.ofPropertyValuesHolder(
this,
nextNumberAlphaAnimator,
offsetPercentAnimator,
currentNumberAlphaAnimator
)
animator.duration = 200
animator.interpolator = DecelerateInterpolator()
animator.addListener(
onEnd = {
currentNumber = nextNumber
}
)
animator
}
override fun onDraw(canvas: Canvas) {
paint.alpha = 255
paint.color = Color.LTGRAY
canvas.drawRect(textRect0, paint)
paint.color = Color.RED
canvas.drawRect(textRect1, paint)
paint.color = Color.GREEN
canvas.drawRect(textRect2, paint)
paint.color = Color.BLACK
if (motionLess.isNotEmpty()) {
drawText(canvas, motionLess, textRect1, 0f)
}
if (nextMotion.isEmpty() || currentMotion.isEmpty()) {
return
}
val textHorizontalOffset =
if (motionLess.isNotEmpty()) paint.measureText(motionLess) else 0f
if (nextNumber > currentNumber) {
paint.alpha = currentNumberAlpha
drawText(canvas, currentMotion, textRect1, textHorizontalOffset, -offsetPercent)
paint.alpha = nextNumberAlpha
drawText(canvas, nextMotion, textRect2, textHorizontalOffset, -offsetPercent)
} else {
paint.alpha = nextNumberAlpha
drawText(canvas, nextMotion, textRect0, textHorizontalOffset, offsetPercent)
paint.alpha = currentNumberAlpha
drawText(canvas, currentMotion, textRect1, textHorizontalOffset, offsetPercent)
}
}
private fun drawText(
canvas: Canvas,
text: String,
rect: Rect,
textHorizontalOffset: Float = 0f,
offsetPercent: Float = 0f
) {
canvas.drawText(
text,
rect.left.toFloat() + textHorizontalOffset,
rect.top + (rect.bottom - rect.top) / 2f - (fontMetrics.bottom + fontMetrics.top) / 2f + offsetPercent * 200,
paint
)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
animator.end()
}
fun plus() {
if (currentNumber == Int.MAX_VALUE) {
return
}
nextNumber = currentNumber + 1
processText(findEqualsStringIndex())
if (animator.isRunning) {
return
}
animator.start()
}
fun minus() {
if (currentNumber == 0) {
return
}
nextNumber = currentNumber - 1
processText(findEqualsStringIndex())
if (animator.isRunning) {
return
}
animator.start()
}
private fun findEqualsStringIndex(): Int {
var equalIndex = -1
val nextNumberStr = nextNumber.toString()
val currentNumberStr = currentNumber.toString()
val endIndex = min(currentNumberStr.length, nextNumberStr.length) - 1
for (index in 0..endIndex) {
if (nextNumberStr[index] != currentNumberStr[index]) {
break
}
equalIndex = index
}
return equalIndex
}
private fun processText(index: Int) {
val currentNumberStr = currentNumber.toString()
val nextNumberStr = nextNumber.toString()
if (index == -1) {
motionLess = ""
currentMotion = currentNumberStr
nextMotion = nextNumberStr
} else {
motionLess = currentNumberStr.substring(0, index + 1)
currentMotion = currentNumberStr.substring(index + 1)
nextMotion = nextNumberStr.substring(index + 1)
}
}
}