Repository: flutter
Issue: Implement TransformToSliverAdaptor
Labels: c: new feature,framework,a: animation,f: scrolling,P3,team-framework,triaged-framework

<!-- Thank you for using Flutter!

     If you are looking for support, please check out our documentation
     or consider asking a question on Stack Overflow:
      * https://flutter.dev/
      * https://api.flutter.dev/
      * https://stackoverflow.com/questions/tagged/flutter?sort=frequent

     If you have found a bug or if our documentation doesn't have an answer
     to what you're looking for, then fill our the template below. Please read
     our guide to filing a bug first: https://flutter.dev/docs/resources/bug-reports
-->

## Use case
I'm building an app that would require completely animating `SliverAppBar` out of view (to the top) based on the `TabController.animation` values as shown below:

<img src="https://user-images.githubusercontent.com/15171489/60546724-94117180-9d26-11e9-8cde-b034f7411954.gif" width=250 />

The following is the implementation I would want to use:
```dart
import 'dart:async';
import 'package:flutter/material.dart';

class AppNavigator extends StatefulWidget {
  const AppNavigator({
    Key key,
    @required this.cameraWidget,
    @required this.chatsWidget,
  }) : super(key: key);

  final Widget cameraWidget;
  final Widget chatsWidget;

  @override
  _AppNavigatorState createState() => _AppNavigatorState();
}

class _AppNavigatorState extends State<AppNavigator>  with SingleTickerProviderStateMixin {

  TabController _tabController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(initialIndex: 1, vsync: this, length: 2);
  }

  @override
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final tabs = <Tab>[Tab(text: "CAMERA"), Tab(text: "CHATS")];
    return Scaffold(
      body: NestedScrollView(
        headerSliverBuilder: (context, inner) {
          return <Widget>[
            AnimatedBuilder(
              animation: _tabController,
              child: SliverAppBar(
                title: const Text('Chat App'),
                pinned: true,
                floating: true,
                bottom: TabBar(
                  tabs: tabs, 
                  controller: _tabController,
                ),
              ),
              builder: (context, child) {
                if (_tabController.animation.value >= 1) { // on the "CHATS" tab
                  return child;
                }
                // on the "CAMERA" tab, I'd want to translate the SliverAppBar
                return Transform.translate(
                  child: child,
                  offset: Offset(0, _tabController.animation.value * -100),
                );
              },
            ),
          ];
        },
        body: TabBarView(
          controller: _tabController,
          children: <Widget>[
            widget.cameraWidget,
            widget.chatsWidget,
          ],
        ),
      ),
    );
  }
}
```

However, Flutter does not allow the use of a widget of type `RenderTransform` to be used in place of a widget of type`RenderSliver` therefore on using `AnimatedBuilder`, I run into the following error:

```
Launching lib/main.dart on Android SDK built for x86 in debug mode...
Built build/app/outputs/apk/debug/app-debug.apk.
I/CameraManagerGlobal(31494): Connecting to camera service
D/EGL_emulation(31494): eglMakeCurrent: 0xe45a9b40: ver 3 0 (tinfo 0xd00c8b70)
I/zygote  (31494): Do partial code cache collection, code=29KB, data=24KB
I/zygote  (31494): After code cache collection, code=29KB, data=24KB
I/zygote  (31494): Increasing code cache capacity to 128KB
I/flutter (31494): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (31494): The following assertion was thrown building AnimatedBuilder(animation: Instance of 'TabController',
I/flutter (31494): state: _AnimatedState#9d909):
I/flutter (31494): A RenderNestedScrollViewViewport expected a child of type RenderSliver but received a child of type
I/flutter (31494): RenderTransform.
I/flutter (31494): RenderObjects expect specific types of children because they coordinate with their children during
I/flutter (31494): layout and paint. For example, a RenderSliver cannot be the child of a RenderBox because a
I/flutter (31494): RenderSliver does not understand the RenderBox layout protocol.
I/flutter (31494):
```

## Expensive Alternative
As of now, I have had to settle for the following implementation which is really expensive as it involves calling `setState` at the root of the app:

```dart
import 'dart:async';

import 'package:flutter/material.dart';

class AppNavigator extends StatefulWidget {
  const AppNavigator({
    Key key,
    @required this.cameraWidget,
    @required this.statusWidget,
  }) : super(key: key);

  final Widget chatsWidget;
  final Widget cameraWidget;

  @override
  _AppNavigatorState createState() => _AppNavigatorState();
}

class _AppNavigatorState extends State<AppNavigator>  with SingleTickerProviderStateMixin {

  bool _pinned = true;
  TabController _tabController;
  ScrollController _scrollController;

  @override
  void initState() {
    super.initState();
    _tabController = TabController(initialIndex: 1, vsync: this, length: 2);
    _tabController.animation.addListener(_handleChangeToCameraTab);
    _scrollController = ScrollController();
  }

  void _handleChangeToCameraTab() {
    final value = _tabController.animation.value;
    setState(() {
      if (value < 1) {
         _scrollController.jumpTo(
           _scrollController.position.maxScrollExtent * (1.0 - value)
         );
      }
      // when animating the SliverAppBar, I don't want the app bar to be pinned
       _pinned = value >= 1.0;
    });
  }

  @override
  void dispose() {
    _tabController.dispose();
    _scrollController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final tabs = <Tab>[Tab(text: "CAMERA"), Tab(text: "CHATS")];

    return Scaffold(
      body: NestedScrollView(
        controller: _scrollController,
        headerSliverBuilder: (context, inner) {
          return <Widget>[
            SliverAppBar(
              title: const Text('Chat App'),
              pinned: _pinned,
              floating: true,
              bottom: TabBar(
                tabs: tabs, 
                controller: _tabController,
              ),
            ),
          ];
        },
        body: TabBarView(
          controller: _tabController,
          children: <Widget>[
            widget.cameraWidget,
            widget.chatsWidget,
          ],
        ),
      ),
    );
  }
}
```
<!--
     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 alternative solutions you've considered. Is there a package
     on pub.dev/flutter that already solves this?
-->

## Proposal
I propose that just as we have `SliverToBoxAdapter`, we also have an implementation of `TransformToSliverAdapter` that would allow the use of animations on Slivers.
<!--
     Briefly but precisely describe what you would like Flutter to be able to do.

     Consider attaching images showing what you are imagining.

     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.
-->