Flutter声明式动画列表插件declarative_animated_list的使用

Flutter声明式动画列表插件 declarative_animated_list 的使用

declarative_animated_list 是一个实现自动更新的动画列表小部件。它基于 Android 的 DiffUtil 进行了轻微的修改,以支持 Flutter 的声明式 UI。

示例动图

基本用法

创建动画列表项

首先,你需要创建一个包含动画效果的列表项。这里我们使用 FadeTransitionSizeTransition 来实现淡入淡出和尺寸变化的效果。

Widget _buildAnimatedTile(Animation<double> animation, PresentationModel model) {
  return FadeTransition(
    opacity: animation,
    child: SizeTransition(
      sizeFactor: animation,
      child: SomeWidget(model),
    ),
  );
}

创建移除动画列表项

同样地,你可以为移除的列表项创建动画效果。

Widget _buildRemovingTile(final Animation<double> animation, final PresentationModel model) { 
  // 可以使用与插入相同的动画效果,或者自定义其他效果
  return FadeTransition(
    opacity: animation,
    child: SizeTransition(
      sizeFactor: animation,
      child: SomeWidget(model),
    ),
  );
}

使用 DeclarativeList

最后,将上述方法集成到 DeclarativeList 中。

final declarativeList = DeclarativeList<PresentationModel>(
  items: presentationModels,
  itemBuilder: (ctx, model, index, animation) => _buildAnimatedTile(animation, model),
  removeBuilder: (ctx, model, index, animation) => _buildRemovingTile(animation, model),
);

完整示例

以下是一个完整的示例,展示了如何在 Flutter 应用中使用 declarative_animated_list 插件来管理待办事项列表。

主文件 main.dart

import 'package:declarative_animated_list/declarative_animated_list.dart';
import 'package:example/bloc.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

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

  @override
  Widget build(BuildContext context) {
    final ToDosBloc bloc = ToDosBloc();
    return MaterialApp(
      title: 'Declarative Animated List Demo',
      theme: ThemeData.dark(),
      home: ToDosPage(
        bloc: bloc,
      ),
    );
  }
}

class ToDosPage extends StatelessWidget {
  final ToDosBloc bloc;

  const ToDosPage({required this.bloc, super.key});

  @override
  Widget build(final BuildContext context) {
    return Scaffold(
      appBar: AppBar(),
      body: StreamBuilder<ToDosState>(
        stream: bloc.toDosState,
        builder: (_, final AsyncSnapshot<ToDosState> snapshot) {
          return _buildBody(context, snapshot);
        },
      ),
      floatingActionButton: _buildFab(context),
    );
  }

  Widget _buildBody(
    BuildContext context,
    AsyncSnapshot<ToDosState> snapshot,
  ) {
    if (snapshot.hasData) {
      return _buildBasedOnState(context, snapshot.data!);
    } else if (snapshot.hasError) {
      return Center(
        child: Text(
          'Error occurred: ${snapshot.error}',
          textAlign: TextAlign.center,
          style: const TextStyle(color: Colors.black),
        ),
      );
    } else {
      return const Center(child: CircularProgressIndicator());
    }
  }

  Widget _buildBasedOnState(
    BuildContext context,
    ToDosState state,
  ) {
    final List<ToDoPresentationModel> toDos = state.toDos.toList()
      ..sort(
        (left, right) {
          if (left.isCompleted == right.isCompleted) {
            return left.description.compareTo(right.description);
          } else {
            return left.isCompleted ? 1 : -1;
          }
        },
      );
    if (toDos.isEmpty) {
      return Center(
        child: Icon(
          Icons.delete_outline,
          size: MediaQuery.of(context).size.height * 0.4,
          color: Theme.of(context).colorScheme.secondary,
        ),
      );
    } else {
      return DeclarativeList<ToDoPresentationModel>(
        items: toDos,
        insertDuration: const Duration(milliseconds: 500),
        removeDuration: const Duration(milliseconds: 500),
        itemBuilder: (_, model, __, anim) => _buildFadeAndSizeTransitioningTile(anim, model),
        removeBuilder: (_, model, __, anim) => _buildFadeAndSizeTransitioningTile(anim, model),
      );
    }
  }

  Widget _buildFadeAndSizeTransitioningTile(
    Animation<double> animation,
    ToDoPresentationModel model,
  ) {
    return FadeTransition(
      opacity: animation,
      child: SizeTransition(sizeFactor: animation, child: _buildTile(model)),
    );
  }

  Widget _buildTile(ToDoPresentationModel toDo) {
    return ListTile(
      onLongPress: () => bloc.removeToDo.add(RemoveToDoEvent(toDo)),
      title: Text(toDo.description),
      leading: IconButton(
        icon: Icon(toDo.isCompleted ? Icons.check : Icons.sync),
        onPressed: () {
          bloc.changeToDoStatus.add(
            ChangeCompletionStatusEvent(
              toDo,
              shouldBeCompleted: !toDo.isCompleted,
            ),
          );
        },
      ),
    );
  }

  Widget _buildFab(BuildContext context) {
    return FloatingActionButton(
      child: const Icon(Icons.add),
      onPressed: () => showDialog<void>(
        context: context,
        builder: (ctx) {
          final controller = TextEditingController();
          final size = MediaQuery.sizeOf(ctx);
          return Container(
            height: size.height * 0.5,
            width: size.width * 0.5,
            padding: const EdgeInsets.all(16.0),
            child: Material(
              child: Column(
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  Padding(
                    padding: const EdgeInsets.all(8.0),
                    child: TextField(controller: controller),
                  ),
                  IconButton(
                    icon: const Icon(Icons.check),
                    onPressed: () {
                      bloc.addToDo.add(AddToDoEvent(controller.text));
                      Navigator.of(ctx).pop();
                    },
                  )
                ],
              ),
            ),
          );
        },
      ),
    );
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
    properties.add(DiagnosticsProperty('bloc', bloc));
  }
}

BLoC 文件 bloc.dart

import 'dart:async';

class ToDosBloc {
  final _toDosController = StreamController<ToDosState>.broadcast();
  Stream<ToDosState> get toDosState => _toDosController.stream;

  final _addToDo = StreamController<AddToDoEvent>.broadcast();
  Sink<AddToDoEvent> get addToDo => _addToDo.sink;

  final _removeToDo = StreamController<RemoveToDoEvent>.broadcast();
  Sink<RemoveToDoEvent> get removeToDo => _removeToDo.sink;

  final _changeToDoStatus = StreamController<ChangeCompletionStatusEvent>.broadcast();
  Sink<ChangeCompletionStatusEvent> get changeToDoStatus => _changeToDoStatus.sink;

  ToDosBloc() {
    _init();
  }

  void _init() {
    _addToDo.stream.listen(_handleAddToDo);
    _removeToDo.stream.listen(_handleRemoveToDo);
    _changeToDoStatus.stream.listen(_handleChangeToDoStatus);

    _toDosController.sink.add(ToDosState([]));
  }

  void _handleAddToDo(AddToDoEvent event) {
    final currentState = _toDosController.valueOrNull ?? ToDosState([]);
    final newToDos = List<ToDoPresentationModel>.from(currentState.toDos)
      ..add(ToDoPresentationModel(event.description, false));
    _toDosController.sink.add(ToDosState(newToDos));
  }

  void _handleRemoveToDo(RemoveToDoEvent event) {
    final currentState = _toDosController.valueOrNull ?? ToDosState([]);
    final newToDos = List<ToDoPresentationModel>.from(currentState.toDos)
      ..remove(event.toDo);
    _toDosController.sink.add(ToDosState(newToDos));
  }

  void _handleChangeToDoStatus(ChangeCompletionStatusEvent event) {
    final currentState = _toDosController.valueOrNull ?? ToDosState([]);
    final newToDos = List<ToDoPresentationModel>.from(currentState.toDos)
      ..[currentState.toDos.indexOf(event.toDo)] = event.toDo.copyWith(isCompleted: event.shouldBeCompleted);
    _toDosController.sink.add(ToDosState(newToDos));
  }

  void dispose() {
    _toDosController.close();
    _addToDo.close();
    _removeToDo.close();
    _changeToDoStatus.close();
  }
}

class AddToDoEvent {
  final String description;

  AddToDoEvent(this.description);
}

class RemoveToDoEvent {
  final ToDoPresentationModel toDo;

  RemoveToDoEvent(this.toDo);
}

class ChangeCompletionStatusEvent {
  final ToDoPresentationModel toDo;
  final bool shouldBeCompleted;

  ChangeCompletionStatusEvent(this.toDo, {required this.shouldBeCompleted});
}

class ToDosState {
  final List<ToDoPresentationModel> toDos;

  ToDosState(this.toDos);
}

class ToDoPresentationModel {
  final String description;
  final bool isCompleted;

  ToDoPresentationModel(this.description, this.isCompleted);

  ToDoPresentationModel copyWith({String? description, bool? isCompleted}) {
    return ToDoPresentationModel(description ?? this.description, isCompleted ?? this.isCompleted);
  }
}

通过以上代码,你可以在 Flutter 应用中使用 declarative_animated_list 插件来创建带有动画效果的待办事项列表。希望这个示例对你有所帮助!


更多关于Flutter声明式动画列表插件declarative_animated_list的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter声明式动画列表插件declarative_animated_list的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,declarative_animated_list 是一个用于在 Flutter 中实现声明式动画列表的插件。这个插件允许你以一种简洁且声明式的方式创建带有动画效果的列表。以下是一个简单的代码示例,展示如何使用 declarative_animated_list 插件来创建一个带有动画效果的列表。

首先,确保你的 Flutter 项目中已经添加了 declarative_animated_list 插件。在你的 pubspec.yaml 文件中添加以下依赖:

dependencies:
  flutter:
    sdk: flutter
  declarative_animated_list: ^x.y.z  # 替换为最新版本号

然后,运行 flutter pub get 来获取依赖。

接下来,在你的 Dart 文件中,你可以按照以下方式使用 DeclarativeAnimatedList

import 'package:flutter/material.dart';
import 'package:declarative_animated_list/declarative_animated_list.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Declarative Animated List Example'),
        ),
        body: AnimatedListExample(),
      ),
    );
  }
}

class AnimatedListExample extends StatefulWidget {
  @override
  _AnimatedListExampleState createState() => _AnimatedListExampleState();
}

class _AnimatedListExampleState extends State<AnimatedListExample> {
  final List<String> items = List.generate(20, (i) => "Item ${i + 1}");
  final GlobalKey<DeclarativeAnimatedListState> listKey = GlobalKey<DeclarativeAnimatedListState>();

  void _addItem() {
    setState(() {
      items.add("Item ${items.length + 1}");
    });
  }

  void _removeItem(int index) {
    setState(() {
      items.removeAt(index);
    });
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Column(
        children: [
          ElevatedButton(
            onPressed: _addItem,
            child: Text('Add Item'),
          ),
          Expanded(
            child: DeclarativeAnimatedList<String>(
              key: listKey,
              items: items,
              itemBuilder: (context, index, item) {
                return ListTile(
                  title: Text(item),
                  trailing: IconButton(
                    icon: Icon(Icons.delete),
                    onPressed: () => _removeItem(index),
                  ),
                );
              },
              itemAnimationBuilder: (context, index, animation) {
                return SlideTransition(
                  position: Tween<Offset>(
                    begin: Offset(1.0, 0.0),
                    end: Offset(0.0, 0.0),
                  ).animate(animation),
                  child: FadeTransition(
                    opacity: Tween<double>(
                      begin: 0.0,
                      end: 1.0,
                    ).animate(animation),
                    child: Padding(
                      padding: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0),
                      child: Divider(
                        height: 0,
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

代码解释

  1. 依赖导入

    • import 'package:flutter/material.dart'; 导入 Flutter 的基础组件。
    • import 'package:declarative_animated_list/declarative_animated_list.dart'; 导入 declarative_animated_list 插件。
  2. 主应用

    • MyApp 类是应用的主入口,包含一个 Scaffold,其主体是 AnimatedListExample 组件。
  3. 动画列表

    • AnimatedListExample 是一个 StatefulWidget,包含一个列表 items 和一个 GlobalKey 用于控制列表的动画状态。
    • _addItem 方法用于向列表中添加新项。
    • _removeItem 方法用于从列表中移除指定索引的项。
  4. 列表构建

    • DeclarativeAnimatedList 组件用于创建带有动画效果的列表。
    • items 属性指定列表的数据源。
    • itemBuilder 属性用于构建列表项。
    • itemAnimationBuilder 属性用于定义列表项的动画效果。在这个例子中,我们使用了 SlideTransitionFadeTransition 来实现滑动和淡入淡出的动画效果。

这个示例展示了如何使用 declarative_animated_list 插件来创建一个带有基本动画效果的列表。你可以根据需求进一步自定义动画效果和列表项的外观。

回到顶部