跳转至

新增功能:Material 涟漪效果

tkdeft 本身不带组件,这次也没有加组件——加的是一个可以接到任何控件上的效果: tkdeft.extra.ripple。你的按钮照旧自己画,只要在点击处记一笔、在重绘里贴一层。

from tkdeft.extra.ripple import Ripple

self.ripple = Ripple(self, on_frame=self._paint)   # 建控制器,每帧回调重绘
self.ripple.start(event.x, event.y)                # 点击处起涟漪
self.ripple.paint(color="#000000", opacity=0.12,   # 在合适层级贴一层
                  corner_radius=8)

tkdeft.extra 是新开的子包:核心包继续只提供零件,可选功能单独放、按需加载 (import tkdeft 不会顺带把 Pillow 的 ImageTk 拉起来)。

examples/ripple_button.py 里有一个完整的按钮示范:圆角底色、悬停 / 按压 / 焦点 / 禁用、空格触发、T 换主题都由示例自己画,涟漪只借这三行。也带自检:

python examples/ripple_button.py                # 默认自动挑栅格引擎
python examples/ripple_button.py --engine tksvg # 换引擎看每帧开销
python examples/ripple_button.py --check        # 自检,不开窗口

值得单独说的几点

  • 半透明涟漪没有用 create_oval:Tk 的 canvas item 没有逐像素 alpha。 这里在 Pillow 里算「圆 ∩ 圆角矩形」的遮罩,超采样 3 倍再缩回,得到一张 与控件等大的 RGBA 图层,create_image 贴上去。底色画在它下面、文字画在上面, 层级由宿主决定。
  • 动画进度按 time.monotonic() 算,不是每帧累加 16 ms—— 主线程卡一下,下一帧直接跳到正确进度,整段动画的时长不会被拖长。
  • 每帧全量重绘在这个尺寸下完全够用:一个 200×48 的按钮, 中位耗时 skia 0.42 ms、pillow 0.38 ms、cairo 0.41 ms, 默认的 tksvg 也只要 4.66 ms(60 fps 的预算是 16.7 ms)。
  • 控制器不碰 canvas item、也不存颜色:paint() 每次现取颜色与圆角, 所以同一份状态能跟着主题换色,不需要重建对象。

顺手记下三个坑

写这个效果时踩到的、文档里原来没写清楚的:

  1. class MyButton(DCanvas, DDrawWidget) 会直接 MRO 报错—— DDrawWidget 已经继承 DCanvas,只写后者就行;
  2. draw_roundrect(..., tags=...) 在SVG 回退路径上会抛 Invalid attribute 'tags' for svg-element <rect>,打标签要用 addtag_withtag(返回的 item id);
  3. 先 pack() 再 mainloop() 时,<Configure> 往往在映射之前就发完了, 而 DDrawWidget._draw() 在未映射时直接返回——控件会一直空白, 直到鼠标划过。补一个 <Map> 绑定即可(示例里已经这么做)。

另外还发现:PrintWindow 抓不到 canvas 上的半透明叠加层(窗口里看得见、 截图里没有),所以文档插图改成"引擎渲染的底色 + ripple_layer() 逐帧合成"。 这一条已经写进 常见问题与排查 第 13 条。

文档

新增 涟漪效果:给任意控件加一层扩散动画, 先讲怎么接(三步、控制器与纯函数的 API),再按"为什么不用 create_oval → 遮罩怎么算 → 浓度与半径两条曲线 → 计时 → 图片保活 → 三个坑 → 性能"拆开讲。 插图 ripple-frames.png 由 docs/gen_figures.py --write --only ripple-frames 生成, 可重新跑。