Instagram Profile Header Layout In Flutter
Solution 1
You can achieve this behaviour using NestedScrollView
with Scaffold
.
As we need the widgets between the AppBar
and TabBar
to be dynamically built and scrolled until TabBar
reaches AppBar
, use the appBar
property of the Scaffold
to build your AppBar
and use headerSliverBuilder
to build other widgets of unknown heights. Use the body
property of NestedScrollView
to build your tab views.
This way the elements of the headerSliverBuilder
would scroll away till the body
reaches the bottom of the AppBar
.
Might be a little confusing to understand with mere words, here is an example for you.
Code:
// InstaProfilePage
class InstaProfilePage extends StatefulWidget {
@override
_InstaProfilePageState createState() => _InstaProfilePageState();
}
class _InstaProfilePageState extends State<InstaProfilePage> {
double get randHeight => Random().nextInt(100).toDouble();
List<Widget> _randomChildren;
// Children with random heights - You can build your widgets of unknown heights here
// I'm just passing the context in case if any widgets built here needs access to context based data like Theme or MediaQuery
List<Widget> _randomHeightWidgets(BuildContext context) {
_randomChildren ??= List.generate(3, (index) {
final height = randHeight.clamp(
50.0,
MediaQuery.of(context).size.width, // simply using MediaQuery to demonstrate usage of context
);
return Container(
color: Colors.primaries[index],
height: height,
child: Text('Random Height Child ${index + 1}'),
);
});
return _randomChildren;
}
@override
Widget build(BuildContext context) {
return Scaffold(
// Persistent AppBar that never scrolls
appBar: AppBar(
title: Text('AppBar'),
elevation: 0.0,
),
body: DefaultTabController(
length: 2,
child: NestedScrollView(
// allows you to build a list of elements that would be scrolled away till the body reached the top
headerSliverBuilder: (context, _) {
return [
SliverList(
delegate: SliverChildListDelegate(
_randomHeightWidgets(context),
),
),
];
},
// You tab view goes here
body: Column(
children: <Widget>[
TabBar(
tabs: [
Tab(text: 'A'),
Tab(text: 'B'),
],
),
Expanded(
child: TabBarView(
children: [
GridView.count(
padding: EdgeInsets.zero,
crossAxisCount: 3,
children: Colors.primaries.map((color) {
return Container(color: color, height: 150.0);
}).toList(),
),
ListView(
padding: EdgeInsets.zero,
children: Colors.primaries.map((color) {
return Container(color: color, height: 150.0);
}).toList(),
)
],
),
),
],
),
),
),
);
}
}
Output:
Hope this helps!
Solution 2
Another solution is that you could use a pinned SliverAppBar
with FlexibleSpaceBar
within DefaultTabController
. Sample codes:
Scaffold(
body: DefaultTabController(
length: 2,
child: NestedScrollView(
headerSliverBuilder: (context, value) {
return [
SliverAppBar(
floating: true,
pinned: true,
bottom: TabBar(
tabs: [
Tab(text: "Posts"),
Tab(text: "Likes"),
],
),
expandedHeight: 450,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.pin,
background: Profile(), // This is where you build the profile part
),
),
];
},
body: TabBarView(
children: [
Container(
child: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return Container(
height: 40,
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('List Item $index'),
);
},
),
),
Container(
child: ListView.builder(
itemCount: 100,
itemBuilder: (context, index) {
return Container(
height: 40,
alignment: Alignment.center,
color: Colors.lightBlue[100 * (index % 9)],
child: Text('List Item $index'),
);
},
),
),
],
),
),
),
),
Before scrolling:
After scrolling:
Comments
-
Ted Henry over 1 year
I've been investigating SliverAppBar, CustomScrollView, NestedScrollView, SliverPersistentHeader, and more. I cannot find a way to build something like the Instagram user profile screen's header where only the tab bar is pinned. The main body of the screen is a TabBarView and each pane has a scrollable list.
With SliverAppBar, it is easy to add the TabBar in the bottom parameter. But I want to have an extra widget of unknown/variable height above that TabBar. The extra widget should scroll out of the way when the page is scrolled and and then the TabBar is what is pinned at the top of the screen.
All I could manage was a fixed content before the tab bar and a fixed tab bar. I cannot get the header to scroll up and stick the
TabBar
at the top just just below theAppBar
.import 'package:flutter/material.dart'; void main() { runApp(MaterialApp(home: MyApp())); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return DefaultTabController( length: 2, child: Scaffold( appBar: AppBar( title: Text("pabloaleko"), ), body: CustomScrollView( physics: const BouncingScrollPhysics(), slivers: <Widget>[ SliverToBoxAdapter( child: SafeArea( child: Text("an unknown\namount of content\n goes here in the header"), ), ), SliverToBoxAdapter( child: TabBar( tabs: [ Tab(child: Text('Days', style: TextStyle(color: Colors.black))), Tab(child: Text('Months', style: TextStyle(color: Colors.black))), ], ), ), SliverFillRemaining( child: TabBarView( children: [ ListView( children: <Widget>[ ListTile(title: Text('Sunday 1')), ListTile(title: Text('Monday 2')), ListTile(title: Text('Tuesday 3')), ListTile(title: Text('Wednesday 4')), ListTile(title: Text('Thursday 5')), ListTile(title: Text('Friday 6')), ListTile(title: Text('Saturday 7')), ListTile(title: Text('Sunday 8')), ListTile(title: Text('Monday 9')), ListTile(title: Text('Tuesday 10')), ListTile(title: Text('Wednesday 11')), ListTile(title: Text('Thursday 12')), ListTile(title: Text('Friday 13')), ListTile(title: Text('Saturday 14')), ], ), ListView( children: <Widget>[ ListTile(title: Text('January')), ListTile(title: Text('February')), ListTile(title: Text('March')), ListTile(title: Text('April')), ListTile(title: Text('May')), ListTile(title: Text('June')), ListTile(title: Text('July')), ListTile(title: Text('August')), ListTile(title: Text('September')), ListTile(title: Text('October')), ListTile(title: Text('November')), ListTile(title: Text('December')), ], ), ], ), ), ], ), ), ); } }
-
Ted Henry over 4 yearsThis is a nice solution.Why are "A" and "B" tab text showing up as white on white in my app but black on white in your app?
-
Hemanth Raj over 4 yearsHey, sorry about that. I had my
Theme
's primary color set toColors.white
. It basically depends on theprimaryColor
andaccentColor
of your .Theme
-
kanwar manraj over 3 yearsHey!! @HemanthRaj I tried this method, but I am stuck with this issue if you please could check it out Bottom overflow due to bottom navigation bar and tab Bar stackoverflow.com/q/65429750/13406343?sem=2
-
daicky777 about 3 yearsHow can you implement swipe refresh for the top NestedScrollView??