高仿PPT特殊文字效果,TextView实现

2,740 阅读3分钟

本文正在参加「金石计划」

事情是这样的,我无聊刷到一个B站视频【旁门左道PPT】我发现了大厂发布会中,少文字PPT还贼高级的秘密!。看到视频中这个特殊的PPT文字效果,个人感觉非常高端。我就想,能不能用TextView来实现。于是就有了这篇文章,效果如下图:

简单填充加入文字排版加入动画

图片填充

在Android中,google提供了 BitmapShader 来实现图片填充的功能。代码如下

public BitmapShader(@NonNull Bitmap bitmap, 
                        @NonNull TileMode tileX, 
                        @NonNull TileMode tileY)

参数介绍:

● bitmap:用来做填充的 Bitmap 对象

● tileX:横向的 TileMode(平铺模式)

● tileY:纵向的 TileMode

TileMode有三种:分别是 Shader.TileMode.CLAMP、Shader.TileMode.MIRROR、Shader.TileMode.REPEAT

● Shader.TileMode.CLAMP:如果着色器超出原始边界范围,会复制边缘颜色。

● Shader.TileMode.MIRROR:横向和纵向的重复着色器的图像,交替镜像图像是相邻的图像总是接合。

● Shader.TileMode.REPEAT: 横向和纵向的重复着色器的图像。

接下来,我们自定义 TextView,让它使用我们定义的Shader,代码如下:

class MaskTextView: androidx.appcompat.widget.AppCompatTextView {

    constructor(context: Context) : super(context)
    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    private var shader: BitmapShader? = null

    fun setMaskDrawable(source: Drawable): Unit {
        val maskW: Int = source.getIntrinsicWidth()
        val maskH: Int = source.getIntrinsicHeight()

        val b = Bitmap.createBitmap(maskW, maskH, Bitmap.Config.ARGB_8888)
        val c = Canvas(b)

        c.drawColor(currentTextColor)
        source.setBounds(0, 0, maskW, maskH)
        source.draw(c)
        
        shader = BitmapShader(b, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP)
        paint.shader = shader
    }
}

免费壁纸网站中找到一个你喜欢的图片,调用 setMaskDrawable 方法时,我们就可以看到填充后的效果了。效果如下:

但是光这个效果还不够,还需要设置文字排版。看【旁门左道PPT】我发现了大厂发布会中,少文字PPT还贼高级的秘密!我们知道,有三种文字排版,分别是 高低低高、高低高低、低高低高,它们都需要修改文字的 baseline 来实现。

如何修改单个字符的 baseline 呢?很简单,不需要重写 onDraw 方法。我们可以自定义 Span,然后通过 TextPaint 来实现。在上代码前,先介绍一下 TextPaint,TextPaint 继承 Paint,在绘制和测量文本时给Android一些额外的数据。它的属性介绍如下:

● baselineShift - 基线是文本底部的线。改变baselineShift会使基线向上或向下移动,所以它影响到文本在一条线上的绘制高度。

● bgColor - 这是文本后面的背景颜色。

● density - 暂不清楚它的作用

● drawableState - 暂不清楚它的作用

● linkColor - 一个链接的文本颜色。

可以看到我们只需要修改 baselineShift 就可以改变单个文字的 baseline 了,自定义的Span的代码如下:

class TextUpOrDownSpan(private val isUp:Boolean, private val offset: Int): CharacterStyle() {

    override fun updateDrawState(tp: TextPaint?) {
        tp?.baselineShift = if(isUp) - offset else offset
    }
}

效果如下:

添加一个波浪动画

我们也可以给我们的图片填充增加一个动画,其中最常见的就是波浪动画了。效果实现很简单:

第一步:在波浪效果网站上下载一张自己想要的波浪图片

第二步:创建自定义的TextView,加上对应的参数,方便做动画。代码如下:

class AnimatorMaskTextView: androidx.appcompat.widget.AppCompatTextView {

    private var shader: BitmapShader? = null
    private var shaderMatrix: Matrix = Matrix()
    private var offsetY = 0f
    var maskX = 0f
        set(value) {
            field = value
            invalidate()
        }
    var maskY = 0f
        set(value) {
            field = value
            invalidate()
        }

    fun setMaskDrawable(source: Drawable): Unit {
        val maskW: Int = source.getIntrinsicWidth()
        val maskH: Int = source.getIntrinsicHeight()

        val b = Bitmap.createBitmap(maskW, maskH, Bitmap.Config.ARGB_8888)
        val c = Canvas(b)

        c.drawColor(currentTextColor)
        source.setBounds(0, 0, maskW, maskH)
        source.draw(c)

        shader = BitmapShader(b, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP)
        paint.shader = shader
        offsetY = ((height - maskH) / 2).toFloat()
    }

    override fun onDraw(canvas: Canvas?) {
        shaderMatrix.setTranslate(maskX, offsetY + maskY)
        shader?.setLocalMatrix(shaderMatrix)
        paint.shader = shader
        super.onDraw(canvas)
    }
}

第三步:使用Android的动画api,控制图片的位置。代码如下:

val maskXAnimator: ObjectAnimator = 
    ObjectAnimator.ofFloat(textView, "maskX", 0f, textView.width.toFloat())
val maskYAnimator: ObjectAnimator =
    ObjectAnimator.ofFloat(textView, "maskY", 0f, (-textView.getHeight()).toFloat())
val animatorSet = AnimatorSet()
animatorSet.playTogether(maskXAnimator, maskYAnimator)
animatorSet.start()

效果如下:

参考