Flutter Dart 键、初始化、构造函数
Flutter Dart Keys, Initialization, Constructors
我是(亲戚)初学者,在网上找到了这个2年前的代码
问题是 VS Studio 显示多个问题,例如:
第 86-90,190 行“由于其类型,参数 '' 的值不能为 'null'”,
第 98-100 行“必须初始化不可为 null 的实例字段”,第 127 行“不能将参数类型 'Widget?' 分配给参数类型 'Widget'”,
第 243 行“'RenderObject' 类型的值无法分配给变量 'RenderBox'”
和第 318 行“参数类型 'num' 不能赋值给参数类型 'double'”
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue, brightness: Brightness.dark),
home: FlightsStepper(),
));
}
class FlightsStepper extends StatefulWidget {
@override
_FlightsStepperState createState() => _FlightsStepperState();
}
class _FlightsStepperState extends State<FlightsStepper> {
int pageNumber = 1;
@override
Widget build(BuildContext context) {
Widget page = pageNumber == 1
? Page(
key: Key('page1'),
onOptionSelected: () => setState(() => pageNumber = 2),
question:
'Do you typically fly for business, personal reasons, or some other reason?',
answers: <String>['Business', 'Personal', 'Others'],
number: 1,
)
: Page(
key: Key('page2'),
onOptionSelected: () => setState(() => pageNumber = 1),
question: 'How many hours is your average flight?',
answers: <String>[
'Less than two hours',
'More than two but less than five hours',
'Others'
],
number: 2,
);
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: backgroundDecoration,
child: SafeArea(
child: Stack(
children: <Widget>[
ArrowIcons(),
Plane(),
Line(),
Positioned.fill(
left: 32.0 + 8,
child: AnimatedSwitcher(
child: page,
duration: Duration(milliseconds: 250),
),
),
],
),
),
),
);
}
}
class Line extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Positioned(
left: 32.0 + 32 + 8,
top: 40,
bottom: 0,
width: 1,
child: Container(color: Colors.white.withOpacity(0.5)),
);
}
}
class Page extends StatefulWidget {
final int number;
final String question;
final List<String> answers;
final VoidCallback onOptionSelected;
const Page(
{Key key,
@required this.onOptionSelected,
@required this.number,
@required this.question,
@required this.answers})
: super(key: key);
@override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> with SingleTickerProviderStateMixin {
List<GlobalKey<_ItemFaderState>> keys;
int selectedOptionKeyIndex;
AnimationController _animationController;
@override
void initState() {
super.initState();
keys = List.generate(
2 + widget.answers.length,
(_) => GlobalKey<_ItemFaderState>(),
);
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
onInit();
}
Future<void> animateDot(Offset startOffset) async {
OverlayEntry entry = OverlayEntry(
builder: (context) {
double minTop = MediaQuery.of(context).padding.top + 52;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Positioned(
left: 26.0 + 32 + 8,
top: minTop +
(startOffset.dy - minTop) * (1 - _animationController.value),
child: child,
);
},
child: Dot(),
);
},
);
Overlay.of(context).insert(entry);
await _animationController.forward(from: 0);
entry.remove();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 32),
ItemFader(key: keys[0], child: StepNumber(number: widget.number)),
ItemFader(
key: keys[1],
child: StepQuestion(question: widget.question),
),
Spacer(),
...widget.answers.map((String answer) {
int answerIndex = widget.answers.indexOf(answer);
int keyIndex = answerIndex + 2;
return ItemFader(
key: keys[keyIndex],
child: OptionItem(
name: answer,
onTap: (offset) => onTap(keyIndex, offset),
showDot: selectedOptionKeyIndex != keyIndex,
),
);
}),
SizedBox(height: 64),
],
);
}
void onTap(int keyIndex, Offset offset) async {
for (GlobalKey<_ItemFaderState> key in keys) {
await Future.delayed(Duration(milliseconds: 40));
key.currentState.hide();
if (keys.indexOf(key) == keyIndex) {
setState(() => selectedOptionKeyIndex = keyIndex);
animateDot(offset).then((_) => widget.onOptionSelected());
}
}
}
void onInit() async {
for (GlobalKey<_ItemFaderState> key in keys) {
await Future.delayed(Duration(milliseconds: 40));
key.currentState.show();
}
}
}
class StepNumber extends StatelessWidget {
final int number;
const StepNumber({Key key, @required this.number}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 64, right: 16),
child: Text(
'0$number',
style: TextStyle(
fontSize: 64,
fontWeight: FontWeight.bold,
color: Colors.white.withOpacity(0.5),
),
),
);
}
}
class StepQuestion extends StatelessWidget {
final String question;
const StepQuestion({Key key, @required this.question}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 64, right: 16),
child: Text(
question,
style: TextStyle(fontSize: 24),
),
);
}
}
class OptionItem extends StatefulWidget {
final String name;
final void Function(Offset dotOffset) onTap;
final bool showDot;
const OptionItem(
{Key key, @required this.name, @required this.onTap, this.showDot = true})
: super(key: key);
@override
_OptionItemState createState() => _OptionItemState();
}
class _OptionItemState extends State<OptionItem> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
RenderBox object = context.findRenderObject();
Offset globalPosition = object.localToGlobal(Offset.zero);
widget.onTap(globalPosition);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Row(
children: <Widget>[
SizedBox(width: 26),
Dot(visible: widget.showDot),
SizedBox(width: 26),
Expanded(
child: Text(
widget.name,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 26),
),
)
],
),
),
);
}
}
class ItemFader extends StatefulWidget {
final Widget child;
const ItemFader({Key key, @required this.child}) : super(key: key);
@override
_ItemFaderState createState() => _ItemFaderState();
}
class _ItemFaderState extends State<ItemFader>
with SingleTickerProviderStateMixin {
//1 means its below, -1 means its above
int position = 1;
AnimationController _animationController;
Animation _animation;
void show() {
setState(() => position = 1);
_animationController.forward();
}
void hide() {
setState(() => position = -1);
_animationController.reverse();
}
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 600),
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 64 * position * (1 - _animation.value)),
child: Opacity(
opacity: _animation.value,
child: child,
),
);
},
child: widget.child,
);
}
}
class Dot extends StatelessWidget {
final bool visible;
const Dot({Key key, this.visible = true}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: visible ? Colors.white : Colors.transparent,
),
);
}
}
class ArrowIcons extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Positioned(
left: 8,
bottom: 0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.arrow_upward),
onPressed: () {},
),
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: IconButton(
color: Color.fromRGBO(120, 58, 183, 1),
icon: Icon(Icons.arrow_downward),
onPressed: () {},
),
),
],
),
);
}
}
class Plane extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Positioned(
left: 32.0 + 8,
top: 32,
child: RotatedBox(
quarterTurns: 2,
child: Icon(
Icons.airplanemode_active,
size: 64,
),
),
);
}
}
const backgroundDecoration = BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromRGBO(76, 61, 243, 1),
Color.fromRGBO(120, 58, 183, 1),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
);
大多数错误是由于 Dart 2.0 中引入的空安全功能引起的,阅读更多相关信息 here。如果您能够安装旧版本的 Flutter 或 Dart,该代码可能 运行 没问题。在任何情况下,大多数错误都可以通过遵循 IDE 的建议操作来解决。我帮了你一个忙,因为它对大多数部分来说都相当简单。有一些微妙之处可能不是那么简单。无论如何,我相信这是相同的代码,但删除了错误和警告:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue, brightness: Brightness.dark),
home: const FlightsStepper(),
));
}
class FlightsStepper extends StatefulWidget {
const FlightsStepper({Key? key}) : super(key: key);
@override
_FlightsStepperState createState() => _FlightsStepperState();
}
class _FlightsStepperState extends State<FlightsStepper> {
int pageNumber = 1;
@override
Widget build(BuildContext context) {
Widget page = pageNumber == 1
? Page(
key: const Key('page1'),
onOptionSelected: () => setState(() => pageNumber = 2),
question:
'Do you typically fly for business, personal reasons, or some other reason?',
answers: const <String>['Business', 'Personal', 'Others'],
number: 1,
)
: Page(
key: const Key('page2'),
onOptionSelected: () => setState(() => pageNumber = 1),
question: 'How many hours is your average flight?',
answers: const <String>[
'Less than two hours',
'More than two but less than five hours',
'Others'
],
number: 2,
);
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: backgroundDecoration,
child: SafeArea(
child: Stack(
children: <Widget>[
const ArrowIcons(),
const Plane(),
const Line(),
Positioned.fill(
left: 32.0 + 8,
child: AnimatedSwitcher(
child: page,
duration: const Duration(milliseconds: 250),
),
),
],
),
),
),
);
}
}
class Line extends StatelessWidget {
const Line({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
left: 32.0 + 32 + 8,
top: 40,
bottom: 0,
width: 1,
child: Container(color: Colors.white.withOpacity(0.5)),
);
}
}
class Page extends StatefulWidget {
final int number;
final String question;
final List<String> answers;
final VoidCallback onOptionSelected;
const Page(
{Key? key,
required this.onOptionSelected,
required this.number,
required this.question,
required this.answers})
: super(key: key);
@override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> with SingleTickerProviderStateMixin {
late List<GlobalKey<_ItemFaderState>> keys;
int? selectedOptionKeyIndex;
late AnimationController _animationController;
@override
void initState() {
super.initState();
keys = List.generate(
2 + widget.answers.length,
(_) => GlobalKey<_ItemFaderState>(),
);
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
onInit();
}
Future<void> animateDot(Offset startOffset) async {
OverlayEntry entry = OverlayEntry(
builder: (context) {
double minTop = MediaQuery.of(context).padding.top + 52;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Positioned(
left: 26.0 + 32 + 8,
top: minTop +
(startOffset.dy - minTop) * (1 - _animationController.value),
child: child!,
);
},
child: const Dot(),
);
},
);
Overlay.of(context)?.insert(entry);
await _animationController.forward(from: 0);
entry.remove();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 32),
ItemFader(key: keys[0], child: StepNumber(number: widget.number)),
ItemFader(
key: keys[1],
child: StepQuestion(question: widget.question),
),
const Spacer(),
...widget.answers.map((String answer) {
int answerIndex = widget.answers.indexOf(answer);
int keyIndex = answerIndex + 2;
return ItemFader(
key: keys[keyIndex],
child: OptionItem(
name: answer,
onTap: (offset) => onTap(keyIndex, offset),
showDot: selectedOptionKeyIndex != keyIndex,
),
);
}),
const SizedBox(height: 64),
],
);
}
void onTap(int keyIndex, Offset offset) async {
for (GlobalKey<_ItemFaderState> key in keys) {
await Future.delayed(const Duration(milliseconds: 40));
key.currentState?.hide();
if (keys.indexOf(key) == keyIndex) {
setState(() => selectedOptionKeyIndex = keyIndex);
animateDot(offset).then((_) => widget.onOptionSelected());
}
}
}
void onInit() async {
for (GlobalKey<_ItemFaderState> key in keys) {
await Future.delayed(const Duration(milliseconds: 40));
key.currentState?.show();
}
}
}
class StepNumber extends StatelessWidget {
final int number;
const StepNumber({Key? key, required this.number}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 64, right: 16),
child: Text(
'0$number',
style: TextStyle(
fontSize: 64,
fontWeight: FontWeight.bold,
color: Colors.white.withOpacity(0.5),
),
),
);
}
}
class StepQuestion extends StatelessWidget {
final String question;
const StepQuestion({Key? key, required this.question}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 64, right: 16),
child: Text(
question,
style: const TextStyle(fontSize: 24),
),
);
}
}
class OptionItem extends StatefulWidget {
final String name;
final void Function(Offset dotOffset) onTap;
final bool showDot;
const OptionItem(
{Key? key, required this.name, required this.onTap, this.showDot = true})
: super(key: key);
@override
_OptionItemState createState() => _OptionItemState();
}
class _OptionItemState extends State<OptionItem> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
RenderBox object = context.findRenderObject() as RenderBox;
Offset globalPosition = object.localToGlobal(Offset.zero);
widget.onTap(globalPosition);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Row(
children: <Widget>[
const SizedBox(width: 26),
Dot(visible: widget.showDot),
const SizedBox(width: 26),
Expanded(
child: Text(
widget.name,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 26),
),
)
],
),
),
);
}
}
class ItemFader extends StatefulWidget {
final Widget child;
const ItemFader({Key? key, required this.child}) : super(key: key);
@override
_ItemFaderState createState() => _ItemFaderState();
}
class _ItemFaderState extends State<ItemFader>
with SingleTickerProviderStateMixin {
//1 means its below, -1 means its above
int position = 1;
late AnimationController _animationController;
late CurvedAnimation _animation;
void show() {
setState(() => position = 1);
_animationController.forward();
}
void hide() {
setState(() => position = -1);
_animationController.reverse();
}
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 600),
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 64 * position * (1 - _animation.value)),
child: Opacity(
opacity: _animation.value,
child: child,
),
);
},
child: widget.child,
);
}
}
class Dot extends StatelessWidget {
final bool visible;
const Dot({Key? key, this.visible = true}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: visible ? Colors.white : Colors.transparent,
),
);
}
}
class ArrowIcons extends StatelessWidget {
const ArrowIcons({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
left: 8,
bottom: 0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_upward),
onPressed: () {},
),
Container(
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: IconButton(
color: const Color.fromRGBO(120, 58, 183, 1),
icon: const Icon(Icons.arrow_downward),
onPressed: () {},
),
),
],
),
);
}
}
class Plane extends StatelessWidget {
const Plane({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Positioned(
left: 32.0 + 8,
top: 32,
child: RotatedBox(
quarterTurns: 2,
child: Icon(
Icons.airplanemode_active,
size: 64,
),
),
);
}
}
const backgroundDecoration = BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromRGBO(76, 61, 243, 1),
Color.fromRGBO(120, 58, 183, 1),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
);
我是(亲戚)初学者,在网上找到了这个2年前的代码
问题是 VS Studio 显示多个问题,例如:
第 86-90,190 行“由于其类型,参数 '' 的值不能为 'null'”,
第 98-100 行“必须初始化不可为 null 的实例字段”,第 127 行“不能将参数类型 'Widget?' 分配给参数类型 'Widget'”,
第 243 行“'RenderObject' 类型的值无法分配给变量 'RenderBox'”
和第 318 行“参数类型 'num' 不能赋值给参数类型 'double'”
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue, brightness: Brightness.dark),
home: FlightsStepper(),
));
}
class FlightsStepper extends StatefulWidget {
@override
_FlightsStepperState createState() => _FlightsStepperState();
}
class _FlightsStepperState extends State<FlightsStepper> {
int pageNumber = 1;
@override
Widget build(BuildContext context) {
Widget page = pageNumber == 1
? Page(
key: Key('page1'),
onOptionSelected: () => setState(() => pageNumber = 2),
question:
'Do you typically fly for business, personal reasons, or some other reason?',
answers: <String>['Business', 'Personal', 'Others'],
number: 1,
)
: Page(
key: Key('page2'),
onOptionSelected: () => setState(() => pageNumber = 1),
question: 'How many hours is your average flight?',
answers: <String>[
'Less than two hours',
'More than two but less than five hours',
'Others'
],
number: 2,
);
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: backgroundDecoration,
child: SafeArea(
child: Stack(
children: <Widget>[
ArrowIcons(),
Plane(),
Line(),
Positioned.fill(
left: 32.0 + 8,
child: AnimatedSwitcher(
child: page,
duration: Duration(milliseconds: 250),
),
),
],
),
),
),
);
}
}
class Line extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Positioned(
left: 32.0 + 32 + 8,
top: 40,
bottom: 0,
width: 1,
child: Container(color: Colors.white.withOpacity(0.5)),
);
}
}
class Page extends StatefulWidget {
final int number;
final String question;
final List<String> answers;
final VoidCallback onOptionSelected;
const Page(
{Key key,
@required this.onOptionSelected,
@required this.number,
@required this.question,
@required this.answers})
: super(key: key);
@override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> with SingleTickerProviderStateMixin {
List<GlobalKey<_ItemFaderState>> keys;
int selectedOptionKeyIndex;
AnimationController _animationController;
@override
void initState() {
super.initState();
keys = List.generate(
2 + widget.answers.length,
(_) => GlobalKey<_ItemFaderState>(),
);
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 500),
);
onInit();
}
Future<void> animateDot(Offset startOffset) async {
OverlayEntry entry = OverlayEntry(
builder: (context) {
double minTop = MediaQuery.of(context).padding.top + 52;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Positioned(
left: 26.0 + 32 + 8,
top: minTop +
(startOffset.dy - minTop) * (1 - _animationController.value),
child: child,
);
},
child: Dot(),
);
},
);
Overlay.of(context).insert(entry);
await _animationController.forward(from: 0);
entry.remove();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 32),
ItemFader(key: keys[0], child: StepNumber(number: widget.number)),
ItemFader(
key: keys[1],
child: StepQuestion(question: widget.question),
),
Spacer(),
...widget.answers.map((String answer) {
int answerIndex = widget.answers.indexOf(answer);
int keyIndex = answerIndex + 2;
return ItemFader(
key: keys[keyIndex],
child: OptionItem(
name: answer,
onTap: (offset) => onTap(keyIndex, offset),
showDot: selectedOptionKeyIndex != keyIndex,
),
);
}),
SizedBox(height: 64),
],
);
}
void onTap(int keyIndex, Offset offset) async {
for (GlobalKey<_ItemFaderState> key in keys) {
await Future.delayed(Duration(milliseconds: 40));
key.currentState.hide();
if (keys.indexOf(key) == keyIndex) {
setState(() => selectedOptionKeyIndex = keyIndex);
animateDot(offset).then((_) => widget.onOptionSelected());
}
}
}
void onInit() async {
for (GlobalKey<_ItemFaderState> key in keys) {
await Future.delayed(Duration(milliseconds: 40));
key.currentState.show();
}
}
}
class StepNumber extends StatelessWidget {
final int number;
const StepNumber({Key key, @required this.number}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 64, right: 16),
child: Text(
'0$number',
style: TextStyle(
fontSize: 64,
fontWeight: FontWeight.bold,
color: Colors.white.withOpacity(0.5),
),
),
);
}
}
class StepQuestion extends StatelessWidget {
final String question;
const StepQuestion({Key key, @required this.question}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 64, right: 16),
child: Text(
question,
style: TextStyle(fontSize: 24),
),
);
}
}
class OptionItem extends StatefulWidget {
final String name;
final void Function(Offset dotOffset) onTap;
final bool showDot;
const OptionItem(
{Key key, @required this.name, @required this.onTap, this.showDot = true})
: super(key: key);
@override
_OptionItemState createState() => _OptionItemState();
}
class _OptionItemState extends State<OptionItem> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
RenderBox object = context.findRenderObject();
Offset globalPosition = object.localToGlobal(Offset.zero);
widget.onTap(globalPosition);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Row(
children: <Widget>[
SizedBox(width: 26),
Dot(visible: widget.showDot),
SizedBox(width: 26),
Expanded(
child: Text(
widget.name,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 26),
),
)
],
),
),
);
}
}
class ItemFader extends StatefulWidget {
final Widget child;
const ItemFader({Key key, @required this.child}) : super(key: key);
@override
_ItemFaderState createState() => _ItemFaderState();
}
class _ItemFaderState extends State<ItemFader>
with SingleTickerProviderStateMixin {
//1 means its below, -1 means its above
int position = 1;
AnimationController _animationController;
Animation _animation;
void show() {
setState(() => position = 1);
_animationController.forward();
}
void hide() {
setState(() => position = -1);
_animationController.reverse();
}
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: Duration(milliseconds: 600),
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 64 * position * (1 - _animation.value)),
child: Opacity(
opacity: _animation.value,
child: child,
),
);
},
child: widget.child,
);
}
}
class Dot extends StatelessWidget {
final bool visible;
const Dot({Key key, this.visible = true}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: visible ? Colors.white : Colors.transparent,
),
);
}
}
class ArrowIcons extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Positioned(
left: 8,
bottom: 0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(Icons.arrow_upward),
onPressed: () {},
),
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: IconButton(
color: Color.fromRGBO(120, 58, 183, 1),
icon: Icon(Icons.arrow_downward),
onPressed: () {},
),
),
],
),
);
}
}
class Plane extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Positioned(
left: 32.0 + 8,
top: 32,
child: RotatedBox(
quarterTurns: 2,
child: Icon(
Icons.airplanemode_active,
size: 64,
),
),
);
}
}
const backgroundDecoration = BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromRGBO(76, 61, 243, 1),
Color.fromRGBO(120, 58, 183, 1),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
);
大多数错误是由于 Dart 2.0 中引入的空安全功能引起的,阅读更多相关信息 here。如果您能够安装旧版本的 Flutter 或 Dart,该代码可能 运行 没问题。在任何情况下,大多数错误都可以通过遵循 IDE 的建议操作来解决。我帮了你一个忙,因为它对大多数部分来说都相当简单。有一些微妙之处可能不是那么简单。无论如何,我相信这是相同的代码,但删除了错误和警告:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue, brightness: Brightness.dark),
home: const FlightsStepper(),
));
}
class FlightsStepper extends StatefulWidget {
const FlightsStepper({Key? key}) : super(key: key);
@override
_FlightsStepperState createState() => _FlightsStepperState();
}
class _FlightsStepperState extends State<FlightsStepper> {
int pageNumber = 1;
@override
Widget build(BuildContext context) {
Widget page = pageNumber == 1
? Page(
key: const Key('page1'),
onOptionSelected: () => setState(() => pageNumber = 2),
question:
'Do you typically fly for business, personal reasons, or some other reason?',
answers: const <String>['Business', 'Personal', 'Others'],
number: 1,
)
: Page(
key: const Key('page2'),
onOptionSelected: () => setState(() => pageNumber = 1),
question: 'How many hours is your average flight?',
answers: const <String>[
'Less than two hours',
'More than two but less than five hours',
'Others'
],
number: 2,
);
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: backgroundDecoration,
child: SafeArea(
child: Stack(
children: <Widget>[
const ArrowIcons(),
const Plane(),
const Line(),
Positioned.fill(
left: 32.0 + 8,
child: AnimatedSwitcher(
child: page,
duration: const Duration(milliseconds: 250),
),
),
],
),
),
),
);
}
}
class Line extends StatelessWidget {
const Line({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
left: 32.0 + 32 + 8,
top: 40,
bottom: 0,
width: 1,
child: Container(color: Colors.white.withOpacity(0.5)),
);
}
}
class Page extends StatefulWidget {
final int number;
final String question;
final List<String> answers;
final VoidCallback onOptionSelected;
const Page(
{Key? key,
required this.onOptionSelected,
required this.number,
required this.question,
required this.answers})
: super(key: key);
@override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> with SingleTickerProviderStateMixin {
late List<GlobalKey<_ItemFaderState>> keys;
int? selectedOptionKeyIndex;
late AnimationController _animationController;
@override
void initState() {
super.initState();
keys = List.generate(
2 + widget.answers.length,
(_) => GlobalKey<_ItemFaderState>(),
);
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 500),
);
onInit();
}
Future<void> animateDot(Offset startOffset) async {
OverlayEntry entry = OverlayEntry(
builder: (context) {
double minTop = MediaQuery.of(context).padding.top + 52;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Positioned(
left: 26.0 + 32 + 8,
top: minTop +
(startOffset.dy - minTop) * (1 - _animationController.value),
child: child!,
);
},
child: const Dot(),
);
},
);
Overlay.of(context)?.insert(entry);
await _animationController.forward(from: 0);
entry.remove();
}
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const SizedBox(height: 32),
ItemFader(key: keys[0], child: StepNumber(number: widget.number)),
ItemFader(
key: keys[1],
child: StepQuestion(question: widget.question),
),
const Spacer(),
...widget.answers.map((String answer) {
int answerIndex = widget.answers.indexOf(answer);
int keyIndex = answerIndex + 2;
return ItemFader(
key: keys[keyIndex],
child: OptionItem(
name: answer,
onTap: (offset) => onTap(keyIndex, offset),
showDot: selectedOptionKeyIndex != keyIndex,
),
);
}),
const SizedBox(height: 64),
],
);
}
void onTap(int keyIndex, Offset offset) async {
for (GlobalKey<_ItemFaderState> key in keys) {
await Future.delayed(const Duration(milliseconds: 40));
key.currentState?.hide();
if (keys.indexOf(key) == keyIndex) {
setState(() => selectedOptionKeyIndex = keyIndex);
animateDot(offset).then((_) => widget.onOptionSelected());
}
}
}
void onInit() async {
for (GlobalKey<_ItemFaderState> key in keys) {
await Future.delayed(const Duration(milliseconds: 40));
key.currentState?.show();
}
}
}
class StepNumber extends StatelessWidget {
final int number;
const StepNumber({Key? key, required this.number}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 64, right: 16),
child: Text(
'0$number',
style: TextStyle(
fontSize: 64,
fontWeight: FontWeight.bold,
color: Colors.white.withOpacity(0.5),
),
),
);
}
}
class StepQuestion extends StatelessWidget {
final String question;
const StepQuestion({Key? key, required this.question}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 64, right: 16),
child: Text(
question,
style: const TextStyle(fontSize: 24),
),
);
}
}
class OptionItem extends StatefulWidget {
final String name;
final void Function(Offset dotOffset) onTap;
final bool showDot;
const OptionItem(
{Key? key, required this.name, required this.onTap, this.showDot = true})
: super(key: key);
@override
_OptionItemState createState() => _OptionItemState();
}
class _OptionItemState extends State<OptionItem> {
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
RenderBox object = context.findRenderObject() as RenderBox;
Offset globalPosition = object.localToGlobal(Offset.zero);
widget.onTap(globalPosition);
},
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Row(
children: <Widget>[
const SizedBox(width: 26),
Dot(visible: widget.showDot),
const SizedBox(width: 26),
Expanded(
child: Text(
widget.name,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 26),
),
)
],
),
),
);
}
}
class ItemFader extends StatefulWidget {
final Widget child;
const ItemFader({Key? key, required this.child}) : super(key: key);
@override
_ItemFaderState createState() => _ItemFaderState();
}
class _ItemFaderState extends State<ItemFader>
with SingleTickerProviderStateMixin {
//1 means its below, -1 means its above
int position = 1;
late AnimationController _animationController;
late CurvedAnimation _animation;
void show() {
setState(() => position = 1);
_animationController.forward();
}
void hide() {
setState(() => position = -1);
_animationController.reverse();
}
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 600),
);
_animation = CurvedAnimation(
parent: _animationController,
curve: Curves.easeInOut,
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Transform.translate(
offset: Offset(0, 64 * position * (1 - _animation.value)),
child: Opacity(
opacity: _animation.value,
child: child,
),
);
},
child: widget.child,
);
}
}
class Dot extends StatelessWidget {
final bool visible;
const Dot({Key? key, this.visible = true}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: visible ? Colors.white : Colors.transparent,
),
);
}
}
class ArrowIcons extends StatelessWidget {
const ArrowIcons({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Positioned(
left: 8,
bottom: 0,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_upward),
onPressed: () {},
),
Container(
decoration: const BoxDecoration(
shape: BoxShape.circle,
color: Colors.white,
),
child: IconButton(
color: const Color.fromRGBO(120, 58, 183, 1),
icon: const Icon(Icons.arrow_downward),
onPressed: () {},
),
),
],
),
);
}
}
class Plane extends StatelessWidget {
const Plane({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Positioned(
left: 32.0 + 8,
top: 32,
child: RotatedBox(
quarterTurns: 2,
child: Icon(
Icons.airplanemode_active,
size: 64,
),
),
);
}
}
const backgroundDecoration = BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromRGBO(76, 61, 243, 1),
Color.fromRGBO(120, 58, 183, 1),
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
);