如何看懂 Plaid 的线程化评论区:ThreadedCommentDrawable 自定义绘制原理全解 如何看懂 Plaid 的线程化评论区ThreadedCommentDrawable 自定义绘制原理全解【免费下载链接】plaidAn Android app which provides design news inspiration as well as being an example of implementing material design.项目地址: https://gitcode.com/gh_mirrors/pl/plaidPlaid 是一款开源的 Android 设计资讯应用也是 Material Design 的经典实现范例。它的评论区里有一个不起眼却非常巧妙的设计每条评论左侧的竖向细线直观地表达出回复的嵌套层级——回复几层就画几条线。这就是 Plaid 线程化评论区的核心。本文将带你从零看懂这个效果的实现从 UI 布局到ThreadedCommentDrawable这个自定义 Drawable 的完整绘制原理全程不需要深厚基础跟着源码走一遍即可。为什么用线条来表示回复层级打开一篇 Designer News 文章你会看到评论呈阶梯状排列一级评论靠左二级回复缩进一级左侧多出一条细线三级回复再缩进细线变成两条……这比传统的整块缩进更省空间也比连接线更轻快。整个方案只依赖三个文件非常值得学习文件职责ThreadedCommentDrawable.kt自定义 Drawable按层级画出 N 条竖线CommentViewHolder.kt把 Drawable 绑定到每条评论上designer_news_comment.xml评论布局承载深度指示器的 ImageViewThreadedCommentDrawable不到 100 行的绘制核心ThreadedCommentDrawable继承自 Android 的Drawable构造时只需要两个参数threadWidth单条线的宽度像素gap相邻线之间的间距像素这两个值来自全局尺寸资源 core/src/main/res/values/dimens.xmldimen namecomment_thread_width4dp/dimen dimen namecomment_thread_gap4dp/dimen第一条线画笔只画竖线内部用一个Paint样式为STROKE描边strokeWidth等于线宽颜色初始是一个占位色0xffff00ff亮粉色——真正的显示颜色后面会靠 tint 覆盖private val paint Paint().apply { style Paint.Style.STROKE strokeWidth threadWidth.toFloat() color THREAD_COLOR }第二条线setDepth 决定画几条线fun setDepth(depth: Int) { this.threads depth 1 invalidateSelf() }注意这里threads depth 1根评论depth 为 0也画 1 条线回复层级每加 1线就加 1 条。invalidateSelf()通知系统重绘这是自定义 Drawable 中状态变了要重画的标准姿势。第三条线draw() 里的关键一行override fun draw(canvas: Canvas) { for (thread in 0 until threads) { val left halfThreadWidth thread * (threadWidth gap) canvas.drawLine(left.toFloat(), 0f, left.toFloat(), bounds.bottom.toFloat(), paint) } }逐层拆解halfThreadWidth thread * (threadWidth gap)第thread条线的横坐标 半个线宽让第一条线完整落在画布内 递增的偏移。于是线与线之间恰好相隔threadWidth gap均匀排开canvas.drawLine(left, 0f, left, bounds.bottom, paint)从顶部画到bounds.bottom即整条评论的高度。线会自动长到和评论一样高无需额外计算文本高度——这是用 Drawable 而非在 Adapter 里手动测量的好处。第四条线getIntrinsicWidth 让 ImageView 自适应override fun getIntrinsicWidth(): Int { return threads * threadWidth (threads - 1) * gap }这个返回值就是布局中 ImageView 的实际宽度线越多指示器越宽。也就是说评论的缩进量是由 Drawable 自己算出来的布局侧只需要wrap_content完全不用关心层级。深色/浅色模式tint 的正确打开方式你可能注意到了源码里的线是亮粉色实际界面上却是淡淡的灰。秘密在于布局 designer_news_comment.xml 中 ImageView 的这一行android:tintcolor/thread_depth而 thread_depth.xml 定义的是colorOnSurface主题色 8% 透明度item android:color?attr/colorOnSurface android:alpha0.08 /Drawable 一侧则重写了setTintList用SRC_IN模式把占位色替换为主题色override fun setTintList(tint: ColorStateList?) { if (tint ! null) { setColorFilter(tint.defaultColor, SRC_IN) } else { clearColorFilter() } }这样白天是浅灰线、夜间是深色下的微亮线颜色跟随 Material 主题自动切换Drawable 本身一行颜色逻辑都不用改。装配现场CommentViewHolder 如何绑定CommentViewHolder.kt 的bind()方法是整个链条的收尾fun bind(model: CommentUiModel) { ... threadDepth.setImageDrawable( ThreadedCommentDrawable(threadWidth, threadGap, model.depth) ) }每次绑定都用评论模型 CommentUiModel.kt 里的depth新建一个 Drawable。这里有一个值得新手注意的取舍直接 new 而不是复用旧 Drawable。因为bind()只在可见项上执行数量可控换来的是逻辑简单、没有忘记 setDepth导致的层级错乱风险。另外这个 ViewHolder 还承担了一个有趣的交互长按某条评论进入聚焦模式时setExpanded()会利用drawable.intrinsicWidth把线滑出屏幕、把文字左移填满宽度——也就是说同一个 Drawable 的固有宽度既用于日常布局也用于动画位移量的计算一举两得。自己动手把这套方案搬进你的项目如果你的 App 也有楼中楼评论可以按这个清单照抄思路写一个继承Drawable的类draw()里按层级循环画竖线关键是把线的数量抽象为setDepth()getIntrinsicWidth()返回线数 × 线宽 间距让wrap_content的 ImageView 自动变宽布局里给 ImageView 设置android:tint指向一个带透明度的主题色资源实现昼夜自适应ViewHolder 绑定时用模型的depth字段重建 Drawable保持数据 → 宽度 → 缩进的单源。小结Plaid 的线程化评论区看似只是一排细线实际上把自定义 Drawable、intrinsic 尺寸、tint 主题色、ViewHolder 复用四个知识点串成了一条完整链路draw()中一行等差数列算出每条线的位置getIntrinsicWidth()让层级数自动变成缩进量setTintList() 主题色让细线在深浅色模式下都恰到好处。源码总共不到 200 行含注释是学习自定义 Drawable 怎么做才优雅的极佳样本。建议对照 designernews/src/main/java/io/plaidapp/ui/drawable/ 目录边读边改几分钟就能在自己的项目里画出一模一样的效果。【免费下载链接】plaidAn Android app which provides design news inspiration as well as being an example of implementing material design.项目地址: https://gitcode.com/gh_mirrors/pl/plaid创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考