Flutter扩展滚动功能插件flutter_extended_scroll的使用
Flutter扩展滚动功能插件flutter_extended_scroll的使用
flutter_extended_scroll package for flutter
make method ensureVisible support offsetTop
Getting Started
在 pubspec.yaml
文件中添加以下依赖:
dependencies:
flutter_extended_scroll: ^3.2.2
Example
详细的例子可以在示例代码中查看。
在你的 Dart 文件中导入库:
import 'package:flutter_extended_scroll/flutter_extended_scroll.dart';
示例代码
class DetailPage extends StatefulWidget {
const DetailPage({super.key});
static const String name = "/detailPage";
@override
State<DetailPage> createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
late final ExtendedScrollController _scrollController;
late final RefreshController _refreshController;
bool isTabClicked = false;
// 商品、评论、详情、同店好货锚点key
final cardKeys = <GlobalKey>[
GlobalKey(debugLabel: 'detail_card_0'),
GlobalKey(debugLabel: 'detail_card_1'),
GlobalKey(debugLabel: 'detail_card_2'),
GlobalKey(debugLabel: 'detail_card_3')
];
@override
void initState() {
_scrollController = ExtendedScrollController();
_refreshController = RefreshController();
super.initState();
}
@override
void dispose() {
_scrollController.dispose();
_refreshController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StoreBuilder<AppState>(
onInit: (store) async {
await store.dispatch(InitPageAction());
},
onDispose: (store) {
store.dispatch(ChangeTopTabIndexAction(0));
},
builder: (context, store) {
bool isLoading = store.state.detailPageState.isLoading;
List<Widget> stackWidgets = [];
if (isLoading) {
stackWidgets.add(loadingWidget(context));
} else {
stackWidgets.add(NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
double distance = notification.metrics.pixels;
store.dispatch(ChangePageScrollYAction(distance));
if (isTabClicked) return false;
int newIndex = findFirstVisibleItemIndex(cardKeys, context);
store.dispatch(ChangeTopTabIndexAction(newIndex));
return false;
},
child: Container(
child: SmartRefresher(
controller: _refreshController,
enablePullUp: true,
enablePullDown: false,
onLoading: () async {
store.dispatch(LoadMoreAction(
store.state.detailPageState.pageNum + 1,
() => loadMoreSuccess(_refreshController),
() => loadMoreFail(_refreshController),
));
},
child: ExtendedCustomScrollView(
controller: _scrollController,
slivers: [
goodsInfo(context, cardKeys[0]),
appraiseInfo(context, cardKeys[1]),
detailCard(context, cardKeys[2]),
storeGoodsHeader(context, cardKeys[3]),
storeGoods(context)
],
),
),
),
));
}
stackWidgets.add(
Positioned(
top: 0,
left: 0,
child: tabHeader(
context,
onChange: (index) {
isTabClicked = true;
store.dispatch(ChangeTopTabIndexAction(index));
scroll2PositionByTabIndex(index);
},
),
),
);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark,
child: Column(
children: [
Expanded(
flex: 1,
child: Scaffold(
body: Stack(
children: stackWidgets,
),
floatingActionButton: BackToTop(_scrollController),
),
),
fixedBottom(context)
],
),
);
},
);
}
void scroll2PositionByTabIndex(int index) {
RenderSliverToBoxAdapter? keyRenderObject = cardKeys[index].currentContext?.findAncestorRenderObjectOfType<RenderSliverToBoxAdapter>();
if (keyRenderObject != null) {
_scrollController.position
.ensureVisible(keyRenderObject, offsetTop: 42 + getStatusHeight(context), duration: const Duration(milliseconds: 300), curve: Curves.linear)
.then((value) => isTabClicked = false);
}
}
int findFirstVisibleItemIndex(List<GlobalKey<State<StatefulWidget>>> cardKeys, BuildContext context) {
int i = 0;
for (; i < cardKeys.length; i++) {
RenderSliverToBoxAdapter? keyRenderObject = cardKeys[i].currentContext?.findAncestorRenderObjectOfType<RenderSliverToBoxAdapter>();
if (keyRenderObject != null) {
final dy = (keyRenderObject.parentData as SliverPhysicalParentData).paintOffset.dy;
if (dy > 42 + getStatusHeight(context)) {
break;
}
}
}
final newIndex = i == 0 ? 0 : i - 1;
return newIndex;
}
}
示例代码解释
初始化和释放资源
在 initState
方法中初始化 _scrollController
和 _refreshController
。在 dispose
方法中释放这些控制器。
@override
void initState() {
_scrollController = ExtendedScrollController();
_refreshController = RefreshController();
super.initState();
}
@override
void dispose() {
_scrollController.dispose();
_refreshController.dispose();
super.dispose();
}
构建UI
构建一个包含 ExtendedCustomScrollView
的 Stack
布局,并添加了一些锚点键来跟踪滚动位置。
@override
Widget build(BuildContext context) {
return StoreBuilder<AppState>(
onInit: (store) async {
await store.dispatch(InitPageAction());
},
onDispose: (store) {
store.dispatch(ChangeTopTabIndexAction(0));
},
builder: (context, store) {
bool isLoading = store.state.detailPageState.isLoading;
List<Widget> stackWidgets = [];
if (isLoading) {
stackWidgets.add(loadingWidget(context));
} else {
stackWidgets.add(NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
double distance = notification.metrics.pixels;
store.dispatch(ChangePageScrollYAction(distance));
if (isTabClicked) return false;
int newIndex = findFirstVisibleItemIndex(cardKeys, context);
store.dispatch(ChangeTopTabIndexAction(newIndex));
return false;
},
child: Container(
child: SmartRefresher(
controller: _refreshController,
enablePullUp: true,
enablePullDown: false,
onLoading: () async {
store.dispatch(LoadMoreAction(
store.state.detailPageState.pageNum + 1,
() => loadMoreSuccess(_refreshController),
() => loadMoreFail(_refreshController),
));
},
child: ExtendedCustomScrollView(
controller: _scrollController,
slivers: [
goodsInfo(context, cardKeys[0]),
appraiseInfo(context, cardKeys[1]),
detailCard(context, cardKeys[2]),
storeGoodsHeader(context, cardKeys[3]),
storeGoods(context)
],
),
),
),
));
}
stackWidgets.add(
Positioned(
top: 0,
left: 0,
child: tabHeader(
context,
onChange: (index) {
isTabClicked = true;
store.dispatch(ChangeTopTabIndexAction(index));
scroll2PositionByTabIndex(index);
},
),
),
);
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle.dark,
child: Column(
children: [
Expanded(
flex: 1,
child: Scaffold(
body: Stack(
children: stackWidgets,
),
floatingActionButton: BackToTop(_scrollController),
),
),
fixedBottom(context)
],
),
);
},
);
}
滚动到指定位置
scroll2PositionByTabIndex
方法用于根据给定的索引滚动到指定位置。
void scroll2PositionByTabIndex(int index) {
RenderSliverToBoxAdapter? keyRenderObject = cardKeys[index].currentContext?.findAncestorRenderObjectOfType<RenderSliverToBoxAdapter>();
if (keyRenderObject != null) {
_scrollController.position
.ensureVisible(keyRenderObject, offsetTop: 42 + getStatusHeight(context), duration: const Duration(milliseconds: 300), curve: Curves.linear)
.then((value) => isTabClicked = false);
}
}
查找第一个可见项的索引
findFirstVisibleItemIndex
方法用于查找第一个可见项的索引。
int findFirstVisibleItemIndex(List<GlobalKey<State<StatefulWidget>>> cardKeys, BuildContext context) {
int i = 0;
for (; i < cardKeys.length; i++) {
RenderSliverToBoxAdapter? keyRenderObject = cardKeys[i].currentContext?.findAncestorRenderObjectOfType<RenderSliverToBoxAdapter>();
if (keyRenderObject != null) {
final dy = (keyRenderObject.parentData as SliverPhysicalParentData).paintOffset.dy;
if (dy > 42 + getStatusHeight(context)) {
break;
}
}
}
final newIndex = i == 0 ? 0 : i - 1;
return newIndex;
}
更多关于Flutter扩展滚动功能插件flutter_extended_scroll的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter扩展滚动功能插件flutter_extended_scroll的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter中,flutter_extended_scroll
是一个用于扩展滚动功能的强大插件。它提供了一些额外的功能,比如滚动监听、自动滚动到指定位置等。以下是一个简单的示例,展示如何使用 flutter_extended_scroll
来实现一个带有滚动监听和自动滚动的页面。
首先,确保你已经在 pubspec.yaml
文件中添加了 flutter_extended_scroll
依赖:
dependencies:
flutter:
sdk: flutter
flutter_extended_scroll: ^x.y.z # 请替换为最新版本号
然后,运行 flutter pub get
来安装依赖。
接下来,是示例代码:
import 'package:flutter/material.dart';
import 'package:flutter_extended_scroll/flutter_extended_scroll.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ScrollExample(),
);
}
}
class ScrollExample extends StatefulWidget {
@override
_ScrollExampleState createState() => _ScrollExampleState();
}
class _ScrollExampleState extends State<ScrollExample> with SingleTickerProviderStateMixin {
late ScrollController _scrollController;
late AnimationController _animationController;
late Animation<Offset> _animation;
@override
void initState() {
super.initState();
_scrollController = ScrollController();
_animationController = AnimationController(
duration: const Duration(seconds: 2),
vsync: this,
);
_animation = Tween<Offset>(begin: Offset.zero, end: Offset.byPixels(0, 500)).animate(_animationController);
_scrollController.addListener(() {
// 监听滚动事件
print('Current scroll position: ${_scrollController.position.pixels}');
});
// 自动滚动到指定位置
Future.delayed(Duration(seconds: 1), () {
_scrollController.animateTo(
500.0, // 目标位置
duration: Duration(seconds: 2),
curve: Curves.easeInOut,
);
});
}
@override
void dispose() {
_scrollController.dispose();
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Extended Scroll Example'),
),
body: ExtendedScrollView(
controller: _scrollController,
child: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 触发动画滚动
_animationController.reset();
_animationController.forward();
_scrollController.animateTo(
_animation.value.dy.toDouble(), // 使用动画值作为滚动目标
duration: _animationController.duration,
curve: Curves.easeInOut,
);
},
tooltip: 'Animate Scroll',
child: Icon(Icons.arrow_downward),
),
);
}
}
在这个示例中,我们做了以下事情:
- 添加依赖:在
pubspec.yaml
中添加flutter_extended_scroll
依赖。 - 初始化控制器:在
initState
中初始化ScrollController
和AnimationController
。 - 滚动监听:通过
_scrollController.addListener
监听滚动事件,并打印当前滚动位置。 - 自动滚动:使用
Future.delayed
在1秒后自动滚动到指定位置(500像素)。 - 动画滚动:通过点击浮动按钮触发动画滚动,将列表滚动到动画计算出的位置。
注意,ExtendedScrollView
是 flutter_extended_scroll
插件提供的一个包装组件,它允许你使用 ScrollController
进行更高级的滚动控制。如果插件提供了更多功能,你可以查阅其官方文档以获取更多信息和示例。