Фиксированный компонент Flutter с прокручиваемыми виджетами все прокручиваются

Я изучаю флаттер и пытаюсь реализовать экран с фиксированным компонентом, а остальные можно прокручивать. Класс SingleChildScrollView выглядит так, как мне нужно, но я не могу заставить его работать. Это проект личного обучения, в котором я пытаюсь разработать систему управления запасами с использованием штрих-кодов с пакетом mobile_scanner. Я тестирую на своем телефоне Android, и в приведенном ниже примере есть дублирующиеся поля, чтобы усугубить проблему: я хочу, чтобы окно/предварительный просмотр сканера камеры было зафиксировано в верхней части экрана, а все остальные компоненты (должны ли они быть в форме? ), чтобы его можно было прокручивать. Например, экран получения инвентаря может иметь несколько штрих-кодов, позволяющих точно знать, где хранится товар (штрих-код на товаре, штрих-код на лотке, штрих-код на полке, штрих-код на шкафу). Экран удаления инвентаря может быть просто штрих-кодом товара. Мой код всегда прокручивает весь экран независимо от того, какие виджеты я использую.

Возможно, это будет еще один вопрос, но я упомяну его здесь. Лучшей реализацией было бы использовать чтение штрих-кода в качестве нового виджета/экрана, но я еще недостаточно понимаю управление состоянием. Я использую пакет auto_route и попробую с этим примером кулинарной книги. Я предполагаю, что моя проблема с исправлением и прокруткой произойдет и здесь.

main.dart

import 'package:flutter/material.dart';
import 'package:example/example_screen.dart';

void main() {
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ExampleScreen(),
        ),
      ),
    );
  }
}

example_screen.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// import 'package:auto_route/auto_route.dart';
import 'package:mobile_scanner/mobile_scanner.dart';

// @RoutePage()
class ExampleScreen extends StatefulWidget  {
  ExampleScreen();

  @override
  State<ExampleScreen> createState() => _ExampleScreenState();
}

class _ExampleScreenState extends State<ExampleScreen> with WidgetsBindingObserver {
  final MobileScannerController controller = MobileScannerController(
    autoStart: false,
    torchEnabled: false,
    useNewCameraSelector: true,
  );
      
  final String tag = 'EXAMPLE';
  Barcode? _barcode;
  int activeBarcodeNum = 0;
  StreamSubscription<Object?>? _subscription;
  String? tmpDisplay;

  final textController1 = TextEditingController();
  final textController2 = TextEditingController();  

  void _handleBarcode(BarcodeCapture barcodes) {
    Barcode? tmpCode = barcodes.barcodes.firstOrNull;
    final rawVal = tmpCode?.rawValue.toString();
    
    // TODO: do some logic
    // if (rawVal!.contains('TYPE')) {
      // check current values
      setState(() {
          _barcode = tmpCode;
          controller.stop();
          activeBarcodeNum = 0;
      });
    // }
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);

    _subscription = controller.barcodes.listen(_handleBarcode);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (!controller.value.isInitialized) {
      return;
    }

    switch (state) {
      case AppLifecycleState.detached:
      case AppLifecycleState.hidden:
      case AppLifecycleState.paused:
        return;
      case AppLifecycleState.resumed:
        _subscription = controller.barcodes.listen(_handleBarcode);

        // auto start controller on app resume
        // unawaited(controller.start());
      case AppLifecycleState.inactive:
        unawaited(_subscription?.cancel());
        _subscription = null;
        unawaited(controller.stop());
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Example screen"),
        backgroundColor: Colors.deepOrange[400],
      ),
      body: SingleChildScrollView(
        child: IntrinsicHeight(
          child: Column(
            children: [
              // scanner box
              SizedBox(
                height: 500,
                child: MobileScanner(
                  controller: controller,
                ),
              ),

              Expanded(
                child: Column(
                  children: [
                    Row(
                      children: [
                        Expanded(
                          flex: 3,
                          child: ElevatedButton(
                            onPressed: () async {
                              // start widget
                              setState(() {
                                activeBarcodeNum = 1;
                              });
                  
                              controller.start();
                            },
                            child: const Text(
                              'Scan',
                              style: TextStyle(color: Colors.black, fontSize: 20),
                            ),
                          )
                        ),
                        
                        Expanded(
                          flex: 3,
                          child: Padding(
                            padding: const EdgeInsets.all(18.0),
                            child: Text('Tag: ${_barcode == null ? 'N/A' : _barcode!.rawValue.toString()}'),
                          ),
                        ), 
                      ],
                    ),

                    Row(
                      children: [
                        Expanded(
                          flex: 3,
                          child: ElevatedButton(
                            onPressed: () async {
                              // start widget
                              setState(() {
                                activeBarcodeNum = 1;
                              });
                  
                              controller.start();
                            },
                            child: const Text(
                              'Scan',
                              style: TextStyle(color: Colors.black, fontSize: 20),
                            ),
                          )
                        ),
                        
                        Expanded(
                          flex: 3,
                          child: Padding(
                            padding: const EdgeInsets.all(18.0),
                            child: Text('Tag: ${_barcode == null ? 'N/A' : _barcode!.rawValue.toString()}'),
                          ),
                        ), 
                      ],
                    ),

                    Row(
                      children: [
                        Expanded(
                          flex: 3,
                          child: ElevatedButton(
                            onPressed: () async {
                              // start widget
                              setState(() {
                                activeBarcodeNum = 1;
                              });
                  
                              controller.start();
                            },
                            child: const Text(
                              'Scan',
                              style: TextStyle(color: Colors.black, fontSize: 20),
                            ),
                          )
                        ),
                        
                        Expanded(
                          flex: 3,
                          child: Padding(
                            padding: const EdgeInsets.all(18.0),
                            child: Text('Tag: ${_barcode == null ? 'N/A' : _barcode!.rawValue.toString()}'),
                          ),
                        ), 
                      ],
                    ),

                    Row(
                      children: [
                        Expanded(
                          flex: 3,
                          child: ElevatedButton(
                            onPressed: () async {
                              // start widget
                              setState(() {
                                activeBarcodeNum = 4;
                              });
                  
                              controller.start();
                            },
                            child: const Text(
                              'Scan',
                              style: TextStyle(color: Colors.black, fontSize: 20),
                            ),
                          )
                        ),
                        
                        Expanded(
                          flex: 3,
                          child: Padding(
                            padding: const EdgeInsets.all(18.0),
                            child: Text('Tag: ${_barcode == null ? 'N/A' : _barcode!.rawValue.toString()}'),
                          ),
                        ), 
                      ],
                    ),
                    
                    Row(
                      children: [
                        Expanded(
                          flex: 3,
                          child: ElevatedButton(
                            onPressed: () async {
                              // start widget
                              setState(() {
                                activeBarcodeNum = 4;
                              });
                  
                              controller.start();
                            },
                            child: const Text(
                              'Scan',
                              style: TextStyle(color: Colors.black, fontSize: 20),
                            ),
                          )
                        ),
                        
                        Expanded(
                          flex: 3,
                          child: Padding(
                            padding: const EdgeInsets.all(18.0),
                            child: Text('Tag: ${_barcode == null ? 'N/A' : _barcode!.rawValue.toString()}'),
                          ),
                        ), 
                      ],
                    ),


                    Row(
                      children: [
                        Expanded(
                          flex: 3,
                          child: ElevatedButton(
                            onPressed: () async {
                              // start widget
                              setState(() {
                                activeBarcodeNum = 4;
                              });
                  
                              controller.start();
                            },
                            child: const Text(
                              'Scan',
                              style: TextStyle(color: Colors.black, fontSize: 20),
                            ),
                          )
                        ),
                        
                        Expanded(
                          flex: 3,
                          child: Padding(
                            padding: const EdgeInsets.all(18.0),
                            child: Text('Tag: ${_barcode == null ? 'N/A' : _barcode!.rawValue.toString()}'),
                          ),
                        ), 
                      ],
                    ),


                    Row(
                      children: [
                        Expanded(
                          flex: 3,
                          child: ElevatedButton(
                            onPressed: () async {
                              // start widget
                              setState(() {
                                activeBarcodeNum = 4;
                              });
                  
                              controller.start();
                            },
                            child: const Text(
                              'Scan',
                              style: TextStyle(color: Colors.black, fontSize: 20),
                            ),
                          )
                        ),
                        
                        Expanded(
                          flex: 3,
                          child: Padding(
                            padding: const EdgeInsets.all(18.0),
                            child: Text('Tag: ${_barcode == null ? 'N/A' : _barcode!.rawValue.toString()}'),
                          ),
                        ), 
                      ],
                    ),

                    Padding(
                      //padding: const EdgeInsets.only(left:15.0,right: 15.0,top:0,bottom: 0),
                      padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
                      child: TextField(
                        controller: textController1,
                        keyboardType: TextInputType.number,
                        inputFormatters: <TextInputFormatter>[
                          FilteringTextInputFormatter.digitsOnly
                        ], // Only numbers can be entered
                        decoration: const InputDecoration(
                            border: OutlineInputBorder(),
                            labelText: 'Quantity',
                            hintText: 'Quantity'
                          ),
                      ),
                    ),

                    Padding(
                      padding: const EdgeInsets.all(15.0),
                      child: TextField(
                        controller: textController2,
                        maxLines: 5,
                        decoration: InputDecoration(
                          hintText: "Enter notes here",
                            enabledBorder: OutlineInputBorder(
                              borderSide: const BorderSide(color: Colors.grey),
                              borderRadius: BorderRadius.circular(10),
                            ),
                            focusedBorder: OutlineInputBorder(
                              borderSide: const BorderSide(
                                color: Colors.black,
                                width: 2,
                              ),
                              borderRadius: BorderRadius.circular(15),
                            ),
                            errorBorder: OutlineInputBorder(
                              borderSide: const BorderSide(
                                color: Colors.red,
                                width: 2,
                              ),
                              borderRadius: BorderRadius.circular(10),
                            ),
                          ),
                      ),
                    ),

                    Container(
                      height: 70,
                      width: 250,
                      decoration: BoxDecoration(
                      color: Colors.green, borderRadius: BorderRadius.circular(20)),
                      child: TextButton(
                        onPressed: () {
                          final quantStr = textController1.text;
                          
                          if (quantStr.isNotEmpty) {
                                          // TODO: do stuff with the values
                          }
                          
                          // back to home screen
                          // AutoRouter.of(context).popAndPush(HomeRoute());
                        },
                        child: const Text(
                          'SUBMIT',
                          style: TextStyle(color: Colors.white, fontSize: 25),
                        ),
                      )
                    )
                  ], 
                ),
              ),
            ],
        ),
        ),
      ),
    );
  }

  @override
  Future<void> dispose() async {
    WidgetsBinding.instance.removeObserver(this);
    unawaited(_subscription?.cancel());
    _subscription = null;
    super.dispose();
    await controller.dispose();
  }
}



1
0
167
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Если вы хотите, чтобы первый виджет был зафиксирован/закреплен на экране, вы можете просто извлечь SingleChildScrollView в один виджет ниже в дереве.

В настоящее время у вас есть SingleChildScrollView в качестве корневого виджета:

Scaffold(
  appBar: AppBar(...),
  body: SingleChildScrollView( //--> SingleChildScrollView as top level
    child: IntrinsicHeight(
      child: Column(
        children: [
          SizedBox(
            height: 500,
            child: MobileScanner(...),
          ),
          Expanded(
            child: Column(
              children: [
               _buildScanButton(),
               _buildScanButton(),  
               ...
              ],
            ),
          ),
        ],
      ),
    ),
  ),
)

что делает весь экран прокручиваемым.

Вместо этого переместите SingleChildScrollView из корневого виджета вниз к следующему Column:

Scaffold(
      appBar: AppBar(...),
      body: Column(
        children: [
          SizedBox( // --> No `SingleChildScrollView`, it's moved down the tree
            height: 300,
            child: MobileScanner(
              controller: controller,
            ),
          ),
          Expanded(
            child: SingleChildScrollView( // --> add the `SingleChildScrollView` here.
              child: Column(
                children: [
                  _buildScanButton(),
                  _buildScanButton(),
                  _buildScanButton(),
                  _buildScanButton(),
                  _buildScanButton(),
                  _buildScanButton(),
          ),
        ],
      ),

Что касается вашего второго вопроса о создании кода в новом виджете/экране:

Вы можете прочитать раздел return-data документации.

Это работает в два этапа:

  1. вы переходите на новый экран и присваиваете result переменной:
 // Navigator.push returns a Future that completes after calling
    // Navigator.pop on the Selection Screen.
    final result = await Navigator.push(
      context,
      // Create the SelectionScreen in the next step.
      MaterialPageRoute(builder: (context) => const SelectionScreen()),
    );
// Close the screen and return "Yep!" as the result.
Navigator.pop(context, 'Yep!');

Вот полный работоспособный пример:

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobile_scanner/mobile_scanner.dart';

void main() {
  runApp(const MainApp());
}

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: ExampleScreen(),
        ),
      ),
    );
  }
}

class ExampleScreen extends StatefulWidget {
  const ExampleScreen({super.key});

  @override
  State<ExampleScreen> createState() => _ExampleScreenState();
}

class _ExampleScreenState extends State<ExampleScreen>
    with WidgetsBindingObserver {
  final MobileScannerController controller = MobileScannerController(
    autoStart: false,
    torchEnabled: false,
    useNewCameraSelector: true,
  );

  Barcode? _barcode;
  StreamSubscription<Object?>? _subscription;

  final textController1 = TextEditingController();
  final textController2 = TextEditingController();

  void _handleBarcode(BarcodeCapture barcodes) {
    final Barcode? tmpCode = barcodes.barcodes.firstOrNull;
    setState(() {
      _barcode = tmpCode;
      controller.stop();
    });
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _subscription = controller.barcodes.listen(_handleBarcode);
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (!controller.value.isInitialized) {
      return;
    }

    switch (state) {
      case AppLifecycleState.resumed:
        _subscription = controller.barcodes.listen(_handleBarcode);
        break;
      case AppLifecycleState.inactive:
      case AppLifecycleState.detached:
      case AppLifecycleState.hidden:
      case AppLifecycleState.paused:
        unawaited(_subscription?.cancel());
        _subscription = null;
        unawaited(controller.stop());
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Example Screen"),
        backgroundColor: Colors.deepOrange[400],
      ),
      body: Column(
        children: [
          SizedBox(
            height: 300,
            child: MobileScanner(
              controller: controller,
            ),
          ),
          Expanded(
            child: SingleChildScrollView(
              child: Column(
                children: [
                  _buildScanButton(),
                  _buildScanButton(),
                  _buildScanButton(),
                  _buildScanButton(),
                  _buildScanButton(),
                  _buildScanButton(),
                  Padding(
                    padding: const EdgeInsets.symmetric(
                        horizontal: 15, vertical: 10),
                    child: TextField(
                      controller: textController1,
                      keyboardType: TextInputType.number,
                      inputFormatters: [FilteringTextInputFormatter.digitsOnly],
                      decoration: const InputDecoration(
                        border: OutlineInputBorder(),
                        labelText: 'Quantity',
                        hintText: 'Quantity',
                      ),
                    ),
                  ),
                  Padding(
                    padding: const EdgeInsets.all(15.0),
                    child: TextField(
                      controller: textController2,
                      maxLines: 5,
                      decoration: InputDecoration(
                        hintText: "Enter notes here",
                        enabledBorder: OutlineInputBorder(
                          borderSide: const BorderSide(color: Colors.grey),
                          borderRadius: BorderRadius.circular(10),
                        ),
                        focusedBorder: OutlineInputBorder(
                          borderSide:
                              const BorderSide(color: Colors.black, width: 2),
                          borderRadius: BorderRadius.circular(15),
                        ),
                        errorBorder: OutlineInputBorder(
                          borderSide:
                              const BorderSide(color: Colors.red, width: 2),
                          borderRadius: BorderRadius.circular(10),
                        ),
                      ),
                    ),
                  ),
                  Container(
                    height: 70,
                    width: 250,
                    decoration: BoxDecoration(
                      color: Colors.green,
                      borderRadius: BorderRadius.circular(20),
                    ),
                    child: TextButton(
                      onPressed: () {
                        final quantStr = textController1.text;
                        if (quantStr.isNotEmpty) {
                          // TODO: do stuff with the values
                        }
                      },
                      child: const Text(
                        'SUBMIT',
                        style: TextStyle(color: Colors.white, fontSize: 25),
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildScanButton() {
    return Row(
      children: [
        Expanded(
          flex: 3,
          child: ElevatedButton(
            onPressed: () {
              setState(() {
                controller.start();
              });
            },
            child: const Text(
              'Scan',
              style: TextStyle(color: Colors.black, fontSize: 20),
            ),
          ),
        ),
        Expanded(
          flex: 3,
          child: Padding(
            padding: const EdgeInsets.all(18.0),
            child: Text(
              'Tag: ${_barcode == null ? 'N/A' : _barcode!.rawValue.toString()}',
            ),
          ),
        ),
      ],
    );
  }

  @override
  Future<void> dispose() async {
    WidgetsBinding.instance.removeObserver(this);
    unawaited(_subscription?.cancel());
    _subscription = null;
    super.dispose();
    await controller.dispose();
  }
}

поместите SingleChildScrollView в столбец, который необходимо прокручивать. здесь вы применяете его к каждому ребенку в теле леса.

При использовании виджета SingleChildScrollView весь контент попадает под дочерние элементы и становится прокручиваемым. Поскольку вы все еще учитесь, я думаю, это может вам помочь. Кажется, вы использовали тот же виджет Row для тестирования. Вынесите внутренний виджет Column наружу и поместите фиксированное/непрокручиваемое содержимое в его дочерние элементы. Когда вам нужно прокрутить, используйте SingleChildScrollView и оберните прокручиваемое содержимое внутри своих дочерних элементов. (Обертка SingleChildScrollView контейнером может позволить вам внести некоторые изменения).

 body: Column(
  children: [
              Row(
                  children: [
                    Expanded(
                      flex: 3,
                      child: ElevatedButton(
                        onPressed: () async {
                          // start widget
                          setState(() {
                            activeBarcodeNum = 1;
                          });
              
                          controller.start();
                        },
                        child: const Text(
                          'Scan',
                          style: TextStyle(color: Colors.black, fontSize: 20),
                        ),
                      )
                    ),
                    
                    Expanded(
                      flex: 3,
                      child: Padding(
                        padding: const EdgeInsets.all(18.0),
                        child: Text('Tag: ${_barcode == null ? 'N/A' : _barcode!.rawValue.toString()}'),
                      ),
                    ), 
                  ],
                ),
       SingleChildScrollView(
        children: [
          **...content for scrolling..**
        ]

Другие вопросы по теме