Compose挑灯夜看 - 照亮手机屏幕里面的书本内容

3,363 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情

一、前言

上一篇文章 Compose回忆童年 - 手拉灯绳-开灯/关灯里面82年钨丝灯,让我又有了新的想法,我们怎么照亮手机里面的文本内容呢?

我们会在上一篇文章的基础上来实现“挑灯夜看”的功能,怎么下手呢?往下看👇

二、文本着色器

我们想要实现照亮功能,那肯定需要有不亮的文本内容。

通过透明度来可以吗?肯定不行,文本内容是可以上下滑动的,是一个整体,我们不能通过透明度来做。

在看到小米手机的文本着色效果之后:

小米万象息屏.png

我知道如何下手了,我先来看看ComposeText如何做渐变着色?

1. 有些同学可能喜欢用Canvas去绘制:

Canvas(...) {
   drawIntoCanvas { canvas ->
       canvas.nativeCanvas.drawText(text, x, y, paint)
   }
}

2. 我们可以使用ModifeirdrawWithCache修饰符,官方文档的链接里面也给我们了不少示例。

QQ20220830-203813@2x.png

Text(
    text = "永远相信美好的事情即将发生❤️",
    modifier = Modifier
        .graphicsLayer(alpha = 0.99f)
        .drawWithCache {
            val brush = Brush.horizontalGradient(
                listOf(
                     Color(0xFFE24CE2),
                     Color(0xFF73BB70),
                     Color(0xFFE24CE2)
                )
             )
             onDrawWithContent {
                 drawContent()
                 drawRect(brush, blendMode = BlendMode.SrcAtop)
             }
         }
)

上面代码,我们使用到了BlendMode,我们这里用的是BlendMode#SrcAtop: 将源图像合成到目标图像上,仅限于与目标重叠的位置,确保只有文本可见并且矩形的其余部分被剪切。

3. Google在Compose1.2.0-beta01API变更里面,向TextStyleSpanStyle添加了 Brush API,以提供使用渐变颜色绘制文本的方法。

兄弟们支持了吗.png

private val GradientColors = listOf(
        Color(0xFF00FFFF), Color(0xFF97E063),
        Color(0xFFE24CE2), Color(0xFF97E063)
)
Text(
    modifier = Modifier.align(Alignment.Center).requiredWidthIn(max = 250.dp),
    text = "永远相信美好的事情即将发生❤️,我们不会期待米粉的期待!\n\n兄弟们支持了吗?",
    style = TextStyle(
        brush = Brush.linearGradient(
           colors = GradientColors
       )
    )
)

我们可以看到Emoji表情没有被着色,非常Nice。

我们看一下linearGradient/verticalGradient/radialGradient/sweepGradient效果对比:

linearGradient.png verticalGradient.png

左边的是linearGradient右边的是verticalGradient

4444.png 5555.png

左边的是radialGradient右边的是sweepGradient

还有一种内置的BrushSolidColor,填充指定颜色。

查看Brush#LinearGradient源码发现它继承自ShaderBrush

// androidx.compose.ui.graphics.Brush
class LinearGradient internal constructor(
    private val colors: List<Color>,
    private val stops: List<Float>? = null,
    private val start: Offset,
    private val end: Offset,
    private val tileMode: TileMode = TileMode.Clamp
) : ShaderBrush()

自定义ShaderBrush,可以修改画笔大小,那么我们也来整一个,用于下面的钨丝灯的照亮效果,刚刚上面还介绍了到一个gradient符合我们的要求,radialGradient,更多的源码细节,这里就不做深入介绍,夜深了哈哈哈。

我们接下来需要初始化一个ShaderBrush

object : ShaderBrush() {
    override fun createShader(size: Size): Shader {
        return RadialGradientShader(
            center = ...,
            radius = ...,
            colors = ...
        )
    }
    ...
}

三、实现照亮文本

刚刚上面初始化了一个ShaderBrush,我们照亮文本内容,文本内容不可能只有一屏对吧,肯定需要支持滑动文本,那要怎么做呢?

我想肯定有掘友知道了,我们可以用ModifierverticalScroll修饰符,记录滚动状态ScrollState,然后设置到RadialGradientShadercenter里面。

我们这里的文本内容引用了:三国演义的第一章内容,我们同样需要上一篇文章RopHandleState

private fun ComposeText(state: RopeHandleState) {
    Text(
        text = sanguoString,
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
            .verticalScroll(state.scrollState),
        style = LocalTextStyle.current.merge(
            TextStyle(
                fontSize = 18.sp,
                brush = state.lightContentBrush
            )
        )
    )
}

这里我们用到了TextStyle#Brush的API,同时也添加了滚动修饰符,因为我们需要上下滑动文本,保证“钨丝灯”能照亮我们的文本内容。

我们在RopHandleState里面初始化ScrollState

val scrollState = ScrollState(0)

private val scrollOffset by derivedStateOf {
   // 这里增加Y轴的距离
   Offset(size.width / 2F, scrollState.value.toFloat() + size.width * 0.2F)
}

可以滚动,我们需要把滚动的距离同步给我们的ShaderBrush

// isOpen == true,钨丝灯亮了需要初始化ShaderBrush
object : ShaderBrush() {
     override fun createShader(size: Size): Shader {
     lastScrollOffset = Offset(size.width/2F, scrollOffset.y)
     return RadialGradientShader(
            center = lastScrollOffset!!,
            radius = size.minDimension,
            colors = listOf(Color.Yellow, Color(0xff85733a), Color.DarkGray)
           )
     }
     override fun equals(other: Any?): Boolean {
          return lastScrollOffset?.y == scrollOffset.y
     }
}

// isOpen == false,钨丝灯灭了
SolidColor(Color.DarkGray)

根据“钨丝灯”的状态,返回不同的Brush:

val lightContentBrush by derivedStateOf {
    if(isOpen) {
        object : ShaderBrush() { ... }
    } else {
        SolidColor(Color.DarkGray)
    }
}

这里需要注意一下,我们在打开和关闭钨丝灯的时候,需要把lastScrollOffset设置为初始状态值

fun toggle() {
    isOpen = !isOpen
    lastScrollOffset = Offset.Zero
}

其他相关的代码,请参考上一篇文章 Compose回忆童年 - 手拉灯绳-开灯/关灯

我们来看看最终效果吧

2022-08-30 22_18_31.gif

延伸:这里其实还可通过手指触摸指定范围区域内高亮哦,有兴趣的可以去试试!!