滚动 API
ScrollView、ScrollViewReader、滚动方向和定位锚点。
ScrollView / ScrollViewReader。
#ScrollView
签名
已复制
ScrollView(content=None, axes='vertical', shows_indicators=True, showsIndicators=None)
参数
| 参数 | 类型 | 说明 |
|---|---|---|
content | list[View] | View | None | 可滚动区域;支持 with ScrollView(): 收集子视图。 |
axes | str | vertical、horizontal 或 both。 |
shows_indicators | bool | 是否显示滚动指示条。 |
示例
已复制
import appui
def body():
with appui.ScrollView(axes="vertical") as sc:
for i in range(25):
appui.Text(f"段落 {i}").padding(horizontal=4)
return sc.padding()
appui.run(body, presentation="sheet")
参阅:ScrollViewReader、LazyVStack
#ScrollViewReader
签名
已复制
ScrollViewReader(content=None, axes='vertical', shows_indicators=True,
scroll_to=None, anchor='top', showsIndicators=None, scrollTo=None,
children=None)
参数
| 参数 | 类型 | 说明 |
|---|---|---|
content / children | list[View] | None | 滚动内容。 |
axes | str | 同 ScrollView。 |
shows_indicators | bool | 是否显示指示器。 |
scroll_to / scrollTo | 任意 | None | 初始或受控滚动目标,需与子视图 .id(...) 对应。 |
anchor | str | 滚动对齐锚点,如 top。 |
示例
已复制
import appui
def body():
return appui.ScrollViewReader(
content=[
appui.Text("顶部").id("top"),
appui.Spacer(min_length=400),
appui.Text("底部锚点").id("bottom"),
],
axes="vertical",
scroll_to="bottom",
anchor="top",
).padding()
appui.run(body, presentation="sheet")
参阅:ScrollView、Spacer
#双向位置与原生滚动观察
| API | 系统版本 | 回调数据 |
|---|---|---|
.scroll_position(id=...) | iOS 17+ | 可传 State.bind.<field>,用户滚动与程序改值双向同步稳定 id。 |
.scroll_target_layout() | iOS 17+ | 把子视图标记为对齐或分页目标。 |
.scroll_target_behavior("view_aligned" | "paging") | iOS 17+ | 使用系统吸附与分页行为。 |
.on_scroll_geometry_change(action, minimum_interval=...) | iOS 18+ | 内容偏移、内容尺寸、容器尺寸、insets、bounds 和可见区域。 |
.on_scroll_phase_change(action) | iOS 18+ | 旧/新阶段、是否滚动和速度。 |
.on_scroll_visibility_change(action, threshold=...) | iOS 18+ | 当前子视图是否达到可见阈值。 |
高频几何变化在原生端按 minimum_interval 合并后才发送给 Python。布局、惯性、吸附与逐帧滚动仍由系统完成;回调适合埋点、懒加载和状态展示,不要在其中逐帧重建整个页面。
已复制
import appui
state = appui.State(position="row-0", phase="idle")
def phase_changed(payload):
state.phase = payload.get("new_phase", "unknown")
def visibility_changed(visible):
pass
def row(index):
return (
appui.Text(f"Row {index}")
.id(f"row-{index}")
.frame(max_width=".infinity", min_height=64, alignment="leading")
.on_scroll_visibility_change(
visibility_changed,
threshold=0.5,
)
)
def body():
rows = appui.LazyVStack([row(index) for index in range(30)])
return appui.NavigationStack(
appui.VStack([
appui.Text(f"Phase: {state.phase}"),
appui.ScrollView(rows.scroll_target_layout())
.scroll_position(state.bind.position)
.scroll_target_behavior("view_aligned")
.on_scroll_phase_change(phase_changed),
])
.navigation_title("Scroll")
)
appui.run(body, state=state)