Flutter: ввод текста

За ввод текста во Flutter отвечает виджет TextField и соответствующий класс TextEditingController для управления вводом текста

Для примера отрисуем страницу авторизации. Сначала «Рыба»:

import 'package:flutter/material.dart';
import 'package:invent/globals.dart' as globals;

class Login extends StatefulWidget {
  _LoginState createState() => _LoginState(); // сюда передаем текущее состояние страницы
}
class _LoginState extends State<Login> {// _ впереди класса, означает чтоб скрыть доступ из другх файлов
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Авторизация')),
      body: Center(

      ),
    );
  }
}

Добавим надписи Логин и Пароль:

      body: Center(
        child: Padding(
          padding: EdgeInsetsDirectional.fromSTEB(10, 10, 10, 0),
          child: Column(
              children: [
                  Text('Логин:'),
                  Text('Пароль:',),
                ],
          ),
        ),
      ),

Далее необходимо рассказать о классе TextEditingController.

Данный класс имеет два конструктора:

TextEditingController({String text})
TextEditingController.fromValue(TextEditingValue value)

Первый конструктор в качестве параметра принимает начальное значение, которое затем будет отображаться в ассоциированном текстовом поле. Второй конструктор также принимает начальное значение, но в виде объекта TextEditingValue

TextEditingController позволяет контроллировать введенный и выделенный текст в поле ввода, для чего у него определены три свойства:

  • selection: выделенный текст в виде объекта TextSelection
  • text: текущий введенный текст в виде объекта String
  • value: текущее значение в виде объекта TextEditingValue

При изменении ввода в текстовом поле связанный объект TextEditingController уведомляет слушателей об изменении. Для добавления слушателей — функций обработного вызова в классе определен метод addListener(). Все добавленные слушатели могут считать введенный текст, а также выделенный текст и таким образом узнать о произошедших изменениях.

Когда объект TextEditingController больше не нужен, у него надо вызвать метод dispose(). Это позволит освободить все ресурсы, используемые объектом.

Таким образом модифицируем код, добавив поля ввода для логина и пароля:

class _LoginState extends State<Login> {// _ впереди класса, означает чтоб скрыть доступ из другх файлов
  final _controller1 = TextEditingController();
  final _controller2 = TextEditingController();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Авторизация')),
      body: Center(
        child: Padding(
          padding: EdgeInsetsDirectional.fromSTEB(10, 10, 10, 0),
          child: Column(
              children: [
                  Text('Логин:'),
                  TextField(
                      style: TextStyle(fontSize: 22),
                      controller: _controller1
                  ),
                  Text('Пароль:',),
                  TextField(
                      style: TextStyle(fontSize: 22),
                      controller: _controller2
                  ),
                ],
          ),
        ),
      ),
    );
  }
}

Теперь создадим обработчики событий ввода и начальное заполнение контроллеров.

class _LoginState extends State<Login> {// _ впереди класса, означает чтоб скрыть доступ из другх файлов
  final _controller1 = TextEditingController();
  final _controller2 = TextEditingController();

  _changField(){
    print("-чтото повводили..");
  }

  @override
  void initState() {
    super.initState();
    _controller1.text = "Логин";
    _controller1.addListener(_changField);
    _controller2.text = "***";
    _controller2.addListener(_changField);
  }
  @override
  void dispose() {
    _controller1.dispose();
    _controller2.dispose();
    super.dispose();
  }

Результат:

Если поля ввода заполняются например из списка, то заполнение начальных значений и их сохранение можно организовать как-то так:

 TextField(
                                            style: TextStyle(fontSize: 22),
                                            controller: TextEditingController()..text = globals.ComfortList[index]["temp"].toString(),
                                            onChanged: (text) {
                                              globals.ComfortList[index]["temp"]=text;
                                              print("Введено: {$index},{$text}");
                                            }

Получится:

Flutter: изменение состояния виджета «онлайн»

Вполне типичная ситуация: отображать пункты меню в зависимости от того, авторизован пользователь или нет. Используя глобальные переменные из предыдущей статьи, дополним код, при котором после «прохождения авторизации», соответствующий пункт меню будем скрывать. Для этого, мы можем использовать опять же виджет Visibility, который имеет свойство visible: true/false. Странно конечно, почему любой виджет не имеет этого свойства, а приходится его «обёртывать» им..

....
 Visibility(
              visible: globals.is_login==true?false:true,
              child: ListTile(
                  leading: Icon(Icons.arrow_forward_ios,),
                  title: Text('Авторизация'),
                  trailing: Icon(Icons.arrow_forward_ios, color: Color(0xFF303030), size: 20,),
                  tileColor: Color(0xFFF5F5F5),
                  dense: false,
                  onTap: (){
                    setState(() {
                      globals.UserName="Вася Пукин ";
                      globals.UserId="1";
                      globals.is_login=true;
                    });
                  }
                ),
            ),
...

Функция SetState внутри виджета заставляет перерисовать весь виджет.

Flutter: суперглобальные переменные

Для того чтобы хранить и передавать переменные между экранами и классами, необходимо их объявить в отдельном файле, например globals.dart

library invent.globals;

bool is_login=false;
String UserName = 'Неизвестный герой';
String UserId="0";

Далее во всём используемом коде, в заголовках добавлять

import 'package:invent/globals.dart' as globals;

И далее использовать по типу:

           ListTile(
                leading: Icon(Icons.arrow_forward_ios,),
                title: Text('Загрузка списка'),
                trailing: Icon(Icons.arrow_forward_ios, color: Color(0xFF303030), size: 20,),
                tileColor: Color(0xFFF5F5F5),
                dense: false,
                onTap: (){
                  if (globals.UserId=="0"){
                    print("Доступа нету..");
                  } else {
                    print("Проходи дорогой товарищ");
                  };
                },
              ),

Flutter: генерация apk (Android)

Для того чтобы попробовать получившееся приложение на «реальном» телефоне, а не на эмуляторе, необходимо проделать ряд манипуляций.

Вариант 1: создание не подписанного apk. Не пригодно для размещения в Google Play, но пригодно для того чтобу устанавливать на телефон для «потестить». Этот способ подойдет если нужно «по быстрому» или не планируется распространять приложение через Google Play

Для этого достаточно выполнить в меню: Build -> Flutter -> Build APK. Спустя несколько минут компиляции и сборки, результат можно выловить в папке C:\Users\<пользователь>\AndroidStudioProjects\invent\build\app\outputs\flutter-apk

Вариант 2: создание подписанного apk и публикация в Google Play занимает несколько шагов

  • Добавление иконки приложения (launchpad icon)
  • Настройка подписи приложения
  • Проверка манифеста приложения (AndroidManifest.xml)
  • Проверка конфигурации сборки (build.gradle)
  • Создание версии приложения для публикации (—release)
  • Публиковать в Google Play Store

Добавление иконки приложения

По умолчанию приложение собирается с иконкой Flutter. Чтобы её поменять — нужно заместить эти иконки своими в папке <app dir>/android/app/src/main/res/

Настройка подписи приложения

Для публикации приложения в Play Store необходимо подписать приложение цифровой подписью. Создание ключа:

keytool -genkey -v -keystore ~/.keystore \
	-keyalg RSA \
    -keysize 2048 \
    -validity 10000 \
    -alias key

Далее необходимо связать приложение с ключём. Для этого нужно создать файл <app dir>/android/key.properties

storePassword=<пароль>
keyPassword=<пароль>
keyAlias=key
storeFile=<путь к файлу ключа например /home/user/.keystore>

Сконфигурируем Gradle <app dir>/android/app/build.gradle

  1. заменить следующее
android {

на информацию о файле конфигурации

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
	keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {

2. Заменить конфигурацию сборки релиза

buildTypes {
	release {
    	// TODO: Add your own signing config for the release build.
        // Signing with debug keys for now,
        // so flutter run --release works
        signingConfig signingConfig.debug
    }
}

на следующую информацию, содержащую параметры подписи

signingConfigs {
	release {
    	keyAlias keystoreProperties['keyAlias']
        keyPassword keystoreProperties['keyPassword']
        storeFile file(keystoreProperties['storeFile'])
        storePassword keystoreProperties['storePassword']
    }
}
buildTypes {
	release {
    	signingConfig signingConfigs.release
    }
}

Теперь сборка релиза будет подписываться автоматически.

Проверка манифеста приложения

Необходимо убедиться что в файле <app dir>/android/app/src/main/AndroidManifest.xml содержится верное наименование приложения, а также установлены верные uses-permissions

Проверим конфигурацию сборки

В <app dir>/android/appbuild.gradle необходимо выставить идентификатор приложения, имя и номер сборки.

Flutter: сканирование штрихкода

1. Добавим в pubspec.yaml зависимость

dependencies:
  flutter_barcode_scanner: ^2.0.0

Добавим в код предыдущего проекта:

class _Page1State extends State<Page1> { // _ впереди класса, означает чтоб скрыть доступ из других файлов
...
....
  String _scanBarcode = 'Unknown';
  Future<void> startBarcodeScanStream() async {
      FlutterBarcodeScanner.getBarcodeStreamReceiver(
          '#ff6666', 'Cancel', true, ScanMode.BARCODE)!
          .listen((barcode) => print(barcode));
  }
  Future<void> scanBar() async {
    String barcodeScanRes;
    try {
      barcodeScanRes = await FlutterBarcodeScanner.scanBarcode('#ff6666', 'Отменить', false, ScanMode.BARCODE);
      Fluttertoast.showToast(
          msg: "Прочитано:"+barcodeScanRes,
          toastLength: Toast.LENGTH_SHORT,
          gravity: ToastGravity.CENTER,
          timeInSecForIosWeb: 1,
          backgroundColor: Colors.red,
          textColor: Colors.white,
          fontSize: 16.0
      );
    } on PlatformException {
      barcodeScanRes = 'Ошибка получения версии платформы';
    }
    if (!mounted) return;
    setState(() {
      _scanBarcode = barcodeScanRes;
    });
  }
....
....
....
   ListTile(
                onTap: (){
                  print(scanBar());
                },
                leading: Icon(
                  Icons.sick,
                ),

Следует отметить, что при запуске кода на Android/IOS приложение самостоятельно попросит доступ к камере, а потому еще одной проблемой — обработкой запросов доступа меньше.

1 5 6 7 8 9