【问题标题】:Why are my animations skipping steps sometimes?为什么我的动画有时会跳过步骤?
【发布时间】:2023-02-06 00:46:08
【问题描述】:

我正在尝试为 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),
          ),
        ],
      ),
    );
  }
}

【问题讨论】:

    标签: flutter dart


    【解决方案1】:

    您的问题完全在于您对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 中删除了一条冗余行,但这对功能没有影响。

    【讨论】:

      猜你喜欢
      • 2011-01-01
      • 1970-01-01
      • 2013-12-26
      • 2012-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多