Skip to content

Instantly share code, notes, and snippets.

@liudonghua123
Last active April 25, 2020 14:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save liudonghua123/b6c999910fe150e85956177514de90ce to your computer and use it in GitHub Desktop.
Save liudonghua123/b6c999910fe150e85956177514de90ce to your computer and use it in GitHub Desktop.
import 'package:flutter/material.dart';
import 'dart:math' as math;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: SamplePage(),
),
);
}
}
class SamplePage extends StatefulWidget {
SamplePage({Key key}) : super(key: key);
@override
_SamplePageState createState() => _SamplePageState();
}
class _SamplePageState extends State<SamplePage>
with SingleTickerProviderStateMixin {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
actions: <Widget>[
Material(
color: Colors.transparent,
child: InkWell(
onTap: () {},
child: IconButton(
icon: Icon(Icons.access_alarm),
onPressed: () {},
),
),
),
],
title: Text('SliverAppBar'),
backgroundColor: Theme.of(context).accentColor.withOpacity(0.5),
expandedHeight: 200.0,
flexibleSpace: FlexibleSpaceBar(
background: Container(
child: FittedBox(
fit: BoxFit.contain, child: Text('SliverAppBar.flexibleSpace')),
color: Colors.purple,
),
),
bottom: PreferredSize(
child: Container(
width: MediaQuery.of(context).size.width,
height: 50,
color: Colors.red.withOpacity(0.3),
child: FittedBox(fit: BoxFit.contain, child: Text('SliverAppBar.bottom')),
),
preferredSize: Size.fromHeight(50),
),
// floating: floating,
// snap: snap,
pinned: true,
),
SliverGrid.extent(
children: List.generate(30, (int index) {
return Card(
elevation: 5,
child: Center(child: Text('item ${index.toString()}')),
);
}),
maxCrossAxisExtent: 100.0,
),
SliverPersistentHeader(
pinned: false,
floating: true,
delegate: _SliverAppBarDelegate(
minHeight: 60.0,
maxHeight: 180.0,
child: Container(
child: FittedBox(fit: BoxFit.contain, child: Text('SliverPersistentHeader')),
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.green, Colors.orange],
),
),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
if (index > 30) {
return null;
}
return ListTile(
title: Text('item ${index.toString()}'),
onTap: () {},
);
},
),
// delegate: SliverChildListDelegate(
// List.generate(30, (int index) {
// return ListTile(title: Text('item ${index.toString()}'),onTap: (){},);
// }),
// ),
),
],
),
);
}
}
class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
_SliverAppBarDelegate({
@required this.minHeight,
@required this.maxHeight,
@required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
@override
double get minExtent => minHeight;
@override
double get maxExtent => math.max(maxHeight, minHeight);
@override
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
return new SizedBox.expand(child: child);
}
@override
bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
@liudonghua123
Copy link
Author

@liudonghua123
Copy link
Author

https://api.flutter.dev/flutter/widgets/NestedScrollView-class.html
try to fix pin issue https://juejin.im/post/5bea43ade51d45544844010a

NOT FINISHED!

import 'package:flutter/material.dart';
import 'dart:math' as math;

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: SamplePage(),
      ),
    );
  }
}

class SamplePage extends StatefulWidget {
  SamplePage({Key key}) : super(key: key);

  @override
  _SamplePageState createState() => _SamplePageState();
}

class _SamplePageState extends State<SamplePage>
    with SingleTickerProviderStateMixin {
  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: <Widget>[
          SliverOverlapAbsorber(
            handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
            sliver: SliverAppBar(
              actions: <Widget>[
                Material(
                  color: Colors.transparent,
                  child: InkWell(
                    onTap: () {},
                    child: IconButton(
                      icon: Icon(Icons.access_alarm),
                      onPressed: () {},
                    ),
                  ),
                ),
              ],
              title: Text('SliverAppBar'),
              backgroundColor: Theme.of(context).accentColor.withOpacity(0.5),
              expandedHeight: 200.0,
              flexibleSpace: FlexibleSpaceBar(
                background: Container(
                  child: FittedBox(
                      fit: BoxFit.contain,
                      child: Text('SliverAppBar.flexibleSpace')),
                  color: Colors.purple,
                ),
              ),
              bottom: PreferredSize(
                child: Container(
                  width: MediaQuery.of(context).size.width,
                  height: 50,
                  color: Colors.red.withOpacity(0.3),
                  child: FittedBox(
                      fit: BoxFit.contain, child: Text('SliverAppBar.bottom')),
                ),
                preferredSize: Size.fromHeight(50),
              ),
              // floating: floating,
              // snap: snap,
              pinned: true,
            ),
          ),
          SliverOverlapInjector(
            // This is the flip side of the SliverOverlapAbsorber above.
            handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
          ),
          SliverGrid.extent(
            children: List.generate(30, (int index) {
              return Card(
                elevation: 5,
                child: Center(child: Text('item ${index.toString()}')),
              );
            }),
            maxCrossAxisExtent: 100.0,
          ),
          SliverPersistentHeader(
            pinned: false,
            floating: true,
            delegate: _SliverAppBarDelegate(
              minHeight: 60.0,
              maxHeight: 180.0,
              child: Container(
                child: FittedBox(
                    fit: BoxFit.contain, child: Text('SliverPersistentHeader')),
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [Colors.green, Colors.orange],
                  ),
                ),
              ),
            ),
          ),
          SliverList(
            delegate: SliverChildBuilderDelegate(
              (BuildContext context, int index) {
                if (index > 30) {
                  return null;
                }
                return ListTile(
                  title: Text('item ${index.toString()}'),
                  onTap: () {},
                );
              },
            ),
            // delegate: SliverChildListDelegate(
            //   List.generate(30, (int index) {
            //     return ListTile(title: Text('item ${index.toString()}'),onTap: (){},);
            //   }),
            // ),
          ),
        ],
      ),
    );
  }
}

class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate {
  _SliverAppBarDelegate({
    @required this.minHeight,
    @required this.maxHeight,
    @required this.child,
  });

  final double minHeight;
  final double maxHeight;
  final Widget child;

  @override
  double get minExtent => minHeight;

  @override
  double get maxExtent => math.max(maxHeight, minHeight);

  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    return new SizedBox.expand(child: child);
  }

  @override
  bool shouldRebuild(_SliverAppBarDelegate oldDelegate) {
    return maxHeight != oldDelegate.maxHeight ||
        minHeight != oldDelegate.minHeight ||
        child != oldDelegate.child;
  }
}

@liudonghua123
Copy link
Author

problem demo on https://juejin.im/post/5bea43ade51d45544844010a

import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(
    home: new SamplePage(),
  ));
}

class SamplePage extends StatefulWidget {
  @override
  _SamplePageState createState() => _SamplePageState();
}

class _SamplePageState extends State<SamplePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(body: getBody());
  }

  Widget getBody() {
    return new NestedScrollView(
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          var widgets = <Widget>[
            new SliverOverlapAbsorber(
              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
              child: SliverAppBar(
                actions: <Widget>[
                  Material(
                    color: Colors.transparent,
                    child: InkWell(
                      onTap: () {},
                      child: IconButton(
                        icon: Icon(Icons.access_alarm),
                        onPressed: () {},
                      ),
                    ),
                  ),
                ],
                title: Text('SliverAppBar'),
                backgroundColor: Theme.of(context).accentColor.withOpacity(0.5),
                expandedHeight: 200.0,
                flexibleSpace: FlexibleSpaceBar(
                  background: Container(
                    child: FittedBox(
                        fit: BoxFit.contain,
                        child: Text('SliverAppBar.flexibleSpace')),
                    color: Colors.purple,
                  ),
                ),
                bottom: PreferredSize(
                  child: Container(
                    width: MediaQuery.of(context).size.width,
                    height: 50,
                    color: Colors.red.withOpacity(0.3),
                    child: FittedBox(
                        fit: BoxFit.contain,
                        child: Text('SliverAppBar.bottom')),
                  ),
                  preferredSize: Size.fromHeight(50),
                ),
                // floating: floating,
                // snap: snap,
                pinned: true,
                forceElevated: innerBoxIsScrolled,
              ),
            ),

//            new SliverOverlapAbsorber(
//                handle:
//                    NestedScrollView.sliverOverlapAbsorberHandleFor(context),
//                child:
            SliverPersistentHeader(
                pinned: true,
                floating: false,
                delegate: _SliverPersistentHeaderDelegate(
                    Container(
                        color: Color.fromARGB(100, 100, 100, 100),
                        height: 100.0,
                        child: Center(
                          child: Text("Pinned wiget"),
                        )),
                    100.0))
            // ),
          ];

          return widgets;
        },
        body: SafeArea(
            top: false,
            bottom: false,
            child: new Builder(
                // This Builder is needed to provide a BuildContext that is "inside"
                // the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
                // find the NestedScrollView.
                builder: (BuildContext context) {
              return new CustomScrollView(
                // The "controller" and "primary" members should be left
                // unset, so that the NestedScrollView can control this
                // inner scroll view.
                // If the "controller" property is set, then this scroll
                // view will not be associated with the NestedScrollView.
                // The PageStorageKey should be unique to this ScrollView;
                // it allows the list to remember its scroll position when
                // the tab view is not on the screen.
//                key: new PageStorageKey<String>("KeyName$name"),
                slivers: <Widget>[
                  new SliverOverlapInjector(
                    // This is the flip side of the SliverOverlapAbsorber above.
                    handle: NestedScrollView.sliverOverlapAbsorberHandleFor(
                        context),
                  ),
                  new SliverPadding(
                    padding: const EdgeInsets.all(8.0),
                    // In this example, the inner scroll view has
                    // fixed-height list items, hence the use of
                    // SliverFixedExtentList. However, one could use any
                    // sliver widget here, e.g. SliverList or SliverGrid.
                    sliver: new SliverFixedExtentList(
                      // The items in this example are fixed to 48 pixels
                      // high. This matches the Material Design spec for
                      // ListTile widgets.
                      itemExtent: 48.0,
                      delegate: new SliverChildBuilderDelegate(
                        (BuildContext context, int index) {
                          // This builder is called for each child.
                          // In this example, we just number each list item.
                          return Container(
                            color: Colors.red,
                            height: 48.0,
                            width: double.infinity,
                            child: Text("Index$index"),
                          );
                        },
                        // The childCount of the SliverChildBuilderDelegate
                        // specifies how many children this inner list
                        // has. In this example, each tab has a list of
                        // exactly 30 items, but this is arbitrary.
                        childCount: 2,
                      ),
                    ),
                  ),
                ],
              );
            })));
  }
}

class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double height;
  final Widget widget;
  _SliverPersistentHeaderDelegate(this.widget, this.height);
  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    // TODO: implement build
    return widget;
  }

  // TODO: implement maxExtent
  @override
  double get maxExtent => height;

  // TODO: implement minExtent
  @override
  double get minExtent => height;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    // TODO: implement shouldRebuild
    return false;
  }
}

@liudonghua123
Copy link
Author

More clear explanation code.

import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart' hide NestedScrollView;

void main() {
  runApp(MaterialApp(
    home: SamplePage(),
  ));
}

class SamplePage extends StatefulWidget {
  @override
  _SamplePageState createState() => _SamplePageState();
}

class _SamplePageState extends State<SamplePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(body: getBody());
  }

  Widget getBody() {
    return NestedScrollView(
        pinnedHeaderSliverHeightBuilder: () {
          return MediaQuery.of(context).padding.top +
              AppBar().preferredSize.height +
              // bottom
              50 +
              // pinned widget
              100;
        },
        headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
          var widgets = <Widget>[
            SliverOverlapAbsorber(
              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
              child: SliverAppBar(
                actions: <Widget>[
                  Material(
                    color: Colors.transparent,
                    child: InkWell(
                      onTap: () {},
                      child: IconButton(
                        icon: Icon(Icons.access_alarm),
                        onPressed: () {},
                      ),
                    ),
                  ),
                ],
                title: Text('SliverAppBar'),
                backgroundColor: Theme.of(context).accentColor.withOpacity(0.5),
                expandedHeight: 200.0,
                flexibleSpace: FlexibleSpaceBar(
                  background: Container(
                    child: FittedBox(
                        fit: BoxFit.contain,
                        child: Text('SliverAppBar.flexibleSpace')),
                    color: Colors.purple,
                  ),
                ),
                bottom: PreferredSize(
                  child: Container(
                    width: MediaQuery.of(context).size.width,
                    height: 50,
                    color: Colors.red.withOpacity(0.3),
                    child: FittedBox(
                        fit: BoxFit.contain,
                        child: Text('SliverAppBar.bottom')),
                  ),
                  preferredSize: Size.fromHeight(50),
                ),
                // floating: floating,
                // snap: snap,
                pinned: true,
                forceElevated: innerBoxIsScrolled,
              ),
            ),
            SliverOverlapInjector(
              handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
            ),
            SliverPersistentHeader(
              pinned: true,
              delegate: _SliverPersistentHeaderDelegate(
                  Container(
                      color: Color.fromARGB(100, 100, 100, 100),
                      height: 100.0,
                      child: Center(
                        child: Text("Pinned wiget"),
                      )),
                  100.0),
            ),
            // ),
          ];

          return widgets;
        },
        body: SafeArea(
            top: false,
            bottom: false,
            child: Builder(
                // This Builder is needed to provide a BuildContext that is "inside"
                // the NestedScrollView, so that sliverOverlapAbsorberHandleFor() can
                // find the NestedScrollView.
                builder: (BuildContext context) {
              return CustomScrollView(
                // The "controller" and "primary" members should be left
                // unset, so that the NestedScrollView can control this
                // inner scroll view.
                // If the "controller" property is set, then this scroll
                // view will not be associated with the NestedScrollView.
                // The PageStorageKey should be unique to this ScrollView;
                // it allows the list to remember its scroll position when
                // the tab view is not on the screen.
//                key:  PageStorageKey<String>("KeyName$name"),
                slivers: <Widget>[
                  SliverList(
                    delegate: SliverChildBuilderDelegate(
                      (BuildContext context, int index) {
                        if (index > 5) {
                          return null;
                        }
                        return ListTile(
                          title: Text('item ${index.toString()}'),
                          onTap: () {},
                        );
                      },
                    ),
                  ),
                  SliverPersistentHeader(
                    pinned: true,
                    delegate: _SliverPersistentHeaderDelegate(
                        Container(
                            color: Color.fromARGB(100, 100, 100, 100),
                            height: 100.0,
                            child: Center(
                              child: Text("body Pinned wiget 1"),
                            )),
                        100.0),
                  ),
                  SliverList(
                    delegate: SliverChildBuilderDelegate(
                      (BuildContext context, int index) {
                        if (index > 5) {
                          return null;
                        }
                        return ListTile(
                          title: Text('item ${index.toString()}'),
                          onTap: () {},
                        );
                      },
                    ),
                  ),
                  SliverPersistentHeader(
                    pinned: true,
                    delegate: _SliverPersistentHeaderDelegate(
                        Container(
                            color: Color.fromARGB(100, 100, 100, 100),
                            height: 100.0,
                            child: Center(
                              child: Text("body Pinned wiget 2"),
                            )),
                        100.0),
                  ),
                  SliverList(
                    delegate: SliverChildBuilderDelegate(
                      (BuildContext context, int index) {
                        if (index > 15) {
                          return null;
                        }
                        return ListTile(
                          title: Text('item ${index.toString()}'),
                          onTap: () {},
                        );
                      },
                    ),
                  ),
                ],
              );
            })));
  }
}

class _SliverPersistentHeaderDelegate extends SliverPersistentHeaderDelegate {
  final double height;
  final Widget widget;
  _SliverPersistentHeaderDelegate(this.widget, this.height);
  @override
  Widget build(
      BuildContext context, double shrinkOffset, bool overlapsContent) {
    // TODO: implement build
    return widget;
  }

  // TODO: implement maxExtent
  @override
  double get maxExtent => height;

  // TODO: implement minExtent
  @override
  double get minExtent => height;

  @override
  bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) {
    // TODO: implement shouldRebuild
    return false;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment