Flutter 中,SingleChildScrollView(类比Android中的ScrollView) 是一个可以滚动单个子控件的小部件。当子控件的大小超过视图时,用户可以滚动以查看所有内容。SingleChildScrollView 通常用于创建可滚动的表单、列表或任何需要垂直或水平滚动的内容。类似于Android中的ScrollView,不过它只能接收一个子组件。
SingleChildScrollView构造方法
const SingleChildScrollView({
super.key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.padding,
this.primary,
this.physics,
this.controller,
this.child,
this.dragStartBehavior = DragStartBehavior.start,
this.clipBehavior = Clip.hardEdge,
this.restorationId,
this.keyboardDismissBehavior = ScrollViewKeyboardDismissBehavior.manual,
})
注意primary属性:
/// {@macro flutter.widgets.scroll_view.primary}
final bool? primary;
primary属性如果为true,表示是否使用 widget 树中默认的PrimaryScrollController。
//如果primary为null。(controller为null && )
final bool effectivePrimary = primary
?? controller == null && PrimaryScrollController.shouldInherit(context, scrollDirection);
final ScrollController? scrollController = effectivePrimary
? PrimaryScrollController.maybeOf(context)
: controller;
SingleChildScrollView中内容不应该超过屏幕太多,否则会影响性能,因为SingleChildScrollView不支持基于 Sliver 的延迟加载模型,所以内容超出屏幕尺寸太多时,使用SingleChildScrollView将会非常昂贵(性能差),应该使用一些支持Sliver延迟加载的可滚动组件,如ListView。
基础用法
通过SingleChildScrollView 包裹一个可能超出视图尺寸的子控件
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
/// 滑动到指定位置 GlobalKey 版本
/// 基于 SingleChildScrollView 和 Column
class ScrollToIndexDemoPage2 extends StatefulWidget {
const ScrollToIndexDemoPage2({super.key});
@override
_ScrollToIndexDemoPageState2 createState() => _ScrollToIndexDemoPageState2();
}
class _ScrollToIndexDemoPageState2 extends State<ScrollToIndexDemoPage2> {
//测试数据
String realStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ScrollToIndexDemoPage2"),
),
body: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Center(
child: Column(
children: realStr
.split("")
.map((c) => Text(
c,
textScaleFactor: 2.0,
))
.toList(),
),
),
),
);
}
}
通过 GlobalKey实现滑动到指定位置(SingleChildScrollView + Column)
GlobalKey的使用
GlobalKey:一个特殊的键,在整个应用范围内唯一的键,与构建的Widget树中的一个特定Element关联。
GlobalKey的作用:
1.操作或获取某个状态类(如StatefulWidget)内部状态
如果需要直接操作或获取某个状态类(如StatefulWidget)内部状态时,可以为(State)创建并分配一个GlobalKey。通过调用GlobalKey.currentState来获取关联的State对象,然后执行方法或改变状态。
2.局部刷新
如果要在不重建整个树的情况下更新单个Widget,当Widget的状态变化时,使用GlobalKey获取State,然后调用State.setState()来触发其自身的重建。
3.查找Widget
通过GlobalKey可以在Widget树中找到特定的Widget或者Element。
4.维持Widget的持久性
Flutter的热重载机制,拥有GlobalKey的Widget在重新加载时不会被销毁,而是保持原有的状态。
import 'dart:math' as math;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class ScrollToIndexDemoPage2 extends StatefulWidget {
const ScrollToIndexDemoPage2({super.key});
//GlobalKey<_ScrollToIndexDemoPageState2> testKey = GlobalKey<_ScrollToIndexDemoPageState2>();
@override
_ScrollToIndexDemoPageState2 createState() => _ScrollToIndexDemoPageState2();
}
class _ScrollToIndexDemoPageState2 extends State<ScrollToIndexDemoPage2> {
GlobalKey scrollKey = GlobalKey();
ScrollController controller = ScrollController();
List<ItemModel> dataList = [];
//测试数据
//String realStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
@override
void initState() {
dataList.clear();
for (int i = 0; i < 100; i++) {
dataList.add(ItemModel(i));
}
super.initState();
}
_scrollToIndex() {
var key = dataList[12];
///获取 renderBox
RenderBox renderBox =
key.globalKey.currentContext!.findRenderObject() as RenderBox;
///获取位置偏移,基于 ancestor: SingleChildScrollView 的 RenderObject()
double dy = renderBox
.localToGlobal(Offset.zero,
ancestor: scrollKey.currentContext!.findRenderObject())
.dy;
///计算真实位移
var offset = dy + controller.offset;
if (kDebugMode) {
//print("*******$offset");
}
controller.animateTo(offset,
duration: const Duration(milliseconds: 500), curve: Curves.linear);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("ScrollToIndexDemoPage2"),
),
// body: SingleChildScrollView(
// padding: EdgeInsets.all(16.0),
// child: Center(
// child: Column(
// children: realStr
// .split("")
// .map((c) => Text(
// c,
// textScaleFactor: 2.0,
// ))
// .toList(),
// ),
// ),
// ),
body: SingleChildScrollView(
key: scrollKey,
scrollDirection: Axis.vertical,
controller: controller,
child: Column(
children: dataList.map<Widget>((data) {
return CardItem(data, key: dataList[data.index].globalKey);
}).toList(),
),
),
persistentFooterButtons: <Widget>[
TextButton(
onPressed: () async {
_scrollToIndex();
},
child: const Text("Scroll to 12"),
),
],
);
}
}
// class _CustomWidgetState extends State<> {
//
// }
class CardItem extends StatelessWidget {
final random = math.Random();
final ItemModel data;
CardItem(this.data, {super.key});
@override
Widget build(BuildContext context) {
return Card(
child: Container(
height: (300 * random.nextDouble()),
alignment: Alignment.centerLeft,
child: Container(
margin: const EdgeInsets.all(5),
child: Text("Item ${data.index}"),
),
),
);
}
}
class ItemModel {
///这个key是关键
GlobalKey globalKey = GlobalKey();
///可以添加你的代码
final int index;
ItemModel(this.index);
}