你能想象一段文字看起来像"浮在水面上",随着波浪上下起伏、半沉半浮吗?这不是特效视频,而是 Titanic——一个由 Romain Piel 开发的 Android 动画库。它通过对 TextView 的 TextPaint 应用动画化的 BitmapShader,让文字呈现出冰山浮水般的波浪效果。虽然项目已归档(DEPRECATED),但它展示的"Shader + Matrix 动画"技巧至今仍是 Android 自定义文字动画的经典教材。本文讲清它是什么、怎么实现的,以及即使项目归档,它的核心技术为何依然值得学。
GitHub 仓库:romainpiel/Titanic(已归档,源码仍可学习)。
1、Titanic 是什么
Titanic 是一个 Android 自定义 TextView 动画库,名字取自"泰坦尼克号"——文字就像那艘沉船一样"半沉在水中"。它的视觉效果是:文字底部有一道波浪纹理,波浪持续横向流动,文字看起来像被水漫过、随波起伏。实现原理是给 TextPaint 设置一个 BitmapShader(波浪位图),然后持续动画地平移这个 Shader 的 Matrix,让"水"看起来在流动。
// 核心定位
Titanic = 给 TextView 的文字加"水波沉浮"动画
// 原理一句话
// TextPaint.setShader(BitmapShader(waveBitmap))
// + 每帧用 Matrix 平移 shader = 水波流动效果
// 基本面
// 作者:Romain Piel(同作者还有 Shimmer-android,也是经典文字动画)
// 状态:2020 年归档(DEPRECATED),源码只读,但技术不过时
// 星标:1.7k+ Star(同类文字动画中的经典)
2、核心原理:Shader + Matrix 动画
Titanic 最值得学的不是"一个现成动画",而是它揭示的一个高级 Paint 技巧:用 Shader 给文字"贴图"。理解这个原理,你能举一反三做出无数种文字效果。
// 原理拆解:三步实现"文字水波"
// 第一步:准备一张波浪图案的 Bitmap(水平重复的波纹)
// 可以是 PNG 资源,也可以程序化绘制
val waveBitmap = BitmapFactory.decodeResource(resources, R.drawable.wave)
// 第二步:创建 BitmapShader,设为水平重复(REPEAT X)
// BitmapShader 把 bitmap 当"颜料",文字画出来时纹理自动贴上去
val shader = BitmapShader(waveBitmap, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP)
// 第三步:把 shader 挂到 TextView 的 TextPaint 上
paint.shader = shader
// → 此时文字不再是纯色,而是"用波浪纹理填充"的
// 第四步(动画关键):每帧用 Matrix 横向平移 shader
val matrix = Matrix()
ValueAnimator.ofFloat(0f, 1f).apply {
repeatCount = ValueAnimator.INFINITE
addUpdateListener {
val dx = it.animatedValue as Float * waveBitmap.width
matrix.setTranslate(dx, 0f) // 横向移动
shader.setLocalMatrix(matrix) // 更新 shader 的变换
invalidate() // 触发重绘
}
start()
}
// → shader 每"往右挪一点",视觉上水波就在"往右流" 这套"BitmapShader + Matrix 平移 + invalidate 循环"是 Android 纹理动画的万能公式。不止水波——换个 bitmap 就是"火焰文字""渐变流光文字""扫描线文字"。Titanic 用最少的代码演示了这个公式,这正是它被归档后仍被反复提及的原因。
3、TitanicTextView 的实现
库的核心类是 TitanicTextView,继承自 TextView,重写了 onSizeChanged 和 onDraw:
class TitanicTextView(context: Context, attrs: AttributeSet?) : AppCompatTextView(context, attrs) {
private var shader: BitmapShader? = null
private var matrix = Matrix()
private var waveBitmap: Bitmap? = null
private var shift = 0f // 波浪偏移量(动画驱动)
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
// 控件大小确定后,创建适配尺寸的波浪 bitmap + shader
waveBitmap = createWaveBitmap(w, h)
shader = BitmapShader(waveBitmap!!, Shader.TileMode.REPEAT, Shader.TileMode.CLAMP)
paint.shader = shader // 挂到 TextPaint
}
override fun onDraw(canvas: Canvas) {
// 每帧更新 shader 的偏移,让水波"流动"
matrix.setTranslate(shift, 0f)
shader?.setLocalMatrix(matrix)
super.onDraw(canvas) // 调用父类 onDraw(它用 TextPaint 画文字)
// → TextPaint 带了 shader,所以文字画出来就是波浪纹理
}
}
// 外部启动动画(Titanic 类负责)
fun start(tv: TitanicTextView) {
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 1000
repeatCount = ValueAnimator.INFINITE
repeatMode = ValueAnimator.RESTART
addUpdateListener {
tv.shift = it.animatedValue as Float * waveWidth
tv.invalidate()
}
start()
}
} 一个巧妙的点是不在 TitanicTextView 里启动动画,而是由外部 Titanic 类控制——这样动画可以随时启停、配置时长和重复模式,解耦了"视图"和"动画"。这种"View 只负责画、外部类负责动"的分离,是自定义动画 View 的好架构。
4、使用方式
// XML 布局
// <com.romainpiel.titanic.library.TitanicTextView
// android:layout_width="wrap_content"
// android:layout_height="wrap_content"
// android:text="TITANIC"
// android:textColor="#FF4081"
// android:textSize="48sp" />
// 代码中启动动画
val tv = findViewById<TitanicTextView>(R.id.titanic_tv)
Titanic().start(tv) // 开始波浪动画
// 停止动画
Titanic().cancel(tv)
// 自定义波浪图片(默认用库自带的蓝色波浪)
// 替换 R.drawable.wave 可实现不同纹理(火焰、渐变等)
- 注意:库已归档(DEPRECATED),JitPack/Central 可能无法直接依赖,建议拷源码集成
- textColor 是"shader 不覆盖区域的底色",实际波浪纹理来自 Bitmap
5、从 Titanic 学到的:Shader 的力量
Titanic 的核心技术不在"水波"本身,而在"Shader + Matrix"这套机制。一旦你理解了"Shader 是给 Paint 的颜料、Matrix 控制颜料的变换",就能衍生出无数效果:
// 举一反三:用同样的"Shader + Matrix 动画"公式做出不同效果
// ① 流光渐变文字(LinearGradient + 横向平移)
shader = LinearGradient(0, 0, width, 0, color1, color2, Shader.TileMode.CLAMP)
// → 渐变会随 Matrix 移动,形成"流光"
// ② 扫描线文字(用透明+不透明交替的 bitmap 做 shader)
// → 文字像被扫描线"刷"过
// ③ 火焰/烟雾文字(用火焰纹理 bitmap 做 shader)
// → 文字像在燃烧
// ④ 灵魂出窍(RadialGradient 做发光底 + 模糊)
// → 文字带"光晕"效果
// 核心不变:paint.shader = 你选的 shader
// shader.setLocalMatrix(动画驱动的 matrix)
// 每帧 invalidate 重画
Android 的 Paint/Shader 体系,和"Photoshop 的图层混合模式"一样强大,但很多开发者从不碰它——因为不知道"能做什么"。Titanic 用最短的代码展示了一个完整案例:原来 Shader 不只是"设置颜色",还能给任何绘制内容贴上动态纹理。这个认知一旦打开,你的自定义 View 表现力会上一个大台阶。
6、与 Shimmer 对比:同作者的两套思路
库 效果 原理 作者
---------------------------------------------------------------------------
Titanic 水波沉浮 BitmapShader + Matrix Romain Piel
Shimmer-android 流光扫过 LinearGradient + 平移 Romain Piel
// 同一作者,两个库,展示了 Shader 动画的两种经典用法:
// Titanic = BitmapShader(位图纹理贴到文字上)
// Shimmer = LinearGradient(线性渐变扫过文字)
// 两者核心都是"Shader + Matrix + invalidate 循环",区别只在 shader 类型
如果你对文字动画感兴趣,Titanic 和 Shimmer 是一对"最佳教材"——两个都读了,你就掌握了"动态纹理"和"动态渐变"两大套路,足以自己组合出几十种文字效果。Facebook 的 Shimmer 库(跨平台版)灵感也来自 Romain Piel 的这个 Android 实现。
7、归档了还能用吗
// 项目状态:DEPRECATED(2020 年归档),不再维护
// - GitHub 只读,不接受 PR/Issue
// - JitPack 依赖可能失效
// 能不能用?能,但有条件:
// 1. 拷源码集成(推荐)
// 核心就两个类:TitanicTextView + Titanic,约 200 行
// 拷进项目、改个包名、按需调波浪 bitmap 即可
// 2. 适配 AndroidX
// 把 android.support 替换为 androidx
// 继承 AppCompatTextView 而非 TextView
// 3. Compose 项目
// Titanic 不兼容 Compose
// Compose 中可用 drawText + BitmapShader 等价实现(原理相同)
// 结论:库归档了,但"Shader + Matrix 动画"的技术永远不会过时
8、最佳实践与扩展
- 推荐"拷源码 + 理解原理 + 自己改",而非依赖归档库
- 波浪 bitmap 可程序化绘制(Canvas 画贝塞尔曲线),避免依赖外部 PNG
- 动画时长/方向可配(横向流、纵向起伏)
- 性能:Shader + invalidate 每帧重绘,长列表中不宜大量使用
- 同类库:Shimmer-android(同作者)、HTextView(多种文字动画)、TimelyTextView
9、总结
Titanic 是 Romain Piel 的经典 Android 文字动画库,通过BitmapShader + Matrix 动画让文字呈现水波沉浮效果。虽然项目已归档,但它展示的"Shader + Matrix + invalidate 循环"万能公式至今是 Android 自定义文字动画的核心技术。理解了这个原理,你能做出流光、扫描线、火焰、渐变等无数种文字效果。
关键要点:
- 定位:文字水波沉浮动画,TextPaint 挂 BitmapShader + Matrix 平移驱动
- 万能公式:paint.shader = 你的 shader → shader.setLocalMatrix(动画 matrix) → invalidate
- 举一反三:换 bitmap/gradient 就能做出火焰、流光、扫描线等效果
- 归档了但技术不过时:拷源码集成 + 改包名 + 自定义纹理即可复用
- 同作者 Shimmer-android 是"姊妹篇"(LinearGradient 版),两本一起读更完整
对于想在 Android 上做炫酷文字动画的开发者而言,Titanic 是一个"小而精"的学习对象——它用不到 200 行代码,完整演示了"Shader + Matrix"这套高级 Paint 技巧。库会归档,但原理不会过时。读懂 Titanic,你掌握的不是"一个水波效果",而是"给任何绘制内容贴动态纹理"的通用能力——这才是它真正的价值。
参考资料:
romainpiel/Titanic(GitHub,已归档但源码可学)
romainpiel/Shimmer-android(同作者姊妹篇:流光扫过动画)