#flutter #dart #parameter-passing #flutter-provider
Вопрос:
У меня есть следующая структура кода: Родитель —gt; Ребенок —gt;gt; Внук. Мне нужно вызвать функцию в родительском от внука. Кроме того, логическая переменная должна быть доступна как у родителя, так и у внука для изменений пользовательского интерфейса (чтобы они были синхронизированы). Как мне это сделать?
Рабочий стол: (Родительский виджет)
import 'dart:async'; import 'dart:io'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_sound/flutter_sound.dart'; import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:voice_recorder/recording_info_provider.dart'; import 'central_icons_widget.dart'; import 'digital_display.dart'; import 'record_button_with_outer_circle_and_shadow.dart'; import 'recording_state.dart'; import 'recordings_list.dart'; /// const int tSampleRate = 44000; typedef _Fn = void Function(); /// Example app. class HomeScreen extends StatefulWidget { const HomeScreen({Key? key}) : super(key: key); @override _HomeScreenState createState() =gt; _HomeScreenState(); } class _HomeScreenState extends Statelt;HomeScreengt; { FlutterSoundPlayer? _mPlayer = FlutterSoundPlayer(); FlutterSoundRecorder? _mRecorder = FlutterSoundRecorder(); bool _mPlayerIsInited = false; bool _mRecorderIsInited = false; bool _mplaybackReady = false; String? _mPath; StreamSubscription? _mRecordingDataSubscription; Futurelt;voidgt; _openRecorder() async { var statusMicPermission = await Permission.microphone.request(); if (statusMicPermission != PermissionStatus.granted) { throw RecordingPermissionException('Microphone permission not granted'); } var statusStoragePermission = await Permission.manageExternalStorage.request(); if (statusStoragePermission != PermissionStatus.granted) { throw Exception('Storage permission not granted'); } await _mRecorder!.openAudioSession(); setState(() { _mRecorderIsInited = true; }); } @override void initState() { super.initState(); // Be careful : openAudioSession return a Future. // Do not access your FlutterSoundPlayer or FlutterSoundRecorder before the completion of the Future _mPlayer!.openAudioSession().then((value) { setState(() { _mPlayerIsInited = true; }); }); _openRecorder(); } @override void dispose() { stopPlayer(); _mPlayer!.closeAudioSession(); _mPlayer = null; stopRecorder(); _mRecorder!.closeAudioSession(); _mRecorder = null; super.dispose(); } Futurelt;Directorygt; _createFolder() async { final folderName = "voice_recorder"; final dir = Directory("storage/emulated/0/$folderName"); if (await dir.exists()) { // TODO: print("exist"); } else { // TODO: print("not exist"); dir.create(); } return dir; } Futurelt;IOSinkgt; createFile() async { // var tempDir = await getExternalStorageDirectory(); // _mPath = '${tempDir!.path}/flutter_sound_example.pcm'; var directory = await _createFolder(); _mPath = '${directory.path}/flutter_sound_example.pcm'; var outputFile = File(_mPath!); if (outputFile.existsSync()) { await outputFile.delete(); } return outputFile.openWrite(); } Futurelt;voidgt; record() async { assert(_mRecorderIsInited amp;amp; _mPlayer!.isStopped); var sink = await createFile(); var recordingDataController = StreamControllerlt;Foodgt;(); _mRecordingDataSubscription = recordingDataController.stream.listen((buffer) { if (buffer is FoodData) { sink.add(buffer.data!); } }); await _mRecorder!.startRecorder( toStream: recordingDataController.sink, codec: Codec.pcm16, numChannels: 1, sampleRate: tSampleRate, ); //isRecording = _mRecorder!.isRecording; setState(() {}); } Futurelt;voidgt; stopRecorder() async { await _mRecorder!.stopRecorder(); if (_mRecordingDataSubscription != null) { await _mRecordingDataSubscription!.cancel(); _mRecordingDataSubscription = null; } _mplaybackReady = true; } _Fn? getRecorderFn() { // RecordingState.isRecording = !RecordingState.isRecording; // setState(() {}); // RecordingInfoProvider recordingInfoProvider = // Provider.oflt;RecordingInfoProvidergt;(context, listen: false); // recordingInfoProvider.updateRecordingStatus(); if (!_mRecorderIsInited || !_mPlayer!.isStopped) { return null; } return _mRecorder!.isStopped ? () { record; } : () { //stopRecorder().then((value) =gt; setState(() {})); stopRecorder; }; } Function? toggleRecording() { if (!_mRecorderIsInited || !_mPlayer!.isStopped) { return null; } return _mRecorder!.isStopped ? () { record; } : () { //stopRecorder().then((value) =gt; setState(() {})); stopRecorder; }; } void play() async { assert(_mPlayerIsInited amp;amp; _mplaybackReady amp;amp; _mRecorder!.isStopped amp;amp; _mPlayer!.isStopped); await _mPlayer!.startPlayer( fromURI: _mPath, sampleRate: tSampleRate, codec: Codec.pcm16, numChannels: 1, whenFinished: () { setState(() {}); }); // The readability of Dart is very special :-( setState(() {}); } Futurelt;voidgt; stopPlayer() async { await _mPlayer!.stopPlayer(); } _Fn? getPlaybackFn() { if (!_mPlayerIsInited || !_mplaybackReady || !_mRecorder!.isStopped) { return null; } return _mPlayer!.isStopped ? play : () { stopPlayer().then((value) =gt; setState(() {})); }; } @override Widget build(BuildContext context) { RecordingInfoProvider recordingInfoProvider = Provider.oflt;RecordingInfoProvidergt;(context, listen: false); bool _isRecording = recordingInfoProvider.getRecordingStatus(); return Scaffold( backgroundColor: Colors.white, body: SafeArea( child: //FROSTED GLASS BACKGROUND Container( decoration: BoxDecoration( borderRadius: const BorderRadius.all( Radius.circular(25), ), gradient: LinearGradient( colors: [ Colors.grey.withOpacity(0.3), Colors.grey.withOpacity(0.1), ], begin: Alignment.topLeft, end: Alignment.bottomRight, ), ), //child: Expanded( child: BackdropFilter( filter: ImageFilter.blur( sigmaX: 5, sigmaY: 5, ), child: Column( children: [ //TIMER (Digital Display) DigitalDisplay(), //isRecording SHOULD UPDATE THIS WIDGET //RECORD BUTTON //isRecording SHOULD UPDATE THIS WIDGET RecordButtonWithOuterCircleAndShadow( //outerCircleSize: outerCircleSize, //isRecording: _isRecording, / toggleRecording: () {}, ), //MUSIC AND SETTINGS CentralIconsWidget(), //RECORDINGS LIST RecordingsList(), ], ), ), //), ), ), ); } } //typedef VoidCallback = void Function();
Кнопка записи без окружности и тени:
import 'package:blobs/blobs.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:voice_recorder/recording_state.dart'; import 'constants.dart'; import 'home_screen.dart'; import 'record_button_with_shadow.dart'; import 'recording_info_provider.dart'; class RecordButtonWithOuterCircleAndShadow extends StatefulWidget { RecordButtonWithOuterCircleAndShadow({ Key? key, required this.toggleRecording, //required this.isRecording, }) : //_isRecording = isRecording, super(key: key); final Function? toggleRecording; //final VoidCallback? toggleRecording; //final bool isRecording; @override Statelt;RecordButtonWithOuterCircleAndShadowgt; createState() =gt; _RecordButtonWithOuterCircleAndShadowState(); } class _RecordButtonWithOuterCircleAndShadowState extends Statelt;RecordButtonWithOuterCircleAndShadowgt; { bool _isRecording = false; @override Widget build(BuildContext context) { RecordingInfoProvider recordingInfoProvider = Provider.oflt;RecordingInfoProvidergt;(context, listen: false); _isRecording = recordingInfoProvider.getRecordingStatus(); var outerCircleSize = 275.0; return Container( child: //widget.isRecording _isRecording ? Stack( children: [ Container( height: outerCircleSize, width: outerCircleSize, //SHADOW AND BUTTON child: Padding( padding: EdgeInsets.all(40), child: RecordButtonWithShadow( toggleRecording: widget.toggleRecording, //isRecording: _isRecording, size: _isRecording ? 80 : 200, ), ), ), Blob.animatedRandom( size: 325, styles: BlobStyles( color: kRecordButtonColor, fillType: BlobFillType.stroke, //minGrowth:6, //gradient: LinearGradient(), strokeWidth: 3, ), ), ], ) : Container( height: outerCircleSize, width: outerCircleSize, decoration: BoxDecoration( borderRadius: BorderRadius.circular(150), border: Border.all( width: _isRecording ? 5 : 3, color: _isRecording ? kRecordButtonColor : Colors.grey, style: BorderStyle.solid, ), ), //SHADOW AND BUTTON child: Padding( padding: EdgeInsets.all(40), child: RecordButtonWithShadow( toggleRecording: widget.toggleRecording, //isRecording: _isRecording, size: _isRecording ? 80 : 200, ), ), ), ); } } //typedef VoidCallback = void Function(context);
RecordButtonWithShadow:
import 'dart:async'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'constants.dart'; import 'package:animated_text_kit/animated_text_kit.dart'; import 'recording_info_provider.dart'; import 'recording_state.dart'; class RecordButtonWithShadow extends StatefulWidget { RecordButtonWithShadow({ Key? key, required this.toggleRecording, //required this.isRecording, required this.size, }) : super(key: key); //final bool isRecording; final double size; //final VoidCallback? toggleRecording; final Function? toggleRecording; @override _RecordButtonWithShadowState createState() =gt; _RecordButtonWithShadowState(); } class _RecordButtonWithShadowState extends Statelt;RecordButtonWithShadowgt; with TickerProviderStateMixin { late AnimationController animationController; late Animationlt;doublegt; animation; //String _buttonText = 'Record'.toUpperCase(); late bool _isRecording = false; @override void initState() { animationController = AnimationController( vsync: this, duration: Duration(milliseconds: 500), ); animation = Tweenlt;doublegt;(begin: 0, end: pi).animate(animationController); super.initState(); } @override Widget build(BuildContext context) { RecordingInfoProvider recordingInfoProvider = Provider.oflt;RecordingInfoProvidergt;(context); _isRecording = recordingInfoProvider.getRecordingStatus(); //_isRecording = RecordingState.isRecording; var decorationRecording = BoxDecoration( shape: BoxShape.rectangle, borderRadius: BorderRadius.circular(widget.size * 0.35), boxShadow: [ BoxShadow( color: kRecordButtonShadowColor.withOpacity(0.25), spreadRadius: widget.size * 0.1, blurRadius: widget.size * 0.2, offset: Offset(0, widget.size * 0.3), ), ], ); var decorationNotRecording = BoxDecoration( shape: BoxShape.circle, boxShadow: [ BoxShadow( color: kRecordButtonShadowColor.withOpacity(0.4), spreadRadius: widget.size * 0.05, blurRadius: widget.size * 0.15, offset: Offset(0, widget.size * 0.09), ), ], ); return Scaffold( backgroundColor: Colors.transparent, body: Center( //SHADOW child: Container( decoration: //widget.isRecording ? decorationRecording : decorationNotRecording, _isRecording ? decorationRecording : decorationNotRecording, //BUTTON child: AnimatedContainer( duration: Duration(milliseconds: 500), decoration: BoxDecoration(shape: BoxShape.circle), height: widget.size, width: widget.size, child: InkWell( borderRadius: //widget.isRecording _isRecording ? BorderRadius.circular(100) : BorderRadius.circular(widget.size / 2), onTap: () { recordingInfoProvider.toggleRecordingStatus(); widget.toggleRecording; }, child: AnimatedBuilder( animation: animation, builder: (context, child) { return Container( child: Transform.rotate( angle: animation.value, child: Stack( alignment: Alignment.center, children: [ Container( alignment: Alignment.center, decoration: BoxDecoration( borderRadius: //widget.isRecording _isRecording ? BorderRadius.circular( widget.size * 0.3) : null, shape: //widget.isRecording _isRecording ? BoxShape.rectangle : BoxShape.circle, gradient: _isRecording ? LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ kRecordButtonColor.withOpacity(0.8), kRecordButtonColor, kRecordButtonColor, kRecordButtonColor.withOpacity(0.8), ], stops: const [ 0, 0.3, 0.7, 1, ], ) : RadialGradient( colors: [ kRecordButtonColor, kRecordButtonColor, kRecordButtonColor.withOpacity(0.2), ], stops: const [ 0, 0.7, 1, ], ), ), ), Container( alignment: Alignment.center, decoration: BoxDecoration( borderRadius: //widget.isRecording _isRecording ? BorderRadius.circular( widget.size * 0.3, ) : null, shape: _isRecording ? BoxShape.rectangle : BoxShape.circle, gradient: //widget.isRecording _isRecording ? LinearGradient( colors: [ Colors.white.withOpacity(0.2), kRecordButtonColor .withOpacity(0), kRecordButtonColor .withOpacity(0), Colors.white.withOpacity(0.2), ], stops: const [ 0, 0.3, 0.7, 1, ], ) : RadialGradient( colors: [ kRecordButtonColor, kRecordButtonColor, kRecordButtonColor .withOpacity(0.2), ], stops: const [ 0, 0.7, 1, ], ), ), child: //!widget.isRecording !_isRecording ? SizedBox( width: 250.0, child: Center( child: DefaultTextStyle( style: TextStyle( fontSize: widget.size * 0.12, letterSpacing: 3, ), child: AnimatedTextKit( animatedTexts: [ TyperAnimatedText( 'Record'.toUpperCase(), ), ], totalRepeatCount: 1, onTap: () { // startStopRecording(); // animateController(); }, ), ), ), ) : null), ], ), ), ); }), ), ), ), ), ); } }
Я могу передать функцию в качестве параметра, но как тогда обновить логическое значение?!. Кроме того, в настоящее время я получаю сообщение об ошибке: «java.lang.Исключение IllegalStateException: Ответ уже отправлен «.
Ответ №1:
Ваша идея передать функцию и логическое значение виджету внука верна. Что касается обновления логического значения, это должно быть сделано из родительского виджета.
Мне кажется, я заметил ошибку в вашем коде, но я не уверен, так как у меня нет полного кода в родительском виджете:
toggleRecording: getRecorderFn(),
Следует заменить на:
toggleRecording: getRecorderFn,
или
toggleRecording: () =gt; getRecorderFn(),
Способ, которым вы отправляете функцию, неверен, так как вы отправляете не функцию, а ее возврат, иначе говоря, когда виджет будет построен, ваш код вызовет getRecorderFn()
и вернет его результат. Опять же, я не уверен, что возвращаемое значение getRecoderFn()
этого может быть намеренным, но у меня нет остальной части кода, чтобы судить.
Что касается ошибки, требуется более подробная информация.
Комментарии:
1. «Это должно быть сделано из родительского виджета» — как? Как мне поддерживать его в глобальном масштабе для состояния приложения?
2. Если вам нужно поддерживать состояние для всего приложения, я предлагаю вам использовать подход к управлению состоянием, или вы можете поднять состояние до более высокого родительского уровня, но это не рекомендуется, так как это приведет к большому количеству шаблонного кода и не будет эффективным.
3. Мне просто нужно логическое значение для синхронизации нескольких виджетов…
4. Вы можете использовать метод, который присутствует в ответе выше
5. Я уже использую его, это не помогает. Метод не обновляет логическое значение и состояние. Я обновил код.