Repository: flutter
Issue: [Widgets] Add insert animation to AnimatedList
Labels: c: new feature,framework,f: material design,c: proposal,team-design,triaged-design

## Use case

I encountered a problem with the animated list while coding. I was wondering why it has a required remove animation but not even an optional insert animation. It would be quite useful to animate items in that particular list differently. (e.g. animation to fill the list with many items different than single insertions)


<!--
     Please tell us the problem you are running into that led to you wanting
     a new feature.

     Is your feature request related to a problem? Please give a clear and
     concise description of what the problem is.

     Describe the alternative solutions you've considered. Is there a package
     on pub.dev/flutter that already solves this?
-->

## Proposal

Change the insertion method to provide a optional insert animation.

<!--
     Briefly but precisely describe what you would like Flutter to be able to do.

     Consider attaching something showing what you are imagining:
      * images
      * videos
      * code samples

     Does this have to be provided by Flutter directly, or can it be provided
     by a package on pub.dev/flutter? If so, maybe consider implementing and
     publishing such a package rather than filing a bug.
-->

#### Proposed changes:

`packages/flutter/lib/src/widgets/animated_list.dart`

Line 21:
```dart
/// Signature for the builder callback used by [AnimatedListState.insertItem].
typedef AnimatedListInsertedItemBuilder = Widget Function(BuildContext context, Animation<double> animation);
```

Line 26:
```dart
  _ActiveItem.incoming(this.controller, this.itemIndex, this.insertedItemBuilder)
    : removedItemBuilder = null;
  _ActiveItem.outgoing(this.controller, this.itemIndex, this.removedItemBuilder)
    : insertedItemBuilder= null;
  _ActiveItem.index(this.itemIndex)
    : controller = null,
      removedItemBuilder = null,
      insertedItemBuilder = null;

  final AnimationController? controller;
  final AnimatedListRemovedItemBuilder? removedItemBuilder;
  final AnimatedListInsertedItemBuilder? insertItemBuilder;
```

Line 274:
```dart
  void insertItem(int index, { Duration duration = _kDuration, AnimatedListInsertedItemBuilder? builder }) {
    _sliverAnimatedListKey.currentState!.insertItem(index, duration: duration, builder: builder);
  }
```

Line 519 (519 & 543-544):
```dart
  void insertItem(int index, { Duration duration = _kDuration, AnimatedListInsertedItemBuilder? builder }) {
    assert(index != null && index >= 0);
    assert(duration != null);

    final int itemIndex = _indexToItemIndex(index);
    assert(itemIndex >= 0 && itemIndex <= _itemsCount);

    // Increment the incoming and outgoing item indices to account
    // for the insertion.
    for (final _ActiveItem item in _incomingItems) {
      if (item.itemIndex >= itemIndex)
        item.itemIndex += 1;
    }
    for (final _ActiveItem item in _outgoingItems) {
      if (item.itemIndex >= itemIndex)
        item.itemIndex += 1;
    }

    final AnimationController controller = AnimationController(
      duration: duration,
      vsync: this,
    );
    final _ActiveItem incomingItem = _ActiveItem.incoming(
      controller,
      itemIndex,
      builder, // <--
    );
    setState(() {
      _incomingItems
        ..add(incomingItem)
        ..sort();
      _itemsCount += 1;
    });

    controller.forward().then<void>((_) {
      _removeActiveItemAt(_incomingItems, incomingItem.itemIndex)!.controller!.dispose();
    });
  }
```

Line 605 (615-616):
```dart
  Widget _itemBuilder(BuildContext context, int itemIndex) {
    final _ActiveItem? outgoingItem = _activeItemAt(_outgoingItems, itemIndex);
    if (outgoingItem != null) {
      return outgoingItem.removedItemBuilder!(
        context,
        outgoingItem.controller!.view,
      );
    }

    final _ActiveItem? incomingItem = _activeItemAt(_incomingItems, itemIndex);
    final Animation<double> animation = incomingItem?.controller?.view ?? kAlwaysCompleteAnimation;
    if (incomingItem.insertedItemBuilder != null) { //  <--
      return incomingItem.insertedItemBuilder(
        context,
        animation,
      );
    }
    return widget.itemBuilder(
      context,
      _itemIndexToIndex(itemIndex),
      animation,
    );
  }
```