Хотите изучить новый язык – пожалуйста. Мы подготовили для вас туториал по созданию приложения для заметок на Dart и Flutter.
Flutter – это мобильный кроссплатформенный SDK с открытым исходным кодом от Google. Приложения, написанные на Dart и Flutter, по умолчанию включают в себя Material Design компоненты, что придает привлекательный внешний вид и юзабилити.

Инструкция по установке Flutter на официальном сайте.
Во-первых, давайте настроим проект:
- создадим проект Flutter в Android Studio или в terminal/cmd с помощью команды "flutter create notes";
- в dart удалим класс homePage и создадим новый файл с нашим собственным классом homePage, содержащий наш Scaffold (набор виджетов);
- реализуем stateful класс StaggeredGridPage. Это позволит сделать макет приложения для заметок, содержащий элементы в шахматном порядке.
В приложении для создания шахматной сетки используем Staggered grid, а SQLite – для хранения данных.
Ниже приведен код из pubspec.yaml с необходимыми зависимостями. Добавьте их, сохранитесь и с помощью команды "flutter packages get" инициализируйте новые зависимости:
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
flutter_staggered_grid_view: ^0.2.7
auto_size_text: ^1.1.2
sqflite:
path:
intl: ^0.15.7
share: ^0.6.1
Создайте класс для заметок. Вам нужна функция toMap для запросов к БД:
class Note {
int id;
String title;
String content;
DateTime date_created;
DateTime date_last_edited;
Color note_color;
int is_archived = 0;
Note(this.id, this.title, this.content, this.date_created, this.date_last_edited,
this.note_color);
Map<String, dynamic> toMap(bool forUpdate) {
var data = {
'title': utf8.encode(title),
'content': utf8.encode( content ),
'date_created': epochFromDate( date_created ),
'date_last_edited': epochFromDate( date_last_edited ),
'note_color': note_color.value,
'is_archived': is_archived
};
if(forUpdate){ data["id"] = this.id; }
return data;
}
// Преобразование даты и времени в секунды типа int
after midnight 1st Jan, 1970 UTC int epochFromDate(DateTime dt) {
return dt.millisecondsSinceEpoch ~/ 1000; }
void archiveThisNote(){ is_archived = 1; }
}
В итоге получаем домашнюю страницу HomePage.dart с телом StaggeredGridView. В AppBar поместите кнопку, чтобы пользователь мог переключаться между шахматным и list представлением. А еще, оберните тело в SafeArea для "дружественности" к телефонам.
StaggeredGridView требует четкого указания количества заметок в ряду. В горизонтальном формате на экране телефона или планшета будет организовано по три заметки и две для телефона в портретном формате.
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
import '../Models/Note.dart';
import '../Models/SqliteHandler.dart';
import '../Models/Utility.dart';
import '../Views/StaggeredTiles.dart';
import 'HomePage.dart';
class StaggeredGridPage extends StatefulWidget {
final notesViewType;
const StaggeredGridPage({Key key, this.notesViewType}) : super(key: key);
@override
_StaggeredGridPageState createState() => _StaggeredGridPageState();
}
class _StaggeredGridPageState extends State<StaggeredGridPage> {
var noteDB = NotesDBHandler();
List<Map<String, dynamic>> _allNotesInQueryResult = [];
viewType notesViewType ;
@override
void initState() {
super.initState();
this.notesViewType = widget.notesViewType;
}
@override void setState(fn) {
super.setState(fn);
this.notesViewType = widget.notesViewType;
}
@override
Widget build(BuildContext context) {
GlobalKey _stagKey = GlobalKey();
if(CentralStation.updateNeeded) { retrieveAllNotesFromDatabase(); }
return Container(child: Padding(padding: _paddingForView(context) , child:
new StaggeredGridView.count(key: _stagKey,
crossAxisSpacing: 6, mainAxisSpacing: 6,
crossAxisCount: _colForStaggeredView(context),
children: List.generate(_allNotesInQueryResult.length, (i){
return _tileGenerator(i); }), staggeredTiles: _tilesForView() ,
),
)
);
}
int _colForStaggeredView(BuildContext context) {
if (widget.notesViewType == viewType.List) { return 1; }
// для ширины больше 600 размещаем 3 заметки по горизонтали
return MediaQuery.of(context).size.width > 600 ? 3 : 2 ;
}
List<StaggeredTile> _tilesForView() { // Создание шахматного представления
return List.generate(_allNotesInQueryResult.length,(index){ return StaggeredTile.fit( 1 ); }
) ;
}
EdgeInsets _paddingForView(BuildContext context){
double width = MediaQuery.of(context).size.width;
double padding ;
double top_bottom = 8;
if (width > 500) {
padding = ( width ) * 0.05 ; // 5% ширины с двух сторон
} else {
padding = 8;
}
return EdgeInsets.only(left: padding, right: padding, top: top_bottom, bottom: top_bottom);
}
MyStaggeredTile _tileGenerator(int i){
return MyStaggeredTile( Note(
_allNotesInQueryResult[i]["id"],
_allNotesInQueryResult[i]["title"] == null ? "" : utf8.decode(_allNotesInQueryResult[i]
["title"]),
_allNotesInQueryResult[i]["content"] == null ? "" : utf8.decode(_allNotesInQueryResult[i]
["content"]),
DateTime.fromMillisecondsSinceEpoch(_allNotesInQueryResult[i]["date_created"] * 1000),
DateTime.fromMillisecondsSinceEpoch(_allNotesInQueryResult[i]["date_last_edited"] * 1000),
Color(_allNotesInQueryResult[i]["note_color"] ))
);
}
void retrieveAllNotesFromDatabase() {
// запрос всех заметок из БД, упорядоченных по последнему редактированию.
// Архивные заметки исключаются.
var _testData = noteDB.testSelect();
_testData.then((value){
setState(() {
this._allNotesInQueryResult = value;
CentralStation.updateNeeded = false;
});
});
}
}
Для отображения заметок используйте плитки. Плитка должна содержать предварительный просмотр заголовка и содержимого заметки. В обработке текста разной длины поможет библиотека авто-разворачивания текста.
Для постраничной навигации у Flutter есть Navigator (как segue в iOS или Intent в Android).
import 'package:flutter/material.dart';
import 'package:auto_size_text/auto_size_text.dart';
import '../ViewControllers/NotePage.dart';
import '../Models/Note.dart';
import '../Models/Utility.dart';
class MyStaggeredTile extends StatefulWidget {
final Note note;
MyStaggeredTile(this.note);
@override
_MyStaggeredTileState createState() => _MyStaggeredTileState();
}
class _MyStaggeredTileState extends State<MyStaggeredTile> {
String _content ;
double _fontSize ;
Color tileColor ;
String title;
@override
Widget build(BuildContext context) {
_content = widget.note.content;
_fontSize = _determineFontSizeForContent();
tileColor = widget.note.note_color;
title = widget.note.title;
return GestureDetector(
onTap: ()=> _noteTapped(context),
child: Container(
decoration: BoxDecoration(
border: tileColor == Colors.white ? Border.all(color: CentralStation.borderColor) :
null,
color: tileColor,
borderRadius: BorderRadius.all(Radius.circular(8))),
padding: EdgeInsets.all(8),
child: constructChild(),) ,
);
}
void _noteTapped(BuildContext ctx) {
CentralStation.updateNeeded = false;
Navigator.push(ctx, MaterialPageRoute(builder: (ctx) => NotePage(widget.note)));
}
Widget constructChild() {
List<Widget> contentsOfTiles = [];
if(widget.note.title.length != 0) {
contentsOfTiles.add(
AutoSizeText(title,
style: TextStyle(fontSize: _fontSize,fontWeight: FontWeight.bold),
maxLines: widget.note.title.length == 0 ? 1 : 3,
textScaleFactor: 1.5,
),
);
contentsOfTiles.add(Divider(color: Colors.transparent,height: 6,),);
}
contentsOfTiles.add(
AutoSizeText(
_content,
style: TextStyle(fontSize: _fontSize),
maxLines: 10,
textScaleFactor: 1.5,)
);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,
children: contentsOfTiles
);
}
double _determineFontSizeForContent() {
int charCount = _content.length + widget.note.title.length ;
double fontSize = 20 ;
if (charCount > 110 ) { fontSize = 12; }
else if (charCount > 80) { fontSize = 14; }
else if (charCount > 50) { fontSize = 16; }
else if (charCount > 20) { fontSize = 18; }
return fontSize;
}
}
Плитка выглядит примерно так:

Теперь реализуйте вьюху для редактирования/создания заметки, обладающую различными функциями в AppBar, например: отмена, архивирование и т. д. Больше дополнительных действий можно вызвать в модальном блоке: поделиться, копировать, удалить и вызов горизонтально-прокручиваемого выбора цвета для смены фона конкретной заметки.
Виджеты NotePage, BottomSheet и ColorSlider разнесите по разным классам и файлам для чистого управляемого кода. Чтобы изменить цвет, выбранный пользователем из ColorSlider, нужно обновить состояние объекта. Подключите виджеты через callback-функции, чтобы они реагировали на изменения, и могли самостоятельно обновляться.

import 'package:flutter/material.dart';
class ColorSlider extends StatefulWidget {
final void Function(Color) callBackColorTapped ;
final Color noteColor ;
ColorSlider({@required this.callBackColorTapped, @required this.noteColor});
@override
_ColorSliderState createState() => _ColorSliderState();
}
class _ColorSliderState extends State<ColorSlider> {
final colors = [
Color(0xffffffff), // classic white
Color(0xfff28b81), // light pink
Color(0xfff7bd02), // yellow
Color(0xfffbf476), // light yellow
Color(0xffcdff90), // light green
Color(0xffa7feeb), // turquoise
Color(0xffcbf0f8), // light cyan
Color(0xffafcbfa), // light blue
Color(0xffd7aefc), // plum
Color(0xfffbcfe9), // misty rose
Color(0xffe6c9a9), // light brown
Color(0xffe9eaee) // light gray
];
final Color borderColor = Color(0xffd3d3d3);
final Color foregroundColor = Color(0xff595959);
final _check = Icon(Icons.check);
Color noteColor;
int indexOfCurrentColor;
@override void initState() {
super.initState();
this.noteColor = widget.noteColor;
indexOfCurrentColor = colors.indexOf(noteColor);
}
@override
Widget build(BuildContext context) {
return ListView(
scrollDirection: Axis.horizontal,
children:
List.generate(colors.length, (index)
{
return
GestureDetector(
onTap: ()=> _colorChangeTapped(index),
child: Padding(
padding: EdgeInsets.only(left: 6, right: 6),
child:Container(
child: new CircleAvatar(
child: _checkOrNot(index),
foregroundColor: foregroundColor,
backgroundColor: colors[index],
),
width: 38.0,
height: 38.0,
padding: const EdgeInsets.all(1.0), // border width
decoration: new BoxDecoration(
color: borderColor,
shape: BoxShape.circle,
)
) )
);
})
,);
}
void _colorChangeTapped(int indexOfColor) {
setState(() {
noteColor = colors[indexOfColor];
indexOfCurrentColor = indexOfColor;
widget.callBackColorTapped(colors[indexOfColor]);
});
}
Widget _checkOrNot(int index){
if (indexOfCurrentColor == index) {
return _check;
}
return null;
}
}

Комментарии