flutter 为什么我的动画有时会跳过步骤?

laawzig2  于 2023-02-09  发布在  Flutter
关注(0)|答案(1)|浏览(152)

我正在尝试动画一个CustomPainter小部件的属性,该小部件从Riverpod通知程序提供的项目中获取其值。
在我的真实的应用程序的这个分解示例中,我通过更改第二个项目的数据来触发通知程序,然后该项目将调整ListTile前面的圆圈的大小。
它似乎适用于值增加的更改,但当值减小时,它通常会跳过动画的某些部分。
我不确定我是不是在这里做整个动画部分。
代码也在Dartpad上:https://dartpad.dev/?id=e3916b47603988efabd7a08712b98287

// ignore_for_file: avoid_print
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(
    const ProviderScope(
      child: MyApp(),
    ),
  );
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Riverpod + animated CustomPainter',
      home: const Example3(),
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.orange,
          brightness: MediaQueryData.fromWindow(WidgetsBinding.instance.window).platformBrightness,
          surface: Colors.deepOrange[600],
        ),
      ),
    );
  }
}

class ItemPainter extends CustomPainter {
  final double value;
  ItemPainter(this.value);

  final itemPaint = Paint()..color = Colors.orange;

  @override
  void paint(Canvas canvas, Size size) {
    // draw a circle with a size depending on the value
    double radius = size.width / 10 * value / 2;
    canvas.drawCircle(
      Offset(
        size.width / 2,
        size.height / 2,
      ),
      radius,
      itemPaint,
    );
  }

  @override
  bool shouldRepaint(covariant ItemPainter oldDelegate) => oldDelegate.value != value;
}

CustomPaint itemIcon(double value) {
  return CustomPaint(
    painter: ItemPainter(value),
    size: const Size(40, 40),
  );
}

@immutable
class Item {
  const Item({required this.id, required this.value});
  final String id;
  final double value;
}

// notifier that provides a list of items
class ItemsNotifier extends Notifier<List<Item>> {
  @override
  List<Item> build() {
    return [
      const Item(id: 'A', value: 1.0),
      const Item(id: 'B', value: 5.0),
      const Item(id: 'C', value: 10.0),
    ];
  }

  void randomize(String id) {
    // replace the state with a new list of items where the value is randomized from 0.0 to 10.0
    state = [
      for (final item in state)
        if (item.id == id) Item(id: item.id, value: Random().nextInt(100).toDouble() / 10.0) else item,
    ];
  }
}

class AnimatedItem extends StatefulWidget {
  final Item item;

  const AnimatedItem(this.item, {super.key});

  @override
  State<AnimatedItem> createState() => _AnimatedItemState();
}

class _AnimatedItemState extends State<AnimatedItem> with SingleTickerProviderStateMixin {
  late final AnimationController _animationController;
  late Animation<double> animation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      value: widget.item.value,
      vsync: this,
      duration: const Duration(milliseconds: 3000),
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(AnimatedItem oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.item.value != widget.item.value) {
      print('didUpdateWidget: ${oldWidget.item.value} -> ${widget.item.value}');
      _animationController.value = oldWidget.item.value / 10;
      _animationController.animateTo(widget.item.value / 10);
    }
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animationController,
      builder: (context, child) {
        return itemIcon((widget.item.value * _animationController.value));
      },
    );
  }
}

final itemsProvider = NotifierProvider<ItemsNotifier, List<Item>>(() => ItemsNotifier());

class Example3 extends ConsumerWidget {
  const Example3({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final items = ref.watch(itemsProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Animated CustomPainter Problem'),
      ),
      // iterate over the item list in ItemsNotifier
      body: ListView.separated(
        separatorBuilder: (context, index) => const Divider(),
        itemCount: items.length,
        itemBuilder: (context, index) {
          final item = items.elementAt(index);
          return ListTile(
            key: Key(item.id),
            leading: AnimatedItem(item),
            title: Text('${item.value}'),
          );
        },
      ),
      floatingActionButton: Row(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () {
              ref.read(itemsProvider.notifier).randomize('B'); // randomize the value of the second item
            },
            child: const Icon(Icons.change_circle),
          ),
        ],
      ),
    );
  }
}
6mzjoqzu

6mzjoqzu1#

你的问题完全出在你对AnimationController的实现上。我真的不明白你对原始代码的意图,但是它跳跃的原因是因为你在build函数中执行widget.item.value * _animationController.value。当你更新你的项目的值时,它突然改变了widget.item.value,创建了跳跃,然后用AnimationController动画了一个小的改变。
此代码将工作:

class _AnimatedItemState extends State<AnimatedItem> with SingleTickerProviderStateMixin {
  late final AnimationController _animationController;
  late Animation<double> animation;

  @override
  void initState() {
    super.initState();
    _animationController = AnimationController(
      value: widget.item.value,
      vsync: this,
      duration: const Duration(milliseconds: 3000),
      lowerBound: 0.0,
      upperBound: 10.0
    );
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(AnimatedItem oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.item.value != widget.item.value) {
      print('didUpdateWidget: ${oldWidget.item.value} -> ${widget.item.value}');
      _animationController.animateTo(widget.item.value);
    }
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animationController,
      builder: (context, child) {
        return itemIcon(_animationController.value);
      },
    );
  }
}

这段代码调整了AnimationController的边界,以适应您想要动画的值的范围,并且只在build中使用_animationController.value。我还从didUpdateWidget中删除了一个冗余行,但这对功能没有影响。

相关问题