Flutter Collapsible Appbar with custom layout and animation - flutter

I am trying to create an app bar that looks like the following image when expanded
When collapsing I only need the initial column of text to be available in the app bar, so the image should fade away and the text column to animate and become the Appbar title
I have tried using SliverAppBar with a custom scroll view but the Flexible space's title is not looking good also I don't know how to make the image disappear alone and show only the text when collapsed.
As of now, this is the expanded state
And when I try to collapse it, this happens
Here's the code for the same
CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
leading: GestureDetector(
onTap: () => Get.back(),
child: const Icon(
Icons.arrow_back,
color: Colors.black,
),
),
flexibleSpace: FlexibleSpaceBar(
title: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
const Expanded(
child: Text.rich(
TextSpan(
text: "Buffet Deal\n",
style:
TextStyle(color: Colors.black, fontSize: 14),
children: [
TextSpan(
text: "Flash Deal",
style: TextStyle(
color: Colors.black, fontSize: 10),
),
]),
),
),
if (_model.imgUrl != null)
CachedNetworkImage(
imageUrl: _model.imgUrl!,
),
const SizedBox(
width: 18,
)
],
),
),
),
expandedHeight: Get.height * 0.2,
backgroundColor: const Color(0xFFFFF4F4)),
SliverToBoxAdapter(
child: Container()
)
],
),
Your help is appreciated. Thanks in advance

To make the SliverAppBar's flexible space image disappear whenever the expanded space collapse just use the FlexibleSpaceBar.background property.
To solve the overlapping text just wrap the FlexibleSpaceBar around a LayoutBuilder to get if it's expanded or not and adjust the text positioning.
Check out the result below (Also, the live demo on DartPad):
import 'dart:math';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final expandedHeight = MediaQuery.of(context).size.height * 0.2;
return Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
leading: GestureDetector(
onTap: () => {},
child: const Icon(
Icons.arrow_back,
color: Colors.black,
),
),
flexibleSpace: LayoutBuilder(builder: (context, constraints) {
final fraction =
max(0, constraints.biggest.height - kToolbarHeight) /
(expandedHeight - kToolbarHeight);
return FlexibleSpaceBar(
title: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
Expanded(
child: Padding(
padding: EdgeInsets.only(left: 24 * (1 - fraction)),
child: const Text.rich(
TextSpan(
text: "Buffet Deal\n",
style: TextStyle(
color: Colors.black, fontSize: 14),
children: [
TextSpan(
text: "Flash Deal",
style: TextStyle(
color: Colors.black, fontSize: 10),
),
]),
),
),
),
const SizedBox(width: 18)
],
),
),
background: Align(
alignment: Alignment.centerRight,
child: Image.network(
"https://source.unsplash.com/random/400x225?sig=1&licors",
),
),
);
}),
expandedHeight: MediaQuery.of(context).size.height * 0.2,
backgroundColor: const Color(0xFFFFF4F4),
),
const SliverToBoxAdapter(child: SizedBox(height: 1000))
],
),
);
}
}

Related

Static image with scrollable text in flutter

import 'package:flutter_svg/flutter_svg.dart';
void main() {
runApp(MaterialApp(
home: Scaffold(
// adding App Bar
appBar: AppBar(
actions: [
SvgPicture.asset(
"assets/images/Moto.svg",
width: 50,
height: 100,
),
IconButton(
onPressed: () {},
icon: const Icon(Icons.cancel_outlined),
alignment: Alignment.topRight,
)
],
backgroundColor: Colors.white,
title: const Text(
"Version:1.38-alpha(10308)",
style: TextStyle(
color: Colors.white,
),
),
),
body: const MyApp(),
),
));
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: Container(
child: const Expanded(
// SingleChildScrollView contains a
// single child which is scrollable
child: SingleChildScrollView(
// for Vertical scrolling
scrollDirection: Axis.vertical,
child: Text(`
This is simple wrap your Expanded widget with stack and add the image widget as a first child to the stack.
Scaffold(
appBar: AppBar(
title: const Text('Sample UI'),
),
body: Stack(
children: [
SizedBox.expand(
child: Image.network(
'https://images.pexels.com/photos/1624496/pexels-photo-1624496.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1',
fit: BoxFit.cover,
),
),
SingleChildScrollView(
child: Column(
children: List.generate(100,(index) =>
Text(
'Hello welcome $index',
style: const TextStyle(
fontSize: 20,
color: Colors.amberAccent,
fontWeight: FontWeight.bold,
),
)
),
),
)
],
),
)

Custom Sliver App Bar in flutter with an Image and 2 Text widgets going into app bar on scrolling

i want to implement the sliver app bar as shown in the the 2 pictures given below. After much googling , I fount about the CustomScrollView widget and the SliverAppBar widget but all the tutorials and blogs online about sliver app bars show a simple one where an image disappears into an app bar with a text as title on scrolling. However here what I want to achieve is slightly different and I am having a hard time trying to figure out how to do it. Can anyone help me with it?
You can try this:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ScrollController? _scrollController;
bool lastStatus = true;
double height = 200;
void _scrollListener() {
if (_isShrink != lastStatus) {
setState(() {
lastStatus = _isShrink;
});
}
}
bool get _isShrink {
return _scrollController != null &&
_scrollController!.hasClients &&
_scrollController!.offset > (height - kToolbarHeight);
}
#override
void initState() {
super.initState();
_scrollController = ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
_scrollController?.removeListener(_scrollListener);
_scrollController?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(),
title: 'Horizons Weather',
home: Scaffold(
body: NestedScrollView(
controller: _scrollController,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
elevation: 0,
backgroundColor: Colors.blueGrey,
pinned: true,
expandedHeight: 275,
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
title: _isShrink
? const Text(
"Profile",
)
: null,
background: SafeArea(
child: Column(
children: [
Padding(
padding: const EdgeInsets.only(top: 48),
child: ClipRRect(
borderRadius: BorderRadius.circular(100),
child: Image.network(
headerImage,
fit: BoxFit.cover,
height: 100,
width: 100,
),
),
),
const SizedBox(
height: 16,
),
Text(
"Flipkart",
style: textTheme.headline4,
),
const SizedBox(
height: 8,
),
const Text(
"flipkart.com",
),
const SizedBox(
height: 5,
),
const Text(
"Info about the company",
),
],
),
),
),
actions: _isShrink
? [
Padding(
padding: const EdgeInsets.only(left: 8, right: 12),
child: Row(
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, right: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text(
"Flipkart",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
"flipkart.com",
style: TextStyle(
fontSize: 12,
),
),
],
),
),
ClipRRect(
borderRadius: BorderRadius.circular(100),
child: Image.network(
headerImage,
fit: BoxFit.cover,
height: 30,
width: 30,
),
),
],
),
),
]
: null,
),
];
},
body: CustomScrollView(
scrollBehavior: const ConstantScrollBehavior(),
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Center(child: Text("Item: $index")),
);
},
childCount: 50,
),
),
],
),
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
const UserAccountsDrawerHeader(
accountName: Text("Zakaria Hossain"),
accountEmail: Text("zakariaaltime#gmail.com"),
currentAccountPicture: CircleAvatar(
backgroundColor: Colors.orange,
child: Text(
"A",
style: TextStyle(fontSize: 40.0),
),
),
),
ListTile(
leading: Icon(Icons.home),
title: Text("Home"),
onTap: () {
Navigator.pop(context);
},
),
ListTile(
leading: Icon(Icons.settings),
title: Text("Settings"),
onTap: () {
Navigator.pop(context);
},
),
ListTile(
leading: Icon(Icons.contacts),
title: Text("Contact Us"),
onTap: () {
Navigator.pop(context);
},
),
],
),
),
),
);
}
}
Demo

Different elements position in Flutter

I faced with the problem that I have different AppBar icon paddings in my Flutter app.
I tried to solve it with package providing a responsive measuring (something like dp) instead of pixels, even though there is no padding in Pixel's (second phone) app bar icon I have to place text a lot more lower than it should be on Nexus phone
Here is my code:
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
final backgroundColor = 0xffF6F6F6;
final accentColor = 0xff8c84e2;
#override
Widget build(BuildContext context) {
return ScreenUtilInit(
designSize: Size(360, 690),
builder:() => MaterialApp(
// theme: ThemeData(fontFamily: 'Roboto'),
home: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
backgroundColor: Color(backgroundColor),
expandedHeight: MediaQuery.of(context).size.height * 0.5,
leading: Builder(
builder: (context) => IconButton(
padding: EdgeInsets.zero,
tooltip: 'открыть меню',
icon: Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
flexibleSpace: FlexibleSpaceBar(
background: Stack(
children: <Widget>[
Container(
width: 500.w,
height: 500.h,
child: CustomPaint(
painter: CirclePainter(),
),
),
Padding(
padding: EdgeInsets.only(top: 100.h, left: 17.h),
child: Container(
child: Text('Подборки',
style: TextStyle(
color: Colors.white,
fontSize: 24,
fontFamily: 'Consolas')),
),
),
Positioned(
top: 110,
right: 10,
child: TextButton(
onPressed: () {},
child: Text(
'Открыть всё',
style: TextStyle(
color: Colors.white,
fontSize: 14,
fontFamily: 'Consolas'),
),
style: ButtonStyle(),
),
),
How can I make it identical on both phones?

What do I need to do to link to another file with my button widgets?

I am still learning to code. How do I store all my button widgets in another file and import it into my main file? Here is the code:
class _testState extends State<test> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue[900],
appBar: AppBar(
title: Text('test'),
backgroundColor: Colors.red[900],
),
body: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
children: [
//buttons
],
),
);
}
}
How can I take the code below and put it in another file and then link it to the file above by the buttons comment?
Padding(
padding: const EdgeInsets.fromLTRB(0.0, 9.0, 0.0, 0.0),
child: TextButton.icon(
onPressed: () => {},
icon: Column(
children: [
Icon(
Icons.add,
color: Colors.white,
size: 75,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
'Label',
style: TextStyle(
color: Colors.white,
),
),
),
],
),
label: Text(
'', //'Label',
style: TextStyle(
color: Colors.white,
),
),
),
),
-First, create a new file, name it jeff.dart.
-Second, type this in the new file: stless . Press Enter, Flutter will automatically make a new StatelessWidget for you. Change the class name to JeffButton. Now, it look like this :
class JeffButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
);
}
}
Copy and paste the button's code , replace the Container() .Congratulations! You had the JeffButton! Now you can use it everywhere:
class JeffButton extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(0.0, 9.0, 0.0, 0.0),
child: TextButton.icon(
onPressed: () => {},
icon: Column(
children: [
Icon(
Icons.add,
color: Colors.white,
size: 75,
),
Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
'Label',
style: TextStyle(
color: Colors.white,
),
),
),
],
),
label: Text(
'', //'Label',
style: TextStyle(
color: Colors.white,
),
),
),
);
}
}
Now just add it to where you need it ;) :
children: [
//buttons
JeffButton(),
],
Remember to import the jeff.dart file to where you use it, you can do it easily by move the cursor to JeffButton(), show quick fixes, choose Import: .... (which is the first options). This is how to show quick fixes if you didn't know: https://flutter.dev/docs/development/tools/flutter-fix

How to layout a list of cards with various height?

I'm just getting started with Flutter.
What is the recommended way to layout a list of cards on a screen?
Some cards will have only contain a single object as a line of text, but others that contain multiple objects as lines of text should also have a header within the card.
For example, here is a mock-up that I drew that I'm trying to accomplish.
Flutter doesn't like a ListView inside a Card. It generates the following errors:
I/flutter (13243): ══╡ EXCEPTION CAUGHT BY RENDERING LIBRARY ╞═════════════════════════════════════════════════════════
I/flutter (13243): The following assertion was thrown during performResize():
I/flutter (13243): Vertical viewport was given unbounded height.
I/flutter (13243): Viewports expand in the scrolling direction to fill their container.In this case, a vertical
I/flutter (13243): viewport was given an unlimited amount of vertical space in which to expand. This situation
I/flutter (13243): typically happens when a scrollable widget is nested inside another scrollable widget.
I/flutter (13243): If this widget is always nested in a scrollable widget there is no need to use a viewport because
I/flutter (13243): there will always be enough vertical space for the children. In this case, consider using a Column
I/flutter (13243): instead. Otherwise, consider using the "shrinkWrap" property (or a ShrinkWrappingViewport) to size
I/flutter (13243): the height of the viewport to the sum of the heights of its children.
With #aziza's help, I banged out the following code with provides a base layout very close to what I mocked up, but I have a couple of questions:
Is this the most efficient use of nested widgets?
Is there any way to set a global font size so that I don't have to set it on every Text widget?
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My Layout',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'App Bar Title'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List itemList = [
'Card Text 2 Line 1',
'Card Text 2 Line 2',
'Card Text 2 Line 3',
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Sub Title',
style: TextStyle(
fontSize: 25.0,
),
),
],
),
),
Row(
children: [
Expanded(
child: Card(
shape: RoundedRectangleBorder(
side: BorderSide(
width: 3.0,
),
),
margin: EdgeInsets.all(15.0),
color: Colors.grey,
elevation: 10.0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Card 1 Text',
style: TextStyle(
fontSize: 25.0,
),
),
),
),
),
],
),
Row(
children: [
Expanded(
child: Card(
shape: RoundedRectangleBorder(
side: BorderSide(
width: 3.0,
),
),
margin: EdgeInsets.all(15.0),
color: Colors.grey,
elevation: 10.0,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Card 2 Header',
style: TextStyle(
fontSize: 25.0,
),
),
],
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: List.generate(
itemList.length,
(i) => Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
itemList[i],
style: TextStyle(
fontSize: 25.0,
),
),
),
),
),
],
),
),
),
],
),
Row(
children: [
Expanded(
child: Card(
shape: RoundedRectangleBorder(
side: BorderSide(
width: 3.0,
),
),
margin: EdgeInsets.all(15.0),
color: Colors.grey,
elevation: 10.0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
'Card 3 Text',
style: TextStyle(
fontSize: 25.0,
),
),
),
),
),
],
)
],
),
),
);
}
}
CardModel: Represents each list item, which contains an optional header and list of strings. Building ListView: If header field is persent it is added in a column, then the list of strings of the card are added to the above column as well. At the end these individually created cards are wrapped as a list and displayed inside the ListView.
import 'package:flutter/material.dart';
void main() => runApp(
new MaterialApp(
debugShowCheckedModeBanner: false,
home: new CardsDemo(),
),
);
class CardsDemo extends StatefulWidget {
#override
_CardsDemoState createState() => new _CardsDemoState();
}
class _CardsDemoState extends State<CardsDemo> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Cards'),
),
body: new Column(
children: <Widget>[
new Center(
child: new Padding(
padding: const EdgeInsets.all(15.0),
child: new Text(
'Sub Title',
style:
new TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold),
),
),
),
new Expanded(
child: new ListView(
children: _buildCards(),
padding: const EdgeInsets.all(8.0),
),
),
],
),
);
}
Widget _buildCard(CardModel card) {
List<Widget> columnData = <Widget>[];
if (card.isHeaderAvailable) {
columnData.add(
new Padding(
padding: const EdgeInsets.only(bottom: 8.0, left: 8.0, right: 8.0),
child: new Text(
card.headerText,
style: new TextStyle(fontSize: 24.0, fontWeight: FontWeight.w500),
),
),
);
}
for (int i = 0; i < card.allText.length; i++)
columnData.add(
new Text(card.allText[i], style: new TextStyle(fontSize: 22.0),),
);
return new Card(
child: new Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: Column(children: columnData),
),
);
}
List<Widget> _buildCards() {
List<Widget> cards = [];
for (int i = 0; i < sampleCards.length; i++) {
cards.add(_buildCard(sampleCards[i]));
}
return cards;
}
}
class CardModel {
final String headerText;
final List<String> allText;
final bool isHeaderAvailable;
CardModel(
{this.headerText = "", this.allText, this.isHeaderAvailable = false});
}
List<CardModel> sampleCards = [
new CardModel(allText: ["Card 1 Text"]),
new CardModel(
isHeaderAvailable: true,
headerText: "Card 2 Header",
allText: ["Card 2 Text Line 1", "Card 2 Text Line 2"]),
new CardModel(allText: ["Card 3 Text"]),
];