第9章 Gradio 布局:行、列、标签页与 Blocks
界面能运行,不代表就是好界面。
想象一下,打开一个应用,看到二十个组件竖着堆成一列。
一切功能正常,技术上也没任何问题,但想找到需要的东西却让人身心俱疲。
好的界面设计会把相关的控件组织在一起,并把应用的不同部分区分开来。
Gradio 的布局系统正是为此而生。
为什么布局很重要
以一个文档分析器为例。
它可能包含:
文件上传器,
文本预览,
分析设置,
按钮,
摘要,
表格,
以及聊天区域。
把所有组件塞进一根长长的竖列并不理想。更好的做法是把应用划分成几个区块来组织。
Gradio 的 Blocks API 正是构建这类界面的基础。
从 Blocks 开始
一个基础应用长这样:
import gradio as gr
with gr.Blocks() as demo:
gr.Markdown("# My Application")
demo.launch()
Blocks 上下文内的所有内容都属于这个应用。
行(Rows)
Row 会让组件水平排列。
例如:
with gr.Blocks() as demo:
with gr.Row():
first = gr.Textbox(label="First")
second = gr.Textbox(label="Second")
demo.launch()
在布局空间允许的情况下,两个文本框会并排显示。
Row 特别适合放置相互关联的控件。
示例:两数计算器
import gradio as gr
def add(a, b):
return a + b
with gr.Blocks() as demo:
gr.Markdown("# Calculator")
with gr.Row():
a = gr.Number(label="First Number")
b = gr.Number(label="Second Number")
button = gr.Button("Add")
result = gr.Number(label="Result")
button.click(
fn=add,
inputs=[a, b],
outputs=result
)
demo.launch()
两个输入框在逻辑上相关,放在一行里就很合理。
列(Columns)
Column 会让组件垂直堆叠。
with gr.Column():
name = gr.Textbox()
age = gr.Number()
button = gr.Button()
Blocks 应用默认就是纵向排列的,但在嵌套布局时,显式使用 Column 会特别有用。
组合 Row 和 Column
布局设计在此时真正体现出威力。一个 Row 里可以包含两个 Column。
例如:
with gr.Row():
with gr.Column():
input_text = gr.Textbox()
button = gr.Button("Analyze")
with gr.Column():
output = gr.Textbox()
这就形成了一个常见的应用模式:
一侧放控件
另一侧显示结果
构建双面板界面
我们来创建一个简单的文本分析器。
import gradio as gr
def analyze(text):
return (
f"Characters: {len(text)}\n"
f"Words: {len(text.split())}"
)
with gr.Blocks() as demo:
gr.Markdown("# Text Analyzer")
with gr.Row():
with gr.Column():
text = gr.Textbox(
label="Input Text",
lines=12
)
button = gr.Button("Analyze")
with gr.Column():
result = gr.Textbox(
label="Analysis",
lines=12
)
button.click(
fn=analyze,
inputs=text,
outputs=result
)
demo.launch()
到这里,已经不像示例拼凑,而像一个真正的应用了。
缩放与布局比例
可以通过配置控制 Row 和 Column 的相对大小。
例如:
with gr.Row():
with gr.Column(scale=2):
input_text = gr.Textbox()
with gr.Column(scale=1):
output = gr.Textbox()
第一列获得的空间比第二列更大。当应用某侧需要明显更多空间时,这种配置非常实用。
比如,大段文档输入区往往需要比小型设置面板更多的空间。
Tabs
当应用包含多个相关联的工作流时,Tabs 就派得上用场了。
例如:
with gr.Blocks() as demo:
with gr.Tab("Text Analyzer"):
...
with gr.Tab("Image Analyzer"):
...
demo.launch()
用户可以在两个工具之间切换,而不必同时看到所有控件。
什么时候该用 Tabs?
Tabs 适用于以下场景:
工作流之间有关联
用户不需要同时使用两个工作流
每个工作流包含多个控件
否则界面会变得杂乱
不要为了用 Tabs 而用。如果一个应用只有两小块内容,Tabs 反而会增加不必要的操作成本。
示例:多工具应用
想象一个 AI 效率工具,包含:
摘要生成器
翻译器
文本分析器
你可以这样搭建:
with gr.Blocks() as demo:
gr.Markdown("# AI Productivity Tools")
with gr.Tab("Summarizer"):
...
with gr.Tab("Translator"):
...
with gr.Tab("Text Analyzer"):
...
demo.launch()
每个 Tab 就是一个独立的工作流。
分组
分组可以将相关组件组织在一起,而不必单独开一个 Tab。比如把几项设置放在一起。
具体的视觉表现取决于当前 Gradio 版本和主题,但核心思路很简单:把相关的控件放在一起。
折叠面板
当有可选或高级设置时,折叠面板(Accordion)很有用。
想象一个 AI 应用,包含:
提示词
模型
温度参数
最大 token 数
高级采样设置
系统指令
大多数用户可能只关心提示词。
你可以把高级控件放进一个折叠面板里。
概念上:
with gr.Accordion("Advanced Settings"):
temperature = gr.Slider(...)
max_tokens = gr.Slider(...)
这样既保持了主界面的简洁,又给高级用户留出了操作空间。
可见性
有时候,你希望组件只在需要时才显示。
比如,一个应用最初可能显示:
Choose input type
用户选了"Image",就显示图片上传组件;选了"Text",则改为显示文本框。
Gradio 支持通过事件动态修改组件属性,这是构建更简洁界面的一个强大技巧。
条件式界面
假设我们有:
input_type = gr.Radio(
choices=["Text", "Image"],
label="Input Type"
)
我们可以在用户改变选择时,显示对应的组件。
具体的更新语法要与你使用的 Gradio 版本相匹配,但设计模式是这样的:
User chooses mode
↓
Event fires
↓
Interface updates
↓
Relevant component becomes available
对于支持多种输入模式的应用来说,这非常实用。
用 Markdown 做设计元素
别小看 Markdown。你可以用它来构建层次结构:
gr.Markdown("# AI Assistant")
gr.Markdown("## Upload a document")
gr.Markdown("Choose a file to begin.")
一段写得好的说明文字,能让技术界面好用很多。
区分输入与输出区域
一个常用的设计模式是:
gr.Markdown("## Input")
...
gr.Markdown("## Results")
...
例如:
with gr.Blocks() as demo:
gr.Markdown("# Document Analyzer")
gr.Markdown("## Upload a document")
file = gr.File()
gr.Markdown("## Analysis")
result = gr.Textbox(lines=10)
这样就构建出了视觉上的层次结构,完全不需要写自定义前端代码。
一个完整的布局示例
接下来,我们把前面讲过的几种布局概念组合起来。
import gradio as gr
def analyze(text):
words = len(text.split())
characters = len(text)
return words, characters
with gr.Blocks() as demo:
gr.Markdown(
"# Text Analyzer\n"
"Analyze the text you provide."
)
with gr.Row():
with gr.Column(scale=2):
gr.Markdown("### Input")
text = gr.Textbox(
label="Text",
lines=12
)
analyze_button = gr.Button(
"Analyze",
variant="primary"
)
with gr.Column(scale=1):
gr.Markdown("### Results")
words = gr.Number(
label="Words"
)
characters = gr.Number(
label="Characters"
)
analyze_button.click(
fn=analyze,
inputs=text,
outputs=[words, characters]
)
demo.launch()
这是一个布局与功能协同工作的典型示例。
响应式设计
用户可能在不同尺寸的屏幕上使用你的应用。在大显示器上看起来完美的布局,到了小屏上可能就会显得拥挤不堪。
不要假设每个用户都有大显示器。
行和列的使用要讲究分寸。如果两个组件本身就很宽,并排放置在小屏上会很难操作。
不要过度设计界面
人们总想把所有布局功能都用上。
你可能会做出这样的界面:
五个 Tabs
三个手风琴面板
嵌套的行
嵌套的列
多个分组
几十条 Markdown 标题
这样会让界面变得更难理解。
从最简单的布局开始,只要能把操作流程表达清楚就够了。
围绕用户的任务来设计
一个有用的思考起点是:
用户首先要做什么?
把那个操作放在最显眼的位置。
然后问:
他们需要提供哪些信息?
把这些输入框放在一起。
接着:
操作完成后他们应该看到什么?
把结果放在一个显而易见的位置。这样就形成了自然的流程。
示例:文档分析器布局
一个合理的文档分析器可以这样组织:
with gr.Blocks() as demo:
gr.Markdown("# Document Analyzer")
with gr.Row():
with gr.Column():
file = gr.File(label="Upload Document")
analyze_button = gr.Button("Analyze")
with gr.Column():
summary = gr.Textbox(
label="Summary",
lines=10
)
用户一看就懂:上传文件、点击分析、查看结果。
Tabs 还是独立应用
如果两个工具彼此无关,用 Tabs 未必是最佳选择。
比如,把房贷计算器和图像分类器塞进同一个应用,体验不一定更好。
Tabs 最适合用于同一产品内的不同工作流。
布局本身就是功能
这里有个关键认知:布局不只是装饰。
假设某个 AI 应用把"生成"按钮藏在二十个不相关的控件下面。
功能上没问题,但界面让人用起来更费劲。好的布局能降低认知负荷。
动手试试
挑一个你之前写过的应用,重新设计布局。
用到以下元素:
一个标题
一段简短描述
至少一行 Row
至少两列 Column
一个输入区域
一个输出区域
一个高级设置 Accordion
不要为了凑清单而加布局元素,想想每个元素为什么应该放在那里。
要点总结
Blocks让你掌控 Gradio 应用的结构。Rows 让组件水平排列。
Columns 让组件垂直排列,并能控制相对空间占比。
Tabs 用于划分相关工作流。
Accordions 适合放可选或高级设置。
Markdown 可以建立视觉和信息层次。
好的布局让应用更易理解、更易使用。
响应式设计很重要,因为用户的屏幕尺寸各不相同。
能清晰支持用户任务的最简单界面,往往就是最好的界面。