Gradio Blocks 布局控制完全指南:Row、Column、Tab 与可见性动态布局实战
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
Gradio 的BlocksAPI 默认把所有组件按垂直方向堆叠,但在构建真实机器学习演示(表单、多任务工具、向导式流程)时,几乎都需要自定义排列方式。本指南以仓库中 控制布局指南(中文) 为主线,系统讲解gr.Row、gr.Column、gr.Tab、gr.Accordion、可见性切换与组件延迟渲染等全部布局手段,并结合 gradio/layouts 下的源码实现与 demo 目录中的可运行示例,帮助读者彻底掌握 Blocks 界面的布局控制能力,能独立编排从"简单并排按钮"到"可变数量输出""分步向导"的任意界面。
提示:本仓库中同一篇指南还有英文完整版本 guides/03_building-with-blocks/02_controlling-layout.md,含部分较新的布局特性(如浏览器全高全宽、Walkthrough 分步向导),正文中已合并说明。
布局的底层模型:flexbox
在gr.Blocks()中,组件默认按垂直方向依次排列。若要重新排列组件,核心思路是把界面切分为"行(Row)"与"列(Column)"两类容器,并将组件放入其中。
这一套布局在浏览器底层使用的是 Web 开发标准中的flexbox 弹性盒模型。因此,scale、min_width这些概念本质上都映射为 flexbox 的伸缩与换行规则——理解 flexbox 有助于预测不同屏幕宽度下的表现。本文所有布局类(Row、Column、Tabs、Accordion、Sidebar 等)均继承自BlockContext,集中定义在 gradio/layouts 目录中,可直接阅读源码核对每个参数。
Row:让组件水平并排
基础用法
将组件放进with gr.Row:代码块内,它们就会横向排列。例如并排显示两个按钮:
import gradio as gr with gr.Blocks() as demo: with gr.Row(): btn1 = gr.Button("按钮1") btn2 = gr.Button("按钮2")等高排列:equal_height
默认情况下,同一行内的组件各自按内容决定高度。若希望行内所有元素高度一致,可在创建 Row 时传入equal_height=True:
import gradio as gr with gr.Blocks() as demo: with gr.Row(equal_height=True): textbox = gr.Textbox() btn2 = gr.Button("按钮2")从源码看,equal_height是 gradio/layouts/row.py 中Row构造函数的显式参数(默认False),它会被直接存入实例供前端样式层消费。
控制行内宽度:scale 与 min_width
每一个组件(以及布局元素)都内置了scale与min_width两个参数,用于控制它在行内占据的宽度:
scale(整数):决定元素在行内的相对伸展权重。scale=0:元素不会扩展去抢占多余空间(保持"内容宽度")。scale=1及以上:元素会扩展;同一行内多个可扩展元素按 scale 数值等比例分配剩余宽度。- 例如下方
btn2的伸展量是btn1的两倍,而btn0完全不伸展:
import gradio as gr with gr.Blocks() as demo: with gr.Row(): btn0 = gr.Button("按钮0", scale=0) btn1 = gr.Button("按钮1", scale=1) btn2 = gr.Button("按钮2", scale=2)min_width(整数,单位像素):设置元素的最小宽度。当屏幕空间不足以同时满足所有元素的min_width时,Row 会自动换行(这是 flexboxflex-wrap的典型行为),因此它能保证窄屏下的可用性。
值得一提的细节:在 gradio/layouts/column.py 和 gradio/layouts/row.py 的构造函数中,源码会对scale做非整数校验并给出warnings.warn("'scale' value should be an integer..."),说明该参数虽然类型标注为int,但 API 层面允许浮点传入并建议使用整数,避免布局出现非预期比例。
Column 与嵌套:构造真正的应用布局
gr.Column会把其中的组件自上而下垂直排列。由于垂直堆叠本身就是 Blocks 的默认排布方式,Column 通常嵌套在 Row 内部才真正有用——由此形成"行内分列"的经典布局结构。
仓库中 demo/rows_and_columns/run.py 提供了一个完整范例:
import gradio as gr with gr.Blocks() as demo: with gr.Row(): text1 = gr.Textbox(label="t1") slider2 = gr.Textbox(label="s2") drop3 = gr.Dropdown(["a", "b", "c"], label="d3") with gr.Row(): with gr.Column(scale=1, min_width=300): text1 = gr.Textbox(label="prompt 1") text2 = gr.Textbox(label="prompt 2") inbtw = gr.Button("Between") text4 = gr.Textbox(label="prompt 1") text5 = gr.Textbox(label="prompt 2") with gr.Column(scale=2, min_width=300): img1 = gr.Image("images/cheetah.jpg") # 请替换为本地存在的一张图片路径 btn = gr.Button("Go") if __name__ == "__main__": demo.launch()从结构上观察(如需运行,请将上述gr.Image(...)中的路径替换为你机器上真实存在的图片文件,否则启动后该组件会因找不到资源而报错):
- 第一列垂直排列了两个文本框;第二列垂直排列了图片与按钮。
- 两列的相对宽度完全由
scale决定:左侧scale=1、右侧scale=2,因此右侧列占据两倍宽度。 - 每列都设置了
min_width=300,当窗口收窄到无法同时容纳两列的最小宽度时,第二列会折行到下一行显示。
在 gradio/layouts/column.py 中可以看到Column的默认值:scale: int = 1、min_width: int = 320,源码注释明确了优先级规则——若某个scale计算出的列宽小于min_width,则min_width优先生效。此外Column与Row都支持variant参数("default"无背景、"panel"灰背景圆角、"compact"圆角且去掉内部间隙),便于快速获得卡片化视觉。
填充浏览器全高全宽
官方英文版指南还补充了两个页面级布局开关,用于消除默认的留白:
import gradio as gr # 去掉左右内边距,让应用占满浏览器宽度 with gr.Blocks(fill_width=True) as demo: gr.Chatbot()import gradio as gr # 顶层组件占满浏览器高度;配合 scale 让 Chatbot 吃掉全部剩余高度 with gr.Blocks(fill_height=True) as demo: gr.Chatbot(scale=1) gr.Textbox(scale=0)上面第二个例子中,gr.Chatbot(scale=1)会把可扩展高度全部占满,而gr.Textbox(scale=0)保持自身固有高度(固定在底部)。这是制作"终端式"聊天界面的常用手法。
自定义尺寸:像素或任意 CSS 单位
部分组件与布局元素支持直接设置height与width。这两个参数既接受数字(按像素解释),也接受字符串(此时字符串会被直接当作 CSS 单位作用到外层元素上)。这意味着你可以使用px、%、vw、vh、rem等任意合法 CSS 长度单位。
例如按"视口宽度"(viewport width)设定图片编辑器的宽度,使其始终占据浏览器一半宽度:
import gradio as gr with gr.Blocks() as demo: im = gr.ImageEditor(width="50vw") demo.launch()同样的规则适用于Row的height、max_height、min_height参数(见 gradio/layouts/row.py:传数字按像素解析、传字符串按 CSS 单位解析,内容超出时会触发垂直滚动)。这一能力为"自适应宽高"场景提供了比固定像素更灵活的选项。
Tab 选项卡与 Accordion 手风琴
用 gr.Tab 组织互斥内容页
使用with gr.Tab("标签名"):即可创建选项卡。凡是写在该上下文内的组件都会归入这个页签;连续的 Tab 子句会被分组成一组,同一时刻只能选中一个页签、只显示对应上下文中的组件。
demo/blocks_flipper/run.py 给出了一个"翻转文本 / 翻转图像"的双页签示例:
import numpy as np import gradio as gr def flip_text(x): return x[::-1] def flip_image(x): return np.fliplr(x) with gr.Blocks() as demo: gr.Markdown("Flip text or image files using this demo.") with gr.Tab("Flip Text"): text_input = gr.Textbox() text_output = gr.Textbox() text_button = gr.Button("Flip") with gr.Tab("Flip Image"): with gr.Row(): image_input = gr.Image() image_output = gr.Image() image_button = gr.Button("Flip") with gr.Accordion("Open for More!", open=False): gr.Markdown("Look at me...") temp_slider = gr.Slider( 0, 1, value=0.1, step=0.1, interactive=True, label="Slide me", ) text_button.click(flip_text, inputs=text_input, outputs=text_output) image_button.click(flip_image, inputs=image_input, outputs=image_output) if __name__ == "__main__": demo.launch()从源码 gradio/layouts/tabs.py 可进一步挖掘出不少实用参数:
gr.Tabs(selected=...):程序化指定默认选中的页签(需配合子页签的id)。gr.Tab(label, id=...):id用于在事件函数中通过返回gr.Tabs(selected=id)实现"点击按钮跳转页签"。gr.Tab(interactive=False):使该页签不可点击。gr.Tab(render_children=True):页签未激活时也预先渲染(并隐藏)子组件,便于视频、音频等重资源提前加载。Tab拥有select事件;其文档字符串中的EVENTS定义可见,事件数据会携带被点击页签的label与selected状态。- 值得注意,
Tabs.__exit__中实现了严格的子级校验:gr.Tabs()的直接子级只能是gr.Tab()(别名gr.TabItem),若误将普通组件直接放进Tabs会触发UserWarning,提示开发者把内容包进gr.Tab(...)。
用 Accordion 折叠/展开附加内容
gr.Accordion('标签')是一种可开可合的布局元素,作用类似"手风琴"。定义在with gr.Accordion('label'):内部的任何组件,会在用户点击切换图标时统一隐藏或显示。上面的示例在页签组下方放了一个默认折叠的gr.Accordion("Open for More!", open=False),用于收纳可选设置项。
gradio/layouts/accordion.py 源码显示其核心参数为:
label:手风琴的标题。open:是否默认展开(默认True;上面的示例传False让高级选项默认收起)。- 事件上支持
expand/collapse两个事件监听,可感知用户的展开/折叠行为并触发回调。
Sidebar:左侧可折叠面板
在较新版本中,布局家族还加入了gr.Sidebar:一个渲染在屏幕左侧、可展开/折叠的面板,用于把"控制项/输入项"与"主内容区"清晰分隔。典型用法是把下拉框、单选按钮等输入控件放入 Sidebar,把结果输出放在主区域。
仓库中的 demo/blocks_sidebar/run.py 用 Sidebar 实现了一个宠物起名器——左侧面板内放置动物类型、性格等选项,右侧主区域展示生成的名称与按钮。核心结构如下:
with gr.Blocks() as demo: with gr.Sidebar(position="left"): animal_type = gr.Dropdown( choices=["Cat", "Dog", "Bird", "Rabbit"], label="Choose your pet type", value="Cat" ) personality = gr.Radio( choices=["Normal", "Silly", "Royal"], label="Personality type", value="Normal" ) name_output = gr.Textbox(label="Your pet's fancy name:", lines=2) generate_btn = gr.Button("Generate Name! 🎲", variant="primary") generate_btn.click( fn=generate_pet_name, inputs=[animal_type, personality], outputs=name_output )结合 gradio/layouts/sidebar.py 的构造函数,Sidebar支持以下参数:
open:默认是否展开(默认True)。width:侧栏宽度,数字按像素、字符串按 CSS 单位解析(默认320)。position:"left"或"right",决定侧栏位于主区域左侧还是右侧(默认"left")。- 与 Accordion 一样,
Sidebar也暴露expand/collapse事件,便于在面板开合时联动界面。
Walkthrough:分步引导式布局
面向"需要用户按顺序完成多步任务"的应用,布局层还提供了gr.Walkthrough与配套的gr.Step组件,它们提供了一套专门设计的视觉风格与操作体验。其编写方式与Tab类似,区别在于:推进步骤的责任在应用开发者——通过在事件回调中设置父级Walkthrough的选中id(该id须与某个Step的id对应)来切换当前步骤。
demo/walkthrough/run.py 展示了一个"上传图片 → 填写提示词 → 查看结果"的三步引导流程:
import gradio as gr with gr.Blocks() as demo: with gr.Walkthrough(selected=0) as walkthrough: with gr.Step("Image", id=0): image = gr.Image() btn = gr.Button("go to prompt") btn.click(lambda: gr.Walkthrough(selected=1), outputs=walkthrough) with gr.Step("Prompt", id=1): prompt = gr.Textbox() btn = gr.Button("generate") btn.click(lambda: gr.Walkthrough(selected=2), outputs=walkthrough) with gr.Step("Result", id=2): gr.Image(label="result", interactive=False) if __name__ == "__main__": demo.launch()每个"下一步"按钮都通过btn.click(lambda: gr.Walkthrough(selected=N), outputs=walkthrough)把父级Walkthrough组件作为输出、更新其选中步骤,从而实现受控的线性流程。适合做教学向导、多阶段审核、Pipeline 配置等场景。
可见性控制:显示或隐藏组件与整组布局
组件与布局元素都拥有一个visible参数,既可在创建时设定初始状态,也可以在事件回调中通过gr.update(visible=...)动态更新。由于Column本身也是BlockContext,对整列设置可见性即可一次性显示/隐藏一组组件,这是实现"表单提交前隐藏、提交后展示结果区"等交互的最直接手段。
仓库中 demo/blocks_form/run.py 是一个完整的"病历表单"示例:点击 Submit 前,右侧的诊断结果列处于隐藏状态;提交后该列显现、输入按钮列隐藏,并回填诊断内容:
import gradio as gr with gr.Blocks() as demo: name_box = gr.Textbox(label="Name") age_box = gr.Number(label="Age", minimum=0, maximum=100) symptoms_box = gr.CheckboxGroup(["Cough", "Fever", "Runny Nose"]) submit_btn = gr.Button("Submit") with gr.Column(visible=False) as output_col: diagnosis_box = gr.Textbox(label="Diagnosis") patient_summary_box = gr.Textbox(label="Patient Summary") def submit(name, age, symptoms): return { submit_btn: gr.Button(visible=False), output_col: gr.Column(visible=True), diagnosis_box: "covid" if "Cough" in symptoms else "flu", patient_summary_box: f"{name}, {age} y/o", } submit_btn.click( submit, [name_box, age_box, symptoms_box], [submit_btn, diagnosis_box, patient_summary_box, output_col], ) if __name__ == "__main__": demo.launch()关键点解读:
with gr.Column(visible=False) as output_col:把两个结果文本框包进一列并默认隐藏。- 事件函数
submit返回一个字典,其中键可以是组件、布局对象甚至事件源按钮,值可以是新值或gr.update,因此函数可以同时更新隐藏状态与内容——输出列表[...submit_btn, diagnosis_box, patient_summary_box, output_col]中甚至把按钮本身也作为输出,用于把它自身隐藏。 - 布局元素在字典更新里通过
gr.Column(visible=True)(对 Row 同理,见 gradio/layouts/row.py 中Row.update静态方法仅接收visible)完成显示切换。
可变数量输出:用可见性驱动动态界面
把"动态调整可见性"的思路推广,就能实现可变数量输出(Variable Number of Outputs)的演示:界面上输出控件的数量随某个输入实时变化。
demo/variable_outputs/run.py 用一个滑块控制文本框的显示个数:
import gradio as gr max_textboxes = 10 def variable_outputs(k): k = int(k) return [gr.Textbox(visible=True)]*k + [gr.Textbox(visible=False)]*(max_textboxes-k) with gr.Blocks() as demo: s = gr.Slider(1, max_textboxes, value=max_textboxes, step=1, label="How many textboxes to show:") textboxes = [] for i in range(max_textboxes): t = gr.Textbox(f"Textbox {i}") textboxes.append(t) s.change(variable_outputs, s, textboxes) if __name__ == "__main__": demo.launch()实现原理是:一次性定义全部(这里为 10 个)文本框,把它们的引用放进textboxes列表并整体作为事件输出;当滑块值变为k时,回调返回k个visible=True的更新与10-k个visible=False的更新,前端据此重新布局,从而在结构固定的前提下实现"数量可变"的假象——这种模式对"动态添加/移除输入行"类需求非常实用。
分开定义与渲染组件:render 与 unrender
场景:gr.Examples 放在输入框上方
gr.Blocks上下文内定义的组件会立即渲染到 DOM。但有些场景需要先拿到组件对象、后决定它的渲染位置。典型例子是把gr.Examples(示例区)显示在对应的输入框上方:由于gr.Examples构造时需要传入输入组件对象作为参数,就必须先创建输入组件对象、再创建Examples、最后才真正渲染输入框。
解决办法:在gr.Blocks()作用域之外定义组件(此时组件被创建但不会被自动渲染),然后在 UI 中期望它出现的位置调用该组件的.render()方法:
import gradio as gr input_textbox = gr.Textbox() # 先在 Blocks 之外创建,暂不渲染 with gr.Blocks() as demo: gr.Examples(["hello", "bonjour", "merhaba"], input_textbox) input_textbox.render() # 之后在示例区下方渲染输入框运行后,界面会先展示三个示例例句,示例区下方才是真正的文本输入框——这正是"分开定义与渲染"的价值。
逆向操作:unrender 后在其他位置重新渲染
若一个组件已被渲染,但你希望把它挪到应用的另一处,可先调用.unrender()将其从原位置卸载,再调用.render()在新位置重新挂载。例如下面这段代码,textbox原本定义在第一列,但先在第二列被unrender()摘除,最终在第三列才真正出现:
import gradio as gr with gr.Blocks() as demo: with gr.Row(): with gr.Column(): gr.Markdown("Row 1") textbox = gr.Textbox() with gr.Column(): gr.Markdown("Row 2") textbox.unrender() with gr.Column(): gr.Markdown("Row 3") textbox.render() demo.launch()render/unrender这种"延迟渲染 + 移动挂载"机制,与gr.render装饰器(见指南 04_dynamic-apps-with-render-decorator.md)配合,是构建动态、可重排 UI 的重要底层能力。
小结与进阶路线
围绕 Blocks 布局控制,可以提炼出如下决策脉络:
- 基础排布:默认垂直;
gr.Row改横向,equal_height对齐高度,scale/min_width控制伸缩与换行; - 结构化分栏:
Row内嵌多个gr.Column,用scale控制列宽比例、min_width保证窄屏可用(默认 320px); - 页面级自适应:
gr.Blocks(fill_width=True / fill_height=True)撑满浏览器,组件级width/height支持任意 CSS 单位; - 内容分区:
gr.Tab做互斥页签、gr.Accordion做可折叠分组、gr.Sidebar做左右分栏控制面板、gr.Walkthrough+gr.Step做分步引导; - 动态交互:所有组件/布局的
visible参数可用gr.update在回调中切换,由此实现整组显示隐藏、可变数量输出; - 灵活渲染:Blocks 作用域外先定义、用
.render()/.unrender()控制组件挂载位置与时机。
想深入了解相关实现,可在仓库中继续阅读:
- 布局源码实现:gradio/layouts/row.py、gradio/layouts/column.py、gradio/layouts/tabs.py、gradio/layouts/accordion.py、gradio/layouts/sidebar.py;
- 可运行示例:demo/rows_and_columns/run.py、demo/blocks_flipper/run.py、demo/blocks_form/run.py、demo/variable_outputs/run.py、demo/blocks_sidebar/run.py、demo/walkthrough/run.py;
- 配套学习指南:事件监听器与 Blocks 基础(中文)、Blocks 中的状态管理(中文),以及上文反复引用的英文完整版 02_controlling-layout.md。
结合这些源码与示例动手修改scale、min_width、visible等参数并实时预览,即可快速建立对 Gradio 布局模型的直观手感。
【免费下载链接】gradioBuild and share delightful machine learning apps, all in Python. 🌟 Star to support our work!项目地址: https://gitcode.com/GitHub_Trending/gr/gradio
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考