Github 地址:GITHUB
没图说个锤子,先上效果图:
最简陋那个是测试直接通过hive读数据
开发环境:
老规矩先上环境:
- Flutter SDK ---->2.5.3 & Dart sdk 2.14.4
- flutter_bloc ^7.0.0
- Jdk 1.8
插件依赖
创建项目 并依赖下方插件
flutter_slidable: ^1.0.0
hive: ^2.0.4
hive_flutter: ^1.1.0
uuid: ^3.0.5
material_design_icons_flutter: ^5.0.6295
flutter_material_color_picker: ^1.1.0+2
google_fonts: ^2.1.0
animated_text_kit: ^4.2.1
intl: ^0.17.0
bloc: ^7.0.0
equatable: ^2.0.0
flutter_bloc: ^7.0.0
meta: ^1.3.0
在项目根目录创建 packages的路径,如下图
创建项目工具包
新建两个包选择项目根目录的packages位置(如图):
选择packagey因为我们只是在这边写一些方法,如果创建插件项目就太大并不合适
两个包名分别为:
todos_repository
todo_repository_simple
todo_repository的编写
在 todo_repository 包添加 依赖
hive: ^2.0.4
hive_flutter: ^1.1.0
创建一个 todo_modle 的dart文件写入如下代码
在Teminal中cd 到 todo_repository 的目录
执行 这行代码,生成.g文件。也就是 todo_model 的适配器,可以自己手写
flutter packages pub run build_runner build
如果报错尝试执行这条
flutter packages pub run build_runner build --delete-conflicting-outputs
如果还是报错那就直接copy这里的代码
todo_model:
// GENERATED CODE - DO NOT MODIFY BY HAND part of 'todo_model.dart'; // ************************************************************************** // TypeAdapterGenerator // ************************************************************************** class TodoCategoryAdapter extends TypeAdapter<TodoCategory> { @override final int typeId = 1; @override TodoCategory read(BinaryReader reader) { switch (reader.readByte()) { case 0: return TodoCategory.personal; case 1: return TodoCategory.work; case 2: return TodoCategory.shopping; default: return TodoCategory.work; } } @override void write(BinaryWriter writer, TodoCategory obj) { switch (obj) { case TodoCategory.personal: writer.writeByte(0); break; case TodoCategory.work: writer.writeByte(1); break; case TodoCategory.shopping: writer.writeByte(2); break; } } @override int get hashCode => typeId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is TodoCategoryAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } class MyColorAdapter extends TypeAdapter<MyColor> { @override final int typeId = 2; @override MyColor read(BinaryReader reader) { switch (reader.readByte()) { case 0: return MyColor.red; case 1: return MyColor.orange; case 2: return MyColor.teal; case 3: return MyColor.pink; case 4: return MyColor.blueGrey; case 5: return MyColor.blue; case 6: return MyColor.purple; default: return MyColor.red; } } @override void write(BinaryWriter writer, MyColor obj) { switch (obj) { case MyColor.red: writer.writeByte(0); break; case MyColor.orange: writer.writeByte(1); break; case MyColor.teal: writer.writeByte(2); break; case MyColor.pink: writer.writeByte(3); break; case MyColor.blueGrey: writer.writeByte(4); break; case MyColor.blue: writer.writeByte(5); break; case MyColor.purple: writer.writeByte(6); break; } } @override int get hashCode => typeId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is MyColorAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } class TodoModelAdapter extends TypeAdapter<TodoModel> { @override final int typeId = 0; @override TodoModel read(BinaryReader reader) { final numOfFields = reader.readByte(); final fields = <int, dynamic>{ for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; return TodoModel( content: fields[0] as String, done: fields[1] as bool, time: fields[2] as DateTime, category: fields[3] as TodoCategory, color: fields[4] as MyColor, ); } @override void write(BinaryWriter writer, TodoModel obj) { writer ..writeByte(5) ..writeByte(0) ..write(obj.content) ..writeByte(1) ..write(obj.done) ..writeByte(2) ..write(obj.time) ..writeByte(3) ..write(obj.category) ..writeByte(4) ..write(obj.color); } @override int get hashCode => typeId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is TodoModelAdapter && runtimeType == other.runtimeType && typeId == other.typeId; }
todo_model.g.dart:
// GENERATED CODE - DO NOT MODIFY BY HAND part of 'todo_model.dart'; // ************************************************************************** // TypeAdapterGenerator // ************************************************************************** class TodoCategoryAdapter extends TypeAdapter<TodoCategory> { @override final int typeId = 1; @override TodoCategory read(BinaryReader reader) { switch (reader.readByte()) { case 0: return TodoCategory.personal; case 1: return TodoCategory.work; case 2: return TodoCategory.shopping; default: return TodoCategory.work; } } @override void write(BinaryWriter writer, TodoCategory obj) { switch (obj) { case TodoCategory.personal: writer.writeByte(0); break; case TodoCategory.work: writer.writeByte(1); break; case TodoCategory.shopping: writer.writeByte(2); break; } } @override int get hashCode => typeId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is TodoCategoryAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } class MyColorAdapter extends TypeAdapter<MyColor> { @override final int typeId = 2; @override MyColor read(BinaryReader reader) { switch (reader.readByte()) { case 0: return MyColor.red; case 1: return MyColor.orange; case 2: return MyColor.teal; case 3: return MyColor.pink; case 4: return MyColor.blueGrey; case 5: return MyColor.blue; case 6: return MyColor.purple; default: return MyColor.red; } } @override void write(BinaryWriter writer, MyColor obj) { switch (obj) { case MyColor.red: writer.writeByte(0); break; case MyColor.orange: writer.writeByte(1); break; case MyColor.teal: writer.writeByte(2); break; case MyColor.pink: writer.writeByte(3); break; case MyColor.blueGrey: writer.writeByte(4); break; case MyColor.blue: writer.writeByte(5); break; case MyColor.purple: writer.writeByte(6); break; } } @override int get hashCode => typeId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is MyColorAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } class TodoModelAdapter extends TypeAdapter<TodoModel> { @override final int typeId = 0; @override TodoModel read(BinaryReader reader) { final numOfFields = reader.readByte(); final fields = <int, dynamic>{ for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; return TodoModel( content: fields[0] as String, done: fields[1] as bool, time: fields[2] as DateTime, category: fields[3] as TodoCategory, color: fields[4] as MyColor, ); } @override void write(BinaryWriter writer, TodoModel obj) { writer ..writeByte(5) ..writeByte(0) ..write(obj.content) ..writeByte(1) ..write(obj.done) ..writeByte(2) ..write(obj.time) ..writeByte(3) ..write(obj.category) ..writeByte(4) ..write(obj.color); } @override int get hashCode => typeId.hashCode; @override bool operator ==(Object other) => identical(this, other) || other is TodoModelAdapter && runtimeType == other.runtimeType && typeId == other.typeId; }
创建两个抽象方法,分别是
TodoRepository
abstract class TodoRepository { /// 加载待办事项 Future<List<TodoModel>> loadTodos(); /// 保存待办事项 Future saveTodos(List<TodoModel> todos); }
ReactiveTodosRepository
abstract class ReactiveTodosRepository { /// 添加新的待办 Future<void> addNewTodo(TodoModel todo); /// 删除待办事项 Future<void> deleteTodo(List<String> idList); /// 获取全部待办事项 Stream<List<TodoModel>> todos(); /// 更新待办事项 Future<void> updateTodo(TodoModel todo); }
熟悉java的都知道,这和java项目中定义的接口是一样的,应该都能看懂
todos_repository_simple
todos_repository的代码基本上就这样,接下来看看 todo_repository_simple
在todo_repository_simple 我们要做的事情是请求网络数据或者本地数据,并将数据返回到页面给到用户观看的一个方法
首先还是添加插件
hive: ^2.0.4
hive_flutter: ^1.1.0
todos_repository:
path: ../todos_repository
hive插件我们已经分别添加了三次,因为项目里不能像安卓那样直接通过根项目拿到依赖,所以只能重新再依赖一次
而 todos_repository则是我们刚刚写的插件,我们需要在这边去实现它的方法,所以就得把它依赖进来
好,那接下来创建一个本地存储的方法
FileStorage代码:
class FileStorage { final String tag; final Future<Directory> Function() getDirectory; const FileStorage( this.tag, this.getDirectory, ); Future<List<TodoModel>> loadTodos() async { final todoBox = await Hive.openBox<TodoModel>("todos"); List<TodoModel> todos = []; todos.addAll(todoBox.values.map((e) => e).toList()); return todos; } Future saveTodos(List<TodoModel> todos) async { final settingsBox = await Hive.openBox<bool>('settings'); final todosBox = await Hive.openBox<TodoModel>('todos'); await todosBox.addAll(todos); await settingsBox.put('initialized', true); } Future saveTodo(TodoModel todoModel) async { if (todoModel.isInBox) { final key = todoModel.key; Hive.box<TodoModel>('todos').put(key, todoModel); } else { await Hive.box<TodoModel>('todos').add(todoModel); } } }
在这里实现两个方法,其中 loadTodos 是通过 hive中读取用户的全部待办事项,当然,当前 hive box 还没初始化,如果现在直接调用肯定是会报错的,等会会在根项目中进行初始化
saveTodos 保存全部待办事项
创建一个 WebClient
这个方法理应上是获取网络的数据,不过目前没有搭后台,所以就先写个模拟数据用用
WebClient:
class WebClient { final Duration delay; const WebClient([this.delay = const Duration(milliseconds: 3000)]); Future<List<TodoModel>> fetchTodos() async { return Future.delayed( delay, () => [ TodoModel( category: TodoCategory.personal, color: MyColor.purple, content: '去散步', done: false, time: DateTime.now().subtract(const Duration(days: 1))), TodoModel( category: TodoCategory.shopping, color: MyColor.orange, content: '去工作', done: false, time: DateTime.now().subtract(const Duration(days: 2))), TodoModel( category: TodoCategory.work, color: MyColor.blueGrey, content: '去运动', done: true, time: DateTime.now().subtract(const Duration(days: 3))) ]); } Future<bool> postTodos(List<TodoModel> todos) async { return Future.value(true); } }
可以看到上面设置一个Duration的东西,用来模拟请求网络的一个延迟效果
fetchTodos方法中则是模拟获取数据的
postTodos 是往服务器推待办数据,还是因为后台没写,搁置先,后面有心情在继续更
创建TodosRepositoryFlutter
这个方法是继承todo_repository的方法,并通过上面写的两个方法获得数据,代码如下
// Copyright 2018 The Flutter Architecture Sample Authors. All rights reserved. // Use of this source code is governed by the MIT license that can be found // in the LICENSE file. import 'dart:async'; import 'dart:core'; import 'package:meta/meta.dart'; import 'package:todos_repository/todo_repository_core.dart'; import 'file_storage.dart'; import 'web_client.dart'; class TodosRepositoryFlutter implements TodoRepository { final FileStorage fileStorage; final WebClient webClient; const TodosRepositoryFlutter({ required this.fileStorage, this.webClient = const WebClient(), }); ///首先从文件存储中加载待办事项,如果不存在则通过web端去加载 @override Future<List<TodoModel>> loadTodos() async { try { final todos = await fileStorage.loadTodos(); if(todos.isEmpty){ final todos = await webClient.fetchTodos(); fileStorage.saveTodos(todos); return todos; } return todos; } catch (e) { final todos = await webClient.fetchTodos(); fileStorage.saveTodos(todos); return todos; } } //将TODO持久化到本地磁盘和web @override Future saveTodos(List<TodoModel> todos) { return Future.wait<dynamic>([ fileStorage.saveTodos(todos), webClient.postTodos(todos), ]); } @override Future saveTodo(TodoModel todoModel) { return Future.wait<dynamic>([ fileStorage.saveTodo(todoModel) ]); } }
其实 saveTodo 并不应该写在这里,这个方法最开始想要达到得结果是读取全部数据和添加全部数据,
而像添加新的待办事项和删除等操作是应该写另一个地方,但是这里出于偷懒得原因,直接就写这里了,后续完善后会进行修改
那todo_repository_simple的代码也就暂时告一段落,接下来就是根项目的编写了
回到 flutter_bloc_hive_todo中
首先还是依赖包,依赖我们刚刚写的两个包
todo_repository_simple:
path: packages/todo_repository_simple
todos_repository:
path: packages/todos_repository
在lib中创建两个个文件夹 分别是 blocs 和view
先来看看bloc。在AndroidStudio中安装bloc插件
然后右键创建文件的时候就会出现,方便快速创建初始代码
BLOC
在使用bloc前,我们需要了解到Bloc是个什么玩意,emmm网上的文章很多,说得都非常详细,我就不班门弄斧了,只说说我的理解和感受
Bloc 给我的感觉就是
View 通过 event 去调用bloc的方法 bloc在通过修改state 去修改界面显示的信息,互不干扰,这倒是和java的mvp模式有些异曲同工之处
好了,来看看state的代码
abstract class TodosState extends Equatable { const TodosState(); @override List<Object> get props => []; } /// 待办事项加载中 class TodosLoadInProgress extends TodosState {} /// 待办事项加载完成 class TodosLoadSuccess extends TodosState { /// 待办事项集合 final List<TodoModel> todos; const TodosLoadSuccess({this.todos = const []}); @override List<Object> get props => [todos]; @override String toString() => 'TodosLoadSuccess { todos: $todos }'; } /// 待办事项加载失败 class TodosLoadFailure extends TodosState {} /// 添加待办失败 class TodoAddFailure extends TodosState{}
可以看到除了加载成功的状态里有写方法,其他三个均不用理会,因为它们嫩不会返回数据给到界面,我们仅需知道它是什么状态即可
再来看看 event
abstract class TodosEvent extends Equatable { const TodosEvent(); @override List<Object> get props => []; } /// 待办事项加载中 class TodosLoaded extends TodosEvent {} ///添加一个待办事项 class TodoAdded extends TodosEvent{ final TodoModel todo; const TodoAdded(this.todo); @override List<Object> get props => [todo]; @override String toString() => 'TodoAdded { todo: $todo }'; } /// 更新一个待办事项 class TodoUpdated extends TodosEvent{ final TodoModel todo; const TodoUpdated(this.todo); @override List<Object> get props => [todo]; @override String toString() => 'TodoAdded { todo: $todo }'; } /// 删除一个待办事项 class TodoDeleted extends TodosEvent{ final TodoModel todo; const TodoDeleted(this.todo); @override List<Object> get props => [todo]; @override String toString() => 'TodoDeleted { todo: $todo }'; } /// 清空全部待办事项 class ClearCompleted extends TodosEvent {} /// 选中全部待办事项 class ToggleAll extends TodosEvent {}
另外两个方法没想好怎么做,暂时不写,删除添加等代码其实都一样,都是传入一个 待办对象,应该没什么好说的
所以。直接来看 bloc
bloc:
class TodosBloc extends Bloc<TodosEvent, TodosState> { /// 待办事项方法 final TodosRepositoryFlutter todosRepository; TodosBloc(this.todosRepository) : super(TodosLoadInProgress()) { on<TodosEvent>((event, emit) { // TODO: implement event handler }); on<TodosLoaded>(_onLoaded); on<TodoAdded>(_onAddTodo); } /// 加载待办事项 void _onLoaded(TodosEvent event, Emitter<TodosState> emit) async { try { /// 获得初始默认待办事项 final todos = await todosRepository.loadTodos(); emit(TodosLoadSuccess( todos: todos, )); } catch (e) { emit(TodosLoadFailure()); } } /// 添加待办事项 void _onAddTodo(TodoAdded event, Emitter<TodosState> emit) async { try { if (state is TodosLoadSuccess) { emit(TodosLoadSuccess( todos: List.from((state as TodosLoadSuccess).todos) ..add(event.todo))); _saveTodo(event.todo); } } catch (e) { emit(TodoAddFailure()); } } /// 删除待办事项 void _onDelTodo(TodosEvent event, Emitter<TodosState> emit) async {} /// 修改待办事项 void _onUpdateTodo(TodosEvent event, Emitter<TodosState> emit) async {} /// 将todo事项持久化 Future _saveTodo(TodoModel todo) { return todosRepository.saveTodo(todo); } }
如果有用过bloc的都会发现 没有 mapEventToState 方法,这个方法在以前是必备的,所有的event都是通过它来判断,但是在7.0后,官方已经逐渐弃用 mapEventToState 取而代之的是 on的这种写法,mapEventToState 将会在flutter_bloc 8.0版本彻底移除,所以这也是我写这篇文章的原因
仔细看这行代码,我们在super中定义默认的bloc状态是加载中,你也可以写其他状态,但是 这个状态必须是继承你创建的state,而不能直接继承Equatable
TodosBloc(this.todosRepository) : super(TodosLoadInProgress())
这个 TodosRepositoryFlutter 则是我们刚刚写的插件,我们通过它来获得待办数据
final TodosRepositoryFlutter todosRepository;
在 _onLoaded 我们调用它的 loadTodos 方法,这个方法我们刚才的做法是如果没有本地数据就会获取网络数据,当然只是模拟请求后台
而后通过 emit 去修改state 状态,并将待办数据传给 成功的状态的 待办列表,如果出现异常则返回 TodosLoadFailure
在添加待办事项中,我们是是直接将添加的待办数据添加到 state中,然后才会进行网络很本地存储
View
接下来在view中创建这三个文件
因为我们是显示state中的数据,所以为了查看hive中是否存储成功了,所以就创建一个test的界面测试
那先看最简单的的test,很简单,就是监听hive中有没有数据变化,有变化就修改界面
class TestPage extends StatefulWidget { const TestPage({Key? key}) : super(key: key); @override _TestPageState createState() => _TestPageState(); } class _TestPageState extends State<TestPage> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), body: ValueListenableBuilder<Box<TodoModel>>( valueListenable: Hive.box<TodoModel>('todos').listenable(), builder: (context, box, _) { final todos = box.values.toList(); return ListView.builder(itemBuilder: (context,index){ return Text(todos[index].content!); },itemCount: todos.length,); }), ); } }
todo_editor_dialog是弹窗添加待办的一个弹出界面
代码如下:
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc_hive_todo/blocs/todos/todos_bloc.dart'; import 'package:flutter_material_color_picker/flutter_material_color_picker.dart'; import 'package:hive/hive.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:todos_repository/todo_repository_core.dart'; class TodoEditorDialog extends StatefulWidget { const TodoEditorDialog({Key? key,required this.todo}):super(key: key); final TodoModel todo; @override _TodoEditorDialogState createState() => _TodoEditorDialogState(); } class _TodoEditorDialogState extends State<TodoEditorDialog> { final TodoModel _todo = TodoModel(); @override void initState() { super.initState(); _todo.content = widget.todo.content; _todo.category = widget.todo.category; _todo.time = widget.todo.time; _todo.color = widget.todo.color; _todo.done = widget.todo.done; } @override Widget build(BuildContext context) { return Dialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), child: Padding( padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Align( alignment: Alignment.centerLeft, child: Text( '添加新待办', style: TextStyle(fontSize: 20), ), ), if (widget.todo.isInBox) FloatingActionButton( heroTag: 'remove_todo_button', child: const Icon(MdiIcons.delete), backgroundColor: Colors.red, foregroundColor: Colors.white, mini: true, onPressed: () => _deleteHandle(context)), FloatingActionButton( heroTag: 'add_todo_button', child: const Icon(MdiIcons.contentSave), backgroundColor: Colors.green, foregroundColor: Colors.white, onPressed: () => _saveHandle(context)), ], ), ), const SizedBox(height: 10), TextField( controller: TextEditingController(text: _todo.content), autofocus: true, cursorColor: Colors.deepOrange, onChanged: (value) { _todo.content = value; }, onEditingComplete: () async { // todo : save() }, decoration: const InputDecoration( focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: Colors.deepOrange)), prefixIcon: Icon( MdiIcons.rocketLaunch, color: Colors.deepOrange, ), hintText: 'do...'), ), SingleChildScrollView( scrollDirection: Axis.horizontal, child: MaterialColorPicker( shrinkWrap: true, allowShades: false, circleSize: 32, colors: const [ Colors.red, Colors.orange, Colors.teal, Colors.pink, Colors.blueGrey, Colors.blue, Colors.purple, ], onMainColorChange: (color) { _todo.color = color!.toMyColor(); }, selectedColor: _todo.color!.toColor()), ), CategoryPicker( initialCategory: _todo.category ?? TodoCategory.personal, categoryOnChanged: (category) { _todo.category = category; }, ), ], ), ), ); } Future<void> _saveHandle(BuildContext context) async { _todo.time = DateTime.now(); BlocProvider.of<TodosBloc>(context).add( TodoAdded(_todo), ); // // if (widget.todo.isInBox) { // final key = widget.todo.key; // Hive.box<TodoModel>('todos').put(key, _todo); // } else { // await Hive.box<TodoModel>('todos').add(_todo); // } // Navigator.pop(context); } Future<void> _deleteHandle(BuildContext context) async { if (widget.todo.isInBox) await widget.todo.delete(); Navigator.pop(context); } } class CategoryPicker extends StatefulWidget { const CategoryPicker({Key? key, required this.categoryOnChanged, required this.initialCategory}) : super(key: key); final ValueChanged<TodoCategory> categoryOnChanged; final TodoCategory initialCategory; @override _CategoryPickerState createState() => _CategoryPickerState(); } class _CategoryPickerState extends State<CategoryPicker> { var _isSelected = List.generate(3, (index) => false); @override void initState() { super.initState(); final index = TodoCategory.values.indexOf(widget.initialCategory); _isSelected[index] = true; } @override Widget build(BuildContext context) { return ToggleButtons( fillColor: Colors.white.withOpacity(0.1), onPressed: (index) { setState(() { _isSelected = _isSelected.map((e) => e = false).toList(); _isSelected[index] = true; }); final category = TodoCategory.values[index]; widget.categoryOnChanged(category); }, children: const [ _ToggleButtonContainer( icon: Icon(Icons.person), color: Colors.red, label: '暂定'), _ToggleButtonContainer( icon: Icon(Icons.work), color: Colors.blue, label: '工作'), _ToggleButtonContainer( icon: Icon(MdiIcons.shopping), color: Colors.yellow, label: '购物') ], isSelected: _isSelected); } } class _ToggleButtonContainer extends StatelessWidget { const _ToggleButtonContainer({Key? key, required this.color, required this.icon, required this.label}) : super(key: key); final Color color; final Icon icon; final String label; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 8), child: Row( mainAxisSize: MainAxisSize.min, children: [ IconTheme(data: IconThemeData(color: color), child: icon), const SizedBox( 4), Text( label, style: TextStyle(color: color), ), ], ), ); } }
todo_item.dart则是每一个待办的子项
todo_item:
import 'package:flutter/material.dart'; import 'package:todos_repository/todo_repository_core.dart'; class TodoItem extends StatelessWidget { final DismissDirectionCallback onDismissed; final GestureTapCallback onTap; final ValueChanged<bool> onCheckboxChanged; final TodoModel todo; const TodoItem({ Key? key, required this.onDismissed, required this.onTap, required this.onCheckboxChanged, required this.todo, }) : super(key: key); @override Widget build(BuildContext context) { return Dismissible( onDismissed: onDismissed, key: Key("__TodoItem__"), child: ListTile( onTap: onTap, leading: Checkbox( value: todo.done, onChanged: (bool? value) { }, // onChanged: onCheckboxChanged, ), title: Hero( tag: '${todo.category}__heroTag', child: Container( MediaQuery.of(context).size.width, child: Text( todo.content??"空", style: Theme.of(context).textTheme.headline6, ), ), ), subtitle: Text( "${todo.time}", maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context).textTheme.subtitle1, ), ), ); } }
todo_home就是todo显示的界面了,需要调用todo_item,所以先贴出item的代码
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc_hive_todo/blocs/todos/todos.dart'; import 'package:flutter_bloc_hive_todo/view/test.dart'; import 'package:flutter_bloc_hive_todo/view/todo_editor_dialog.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; import 'package:todos_repository/todo_repository_core.dart'; class TodoHome extends StatefulWidget { const TodoHome({Key? key}) : super(key: key); @override _TodoHomeState createState() => _TodoHomeState(); } class _TodoHomeState extends State<TodoHome> { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text("待办事项"), actions: [ IconButton( onPressed: () { //路由跳转 固定写法 PageA 为目标页面类名 Navigator.of(context) .push(MaterialPageRoute(builder: (context) => TestPage())); }, icon: const Icon(Icons.add)) ], ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: FloatingActionButton( foregroundColor: Colors.white, backgroundColor: Colors.red, heroTag: 'add_todo_button', onPressed: () => showTodoEditorDialog(context), child: const Icon(MdiIcons.rocketLaunch), ), body: BlocBuilder<TodosBloc, TodosState>( builder: (context, state) { if (state is TodosLoadInProgress) { return const Text("加载中"); } else if (state is TodosLoadSuccess) { return CustomScrollView( slivers: [ SliverList( delegate: SliverChildListDelegate([ const SizedBox(height: 10), ...state.todos.map( (todo) => InkWell( onTap: () { showTodoEditorDialog(context, todo: todo); }, child: Slidable( startActionPane: ActionPane( motion: const ScrollMotion(), dismissible: DismissiblePane(onDismissed: () {}), children: [ SlidableAction( onPressed: (BuildContext context) {}, flex: 2, backgroundColor: const Color(0xFFFE4A49), foregroundColor: Colors.white, icon: Icons.delete, label: '删除', ), ], ), endActionPane: ActionPane( motion: const ScrollMotion(), children: [ SlidableAction( flex: 2, onPressed: (BuildContext context) async {}, backgroundColor: const Color(0xFF7BC043), foregroundColor: Colors.white, icon: Icons.radio_button_unchecked, label: '完成', ), ], ), child: Card( elevation: 0, child: SizedBox( height: 60, child: Row( children: [ Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0), child: todo.done! ? Stack( alignment: Alignment.center, children: const [ Icon( MdiIcons.check, color: Colors.amberAccent, size: 18, ), Icon(MdiIcons.circleOutline, color: Colors.red), ], ) : const Icon(MdiIcons.circleOutline, color: Colors.cyan), ), const SizedBox( 10), Expanded( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Opacity( opacity: todo.done! ? 0.4 : 1, child: Text( todo.content ?? "空", style: TextStyle( fontSize: 17, fontWeight: todo.done! ? FontWeight.w100 : FontWeight.normal, decoration: TextDecoration.lineThrough), ), ), const SizedBox(height: 6), Opacity( opacity: 0.4, child: Row( children: const [ Icon( Icons.date_range, size: 12, ), SizedBox( 4), Text("'MM-dd-yyyy HH:mm'", style: TextStyle( fontSize: 12, )) ], ), ) ], ), ), Padding( padding: const EdgeInsets.all(8.0) + const EdgeInsets.symmetric(horizontal: 8), child: const Icon(Icons.person, color: Colors.red), ) ], ), ), ), ), ), ) ].toList())) ], ); } return const Text("加载错误"); }, ), ); } } void showTodoEditorDialog(BuildContext context, {TodoModel? todo}) { final _todo = todo ?? TodoModel( color: MyColor.red, category: TodoCategory.personal, content: '', time: DateTime.now(), done: false); Navigator.push( context, PageRouteBuilder( fullscreenDialog: true, opaque: false, barrierDismissible: true, transitionsBuilder: (context, animation, secondaryAnimation, child) { if (animation.status == AnimationStatus.reverse) { return SlideTransition( position: Tween<Offset>( begin: const Offset(0, 1.0), end: Offset.zero, ).animate(animation), child: child); } else { return SlideTransition( position: Tween<Offset>( begin: const Offset(0, 1.0), end: Offset.zero, ).animate(animation), child: child); } }, pageBuilder: (context, _, __) => TodoEditorDialog(todo: _todo))); }
好了最后一个main就完事了
Main:
import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc_hive_todo/view/todo_home.dart'; import 'package:hive/hive.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:path_provider/path_provider.dart'; import 'package:todo_repository_simple/todo_repository_simple.dart'; import 'package:todos_repository/todo_repository_core.dart'; import 'blocs/todos/todos_bloc.dart'; void main() async{ await _hiveSetup(); runApp( BlocProvider( create: (context) { return TodosBloc( const TodosRepositoryFlutter( fileStorage: FileStorage( '__flutter_bloc_app__', getApplicationDocumentsDirectory, ), ), )..add(TodosLoaded()); }, child: App(), ), ); } class App extends StatelessWidget { const App({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp(title: "待办事项", home: TodoHome()); } } Future<void> _hiveSetup() async { await Hive.initFlutter(); Hive.registerAdapter(TodoCategoryAdapter()); Hive.registerAdapter(MyColorAdapter()); Hive.registerAdapter(TodoModelAdapter()); await Hive.openBox<TodoModel>('todos'); }
嗯,先这样,乏了,看心情更新吧