Flutter - Custom sliver app bar with search bar
3,289
I've made a simple example to show the main logic.
Create your own SliverPersistentHeaderDelegate
and calculate shrinkFactor.
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white70,
body: CustomScrollView(
slivers: [
SliverPersistentHeader(
pinned: true,
floating: false,
delegate: SearchHeader(
icon: Icons.terrain,
title: 'Trees',
search: _Search(),
),
),
SliverFillRemaining(
hasScrollBody: true,
child: ListView(
physics: NeverScrollableScrollPhysics(),
children: [
Text('some text'),
Placeholder(
color: Colors.red,
fallbackHeight: 200,
),
Container(
color: Colors.blueGrey,
height: 500,
)
],
),
)
],
),
);
}
}
class _Search extends StatefulWidget {
_Search({Key key}) : super(key: key);
@override
__SearchState createState() => __SearchState();
}
class __SearchState extends State<_Search> {
TextEditingController _editingController;
@override
void initState() {
super.initState();
_editingController = TextEditingController();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 20, right: 5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: TextField(
controller: _editingController,
// textAlignVertical: TextAlignVertical.center,
onChanged: (_) => setState(() {}),
decoration: InputDecoration(
hintText: 'Search',
hintStyle: TextStyle(
color: Theme.of(context).primaryColor.withOpacity(0.5)),
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
),
),
),
_editingController.text.trim().isEmpty
? IconButton(
icon: Icon(Icons.search,
color: Theme.of(context).primaryColor.withOpacity(0.5)),
onPressed: null)
: IconButton(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
icon: Icon(Icons.clear,
color: Theme.of(context).primaryColor.withOpacity(0.5)),
onPressed: () => setState(
() {
_editingController.clear();
},
),
),
],
),
);
}
}
class SearchHeader extends SliverPersistentHeaderDelegate {
final double minTopBarHeight = 100;
final double maxTopBarHeight = 200;
final String title;
final IconData icon;
final Widget search;
SearchHeader({
@required this.title,
this.icon,
this.search,
});
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent,
) {
var shrinkFactor = min(1, shrinkOffset / (maxExtent - minExtent));
var topBar = Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
alignment: Alignment.center,
height:
max(maxTopBarHeight * (1 - shrinkFactor * 1.45), minTopBarHeight),
width: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(title,
style: Theme.of(context).textTheme.headline4.copyWith(
color: Colors.white, fontWeight: FontWeight.bold)),
SizedBox(
width: 20,
),
Icon(
icon,
size: 40,
color: Colors.white,
)
],
),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(36),
bottomRight: Radius.circular(36),
)),
),
);
return Container(
height: max(maxExtent - shrinkOffset, minExtent),
child: Stack(
fit: StackFit.loose,
children: [
if (shrinkFactor <= 0.5) topBar,
Align(
alignment: Alignment.bottomCenter,
child: Padding(
padding: EdgeInsets.only(
bottom: 10,
),
child: Container(
alignment: Alignment.center,
child: search,
width: 200,
height: 50,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.white,
boxShadow: [
BoxShadow(
offset: Offset(0, 10),
blurRadius: 10,
color: Colors.green.withOpacity(0.23),
)
]),
),
),
),
if (shrinkFactor > 0.5) topBar,
],
),
);
}
@override
double get maxExtent => 230;
@override
double get minExtent => 100;
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
}
Author by
Simon B
Updated on December 23, 2022Comments
-
Simon B over 1 year
I want to have a custom sliver appBar with a search bar in it. I made a normal app bar that looks like this : But I want that when we scroll down, the app bar looks like that :
Actually, the code of the normal app bar is just a green
AppBar
ofelevation: 0
and just below I add myHeader()
. Here's the code of my Header :class Header extends StatefulWidget { String title; IconData icon; Header({@required this.title, @required this.icon}); @override _HeaderState createState() => _HeaderState(); } class _HeaderState extends State<Header> { TextEditingController _editingController; @override void initState() { super.initState(); _editingController = TextEditingController(); } @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; return PreferredSize( preferredSize: size, child: Container( margin: EdgeInsets.only(bottom: kDefaultPadding * 2.5), height: size.height*0.2, child: Stack( children: [ Container( height: size.height*0.2-27, width: size.width, decoration: BoxDecoration( color: Theme.of(context).primaryColor, borderRadius: BorderRadius.only( bottomLeft: Radius.circular(36), bottomRight: Radius.circular(36), ) ), child: Align( alignment: Alignment.topCenter, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(widget.title, style: Theme.of(context).textTheme.headline4.copyWith(color: Colors.white, fontWeight: FontWeight.bold)), SizedBox(width: 20,), Icon(widget.icon, size: 40, color: Colors.white,) ], )), ), Positioned( bottom: 0, left: 0, right: 0, child: Container( alignment: Alignment.center, margin: EdgeInsets.symmetric(horizontal: kDefaultPadding), padding: EdgeInsets.symmetric(horizontal: kDefaultPadding), height: 54, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [BoxShadow( offset: Offset(0, 10), blurRadius: 50, color: Theme.of(context).primaryColor.withOpacity(0.23), )] ), child: Row( children: [ Expanded( child: TextField( controller: _editingController, textAlignVertical: TextAlignVertical.center, onChanged: (_) => setState(() {}), decoration: InputDecoration( hintText: 'Search', hintStyle: TextStyle(color: Theme.of(context).primaryColor.withOpacity(0.5)), enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, ), ), ), _editingController.text.trim().isEmpty ? IconButton( icon: Icon(Icons.search, color: Theme.of(context).primaryColor.withOpacity(0.5)), onPressed: null) : IconButton( highlightColor: Colors.transparent, splashColor: Colors.transparent, icon: Icon(Icons.clear, color: Theme.of(context).primaryColor.withOpacity(0.5)), onPressed: () => setState(() { _editingController.clear(); })), ], ), ), ) ], ), ), ); } @override void dispose() { _editingController.dispose(); super.dispose(); } }
Any help to build this is welcome.
-
pskink over 3 years
-
Simon B over 3 yearsIt doesn't help me because I want a rounded corner app bar with a search bar... I know how to do a simple sliver app bar with a text and picture in background...
-
pskink over 3 yearsuse
SliverPersistentHeader
then
-