使用 CupertinoPicker 和 FixExtentScrollController 时出现问题 - 滚动无法正常工作

问题描述 投票:0回答:1

我在 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 会导致问题,我想这是内部的问题。

flutter dart flutter-cupertino cupertinopicker
1个回答
0
投票

尝试一下:

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,
                ),
            ],
          ),
        ),
      ),
    );
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.