我目前正在开发一个 Flutter 项目,其中在 SingleChildScrollView 中有一堆小部件。这些小部件的位置是从现有小部件中检索的,并且由于它们位于 SingleChildScrollView 内,因此位置可以随着用户滚动而动态更改。
我创建了小部件层次结构的简化版本,其中有一个红色容器、一个蓝色容器和一个绿色容器,它们应根据某些条件与红色或蓝色容器重叠。我使用 AnimatedPositioned 来实现动画效果。
这是我的代码片段:
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
bool move = false;
final GlobalKey redContainerKey = GlobalKey();
final GlobalKey blueContainerKey = GlobalKey();
double? _topPosition;
// Position of Strings
Offset _getWidgetPosition(GlobalKey key) {
RenderBox renderBox = key.currentContext!.findRenderObject() as RenderBox;
return renderBox.localToGlobal(const Offset(0, 0));
}
void _getPosition() {
WidgetsBinding.instance.addPostFrameCallback((_) {
double topPosition = move
? _getWidgetPosition(redContainerKey).dy
: _getWidgetPosition(blueContainerKey).dy;
setState(() {
_topPosition = topPosition;
});
print(topPosition);
});
}
final _scrollController = ScrollController();
// Scroll to Top
void _scrollToTop() {
_scrollController.animateTo(
0.0,
duration: const Duration(milliseconds: 500),
curve: Curves.linear,
);
}
// Scroll to Bottom
void _scrollToBottom() {
Future.delayed(const Duration(milliseconds: 500), () {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 500),
curve: Curves.linear,
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SingleChildScrollView(
controller: _scrollController,
child: Stack(
children: [
Column(
children: [
Container(
key: redContainerKey,
color: Colors.red,
height: 50,
width: MediaQuery.of(context).size.width,
),
Container(height: 700),
Container(
key: blueContainerKey,
color: Colors.blue,
height: 50,
width: MediaQuery.of(context).size.width,
),
],
),
AnimatedPositioned(
top: _topPosition,
duration: const Duration(seconds: 1),
child: AnimatedContainer(
duration: const Duration(seconds: 1),
color: Colors.green,
height: 50,
width: MediaQuery.of(context).size.width,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
move = !move;
if (move) {
_scrollToTop();
} else {
_scrollToBottom();
}
_getPosition();
});
},
),
);
}
}
虽然这有效,但问题是绿色容器并不总是按预期与红色或蓝色容器重叠,特别是当屏幕因滚动而移动时。我正在寻找有关如何在 SingleChildScrollView 中无缝实现这种动态定位和动画效果的指导。任何帮助或建议将不胜感激!
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
// Global Key
final GlobalKey stackKey = GlobalKey();
final GlobalKey redContainerKey = GlobalKey();
final GlobalKey blueContainerKey = GlobalKey();
// Flag to Indicate Type of Movement
bool move = false;
// Position (y) for Animated Positioned
double? _topPosition;
// Position of Widget
Offset _getWidgetPosition(GlobalKey key) {
RenderBox renderBox = key.currentContext!.findRenderObject() as RenderBox;
return renderBox
.localToGlobal(const Offset(0, -80)); // Account for AppBar height
}
// Retrieving Position of Widget
void _getPosition() {
WidgetsBinding.instance.addPostFrameCallback((_) {
double topPosition = move
? _getWidgetPosition(redContainerKey).dy
: (_getWidgetPosition(blueContainerKey).dy -
_getWidgetPosition(stackKey).dy);
setState(() {
_topPosition = topPosition;
});
});
}
// Scroll Controller
final _scrollController = ScrollController();
// Scroll to Top
void _scrollToTop() {
_scrollController.animateTo(
0.0,
duration: const Duration(milliseconds: 1000),
curve: Curves.linear,
);
}
// Scroll to Bottom
void _scrollToBottom() {
Future.delayed(const Duration(milliseconds: 500), () {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 500),
curve: Curves.linear,
);
});
}
// Scroll Notification
bool handleScrollNotification(ScrollNotification notification) {
if (notification is ScrollEndNotification) {
_getPosition();
}
return true;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: NotificationListener<ScrollNotification>(
onNotification: handleScrollNotification,
child: SingleChildScrollView(
controller: _scrollController,
child: Stack(
key: stackKey,
children: [
Column(
children: [
Container(
key: redContainerKey,
color: Colors.red,
height: 50,
width: MediaQuery.of(context).size.width,
),
Container(height: 1000),
Container(
key: blueContainerKey,
color: Colors.blue,
height: 50,
width: MediaQuery.of(context).size.width,
),
Container(height: 700), // Change this to observe desired behaviour
],
),
AnimatedPositioned(
top: _topPosition,
duration: const Duration(seconds: 1),
child: AnimatedContainer(
duration: const Duration(seconds: 1),
color: Colors.green,
height: 50,
width: MediaQuery.of(context).size.width,
),
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
move = !move;
});
if (move) {
_scrollToTop();
} else {
_scrollToBottom();
}
_getPosition();
},
),
);
}
}