我在 Flutter 应用程序中将 CupertinoPicker 与 FixExtentScrollController 一起使用时遇到问题。这个想法是让选择器从特定项目(出生年份或体重,取决于屏幕)开始,但滚动行为无法按预期工作。拾取器似乎会“跳跃”位置,并且在某些情况下控制器会冻结或无法正确响应触摸。
出现问题的代码:
class SurveyPage extends StatefulWidget {
const SurveyPage({super.key});
@override
_SurveyPageState createState() => _SurveyPageState();
}
class _SurveyPageState extends State<SurveyPage> {
int _currentStep = 0;
final _formKey = GlobalKey<FormState>();
final TextEditingController _nameController = TextEditingController();
final DateTime _selectedBirthday = DateTime.now();
DateTime dateTime = DateTime(2024, 1, 1);
bool showCard = false;
int selectedWeight = 60;
int selectedYear = 2000;
final List<Map<String, dynamic>> _steps = [
{
'question': 'Qual é o seu nome?',
'type': 'name',
'controller': TextEditingController()
},
{'question': 'Qual é o seu peso?', 'type': 'weight'},
{'question': 'When is your birthday?', 'type': 'birthday'},
];
void _nextStep() {
if (_currentStep < _steps.length - 1) {
setState(() {
_currentStep += 1;
});
} else {
log("Formulário completo!");
log("Nome: ${_nameController.text}");
log("Data de Aniversário: $_selectedBirthday");
}
}
void _previousStep() {
if (_currentStep > 0) {
setState(() {
_currentStep -= 1;
});
}
}
@override
Widget build(BuildContext context) {
double progress = (_currentStep + 1) / _steps.length;
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).brightness == Brightness.dark
? AppColors.darkScaffoldColor
: AppColors.lightScaffoldColor,
title: LinearProgressIndicator(
value: progress,
backgroundColor: Theme.of(context).brightness == Brightness.dark
? AppColors.darkDividerColor
: AppColors.lightDividerColor,
color: AppColors.primaryColor,
borderRadius: BorderRadius.circular(3.r),
minHeight: 6.h,
),
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child: Form(
key: _formKey,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
if (_steps[_currentStep]['type'] == 'name')
Column(
children: [
Text(
_steps[_currentStep]['question'],
style: Theme.of(context).textTheme.displayMedium,
),
32.verticalSpace,
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
hintText: 'Digite aqui',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Este campo é obrigatório';
}
return null;
},
),
],
),
if (_steps[_currentStep]['type'] == 'birthday')
Column(
children: [
Text(
_steps[_currentStep]['question'],
style: Theme.of(context).textTheme.displayMedium,
),
SizedBox(height: 20.h),
SizedBox(
height: 400,
child: CupertinoPicker(
scrollController: FixedExtentScrollController(
initialItem: selectedYear - 1924),
itemExtent: 48.0,
onSelectedItemChanged: (int index) {
int newSelectedYear = 1924 + index;
log('Selecionado: $newSelectedYear');
setState(() {
selectedYear = newSelectedYear;
showCard = selectedYear == 2008;
});
},
children: List.generate(
2008 - 1924 + 1,
(index) => Center(
child: Text(
'${1924 + index}',
style: Theme.of(context)
.textTheme
.displaySmall!,
),
),
),
),
),
if (showCard)
Card(
color: AppColors.alertSecondaryOrangeCard,
margin: const EdgeInsets.all(10),
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Row(
children: [
Image.asset(
AppImages.warning,
height: 32.h,
),
8.horizontalSpace,
Text(
'Atenção!',
style: Theme.of(context)
.textTheme
.displaySmall!,
),
],
),
16.verticalSpace,
Text(
'Devido aos requisitos da COPPA, é preciso ter pelo menos 13 anos de idade. Na União Europeia, o RGPD requer idade mínima de 16 anos.',
style: Theme.of(context)
.textTheme
.bodyMedium!,
),
],
),
),
),
],
),
if (_steps[_currentStep]['type'] == 'weight')
Column(
children: [
Text(
_steps[_currentStep]['question'],
style: Theme.of(context).textTheme.displayMedium,
),
32.verticalSpace,
SizedBox(
height: 400,
child: CupertinoPicker(
scrollController: FixedExtentScrollController(
initialItem: selectedWeight - 40,
),
itemExtent: 48.0,
onSelectedItemChanged: (int index) {
setState(() {
selectedWeight = 40 + index;
log('Selecionado: $selectedWeight kg');
});
},
children: List.generate(
300 - 40 + 1,
(index) => Center(
child: Text(
'${40 + index} kg',
style: Theme.of(context)
.textTheme
.displaySmall!,
),
),
),
),
),
],
),
],
),
),
),
SizedBox(height: 20.h),
if (_currentStep > 0)
CustomDoubleButton(
primaryText: _currentStep == _steps.length - 1
? "Concluir"
: "Próximo",
secondaryText: "Voltar",
onPressedPrimaryButton: _nextStep,
onPressedSecondaryButton: _previousStep,
)
else
CustomButton(
buttonText: "Próximo",
onPressed: _nextStep,
),
],
),
),
),
);
}
}
我正在创建一个表单,用户可以使用 CupertinoPicker 选择他们的体重和出生日期。这个想法是,拾取器从 60 公斤开始,然后达到 2000 公斤,然后用户可以根据需要进行调整。滚动时,选择应更改并更新显示的值。
该行为仅在第一个 cupertinoPicker 中有效,第二个总是出现错误,如果我更改问题的顺序,第一个有效,第二个出现错误。
我尝试重构和更改控制器,但它不起作用,我不知道为什么在同一屏幕上有两个 CupertinoPicker 会导致问题,我想这是内部的问题。
尝试一下:
import 'dart:developer';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:lunaria_cycles/common/components/custom_button.dart';
import 'package:lunaria_cycles/common/components/custom_double_button.dart';
import 'package:lunaria_cycles/common/constants/app_images.dart';
import '../../common/constants/app_colors.dart';
class SurveyPage extends StatefulWidget {
const SurveyPage({super.key});
@override
_SurveyPageState createState() => _SurveyPageState();
}
class _SurveyPageState extends State<SurveyPage> {
int _currentStep = 0;
final _formKey = GlobalKey<FormState>();
final TextEditingController _nameController = TextEditingController();
int selectedWeight = 60;
int selectedYear = 2000;
// Controladores para o picker
late FixedExtentScrollController _weightController;
late FixedExtentScrollController _yearController;
final List<Map<String, dynamic>> _steps = [
{'question': 'Qual é o seu nome?', 'type': 'name'},
{'question': 'Qual é o seu peso?', 'type': 'weight'},
{'question': 'Quando é o seu aniversário?', 'type': 'birthday'},
];
@override
void initState() {
super.initState();
// Inicializando os controladores
_weightController = FixedExtentScrollController(initialItem: selectedWeight - 40);
_yearController = FixedExtentScrollController(initialItem: selectedYear - 1924);
}
@override
void dispose() {
// Liberando os controladores quando o widget é destruído
_weightController.dispose();
_yearController.dispose();
super.dispose();
}
void _nextStep() {
if (_currentStep < _steps.length - 1) {
setState(() {
_currentStep += 1;
});
} else {
log("Formulário completo!");
log("Nome: ${_nameController.text}");
}
}
void _previousStep() {
if (_currentStep > 0) {
setState(() {
_currentStep -= 1;
});
}
}
@override
Widget build(BuildContext context) {
double progress = (_currentStep + 1) / _steps.length;
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).brightness == Brightness.dark
? AppColors.darkScaffoldColor
: AppColors.lightScaffoldColor,
title: LinearProgressIndicator(
value: progress,
backgroundColor: Theme.of(context).brightness == Brightness.dark
? AppColors.darkDividerColor
: AppColors.lightDividerColor,
color: AppColors.primaryColor,
borderRadius: BorderRadius.circular(3.r),
minHeight: 6.h,
),
),
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child: Form(
key: _formKey,
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
if (_steps[_currentStep]['type'] == 'name')
Column(
children: [
Text(
_steps[_currentStep]['question'],
style: Theme.of(context).textTheme.displayMedium,
),
32.verticalSpace,
TextFormField(
controller: _nameController,
decoration: const InputDecoration(
hintText: 'Digite aqui',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'Este campo é obrigatório';
}
return null;
},
),
],
),
if (_steps[_currentStep]['type'] == 'birthday')
Column(
children: [
Text(
_steps[_currentStep]['question'],
style: Theme.of(context).textTheme.displayMedium,
),
SizedBox(height: 20.h),
SizedBox(
height: 400,
child: CupertinoPicker(
scrollController: _yearController,
itemExtent: 48.0,
onSelectedItemChanged: (int index) {
setState(() {
selectedYear = 1924 + index;
});
},
children: List.generate(
2008 - 1924 + 1,
(index) => Center(
child: Text(
'${1924 + index}',
style: Theme.of(context)
.textTheme
.displaySmall!,
),
),
),
),
),
],
),
if (_steps[_currentStep]['type'] == 'weight')
Column(
children: [
Text(
_steps[_currentStep]['question'],
style: Theme.of(context).textTheme.displayMedium,
),
32.verticalSpace,
SizedBox(
height: 400,
child: CupertinoPicker(
scrollController: _weightController,
itemExtent: 48.0,
onSelectedItemChanged: (int index) {
setState(() {
selectedWeight = 40 + index;
});
},
children: List.generate(
300 - 40 + 1,
(index) => Center(
child: Text(
'${40 + index} kg',
style: Theme.of(context)
.textTheme
.displaySmall!,
),
),
),
),
),
],
),
],
),
),
),
SizedBox(height: 20.h),
if (_currentStep > 0)
CustomDoubleButton(
primaryText: _currentStep == _steps.length - 1
? "Concluir"
: "Próximo",
secondaryText: "Voltar",
onPressedPrimaryButton: _nextStep,
onPressedSecondaryButton: _previousStep,
)
else
CustomButton(
buttonText: "Próximo",
onPressed: _nextStep,
),
],
),
),
),
);
}
}