When using the cupertino sliver navigation bar, the body scrolls under it.
I tried using nested scroll view but even that resulted in the same behavior
class MainScreen extends StatefulWidget {
const MainScreen({Key? key}) : super(key: key);
#override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
child: CustomScrollView(
slivers: [
CupertinoSliverNavigationBar(
brightness: Brightness.dark,
padding: EdgeInsetsDirectional.zero,
largeTitle: Text(
'Tasks',
style: appBarTextStyle,
),
trailing: CupertinoButton(
onPressed: () {}, child: primaryIcon(Icons.search)),
leading: CupertinoButton(
onPressed: () {}, child: primaryIcon(Icons.menu)),
),
SliverFillRemaining(
child: Container(
width: double.infinity,
padding: (MediaQuery.of(context).size.width < 768)
? const EdgeInsets.symmetric(
horizontal: 15.0, vertical: 15.0)
: const EdgeInsets.symmetric(
horizontal: 35.0, vertical: 15.0),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
filteredWidget(
context,
'Scheduled',
'No scheduled tasks',
arrayController.scheduledTodos,
Icons.schedule),
filteredWidget(
context,
'Today',
'Schedule a task for today',
arrayController.todayTodos,
Icons.calendar_today),
],
),
const SizedBox(
height: 20.0,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
filteredWidget(
context,
'Completed',
'No completed tasks',
arrayController.doneTodos,
Icons.done_rounded),
filteredWidget(context, 'All', 'No tasks yet',
arrayController.allTodos, Icons.task)
],
),
],
)),
)
],
),
);
}
}
Here is the full code on Github
Reproducible example:
import 'package:flutter/cupertino.dart';
void main() => runApp(const SliverNavBarApp());
class SliverNavBarApp extends StatelessWidget {
const SliverNavBarApp({super.key});
#override
Widget build(BuildContext context) {
return const CupertinoApp(
theme: CupertinoThemeData(brightness: Brightness.light),
home: SliverNavBarExample(),
);
}
}
class SliverNavBarExample extends StatelessWidget {
const SliverNavBarExample({super.key});
#override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
// A ScrollView that creates custom scroll effects using slivers.
child: CustomScrollView(
// A list of sliver widgets.
slivers: <Widget>[
const CupertinoSliverNavigationBar(
leading: Icon(CupertinoIcons.person_2),
// This title is visible in both collapsed and expanded states.
// When the "middle" parameter is omitted, the widget provided
// in the "largeTitle" parameter is used instead in the collapsed state.
largeTitle: Text('Contacts'),
trailing: Icon(CupertinoIcons.add_circled),
),
// This widget fills the remaining space in the viewport.
// Drag the scrollable area to collapse the CupertinoSliverNavigationBar.
SliverFillRemaining(
child: Container(
height: 100.0,
width: double.infinity,
padding: (MediaQuery.of(context).size.width < 768)
? const EdgeInsets.symmetric(
horizontal: 15.0, vertical: 15.0)
: const EdgeInsets.symmetric(
horizontal: 35.0, vertical: 15.0),
),
),
],
),
);
}
}
Related
I have a search page. I display 2 containers with information on the search page. But I ran into a problem, my bottom station container goes off the screen and I need to scroll the page to see the information. How can I put 2 containers on the screen and not have to scroll the page so that 2 containers fit on the same screen?
1
Widget _addresses(Size size, StationCubit stationCubit) => ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height / 2,
),
child: SizedBox(
width: size.width,
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: Container(
padding: const EdgeInsets.only(left: 20, top: 17),
decoration: BoxDecoration(
color: constants.Colors.greyXDark.withOpacity(0.8),
borderRadius: BorderRadius.circular(24),
),
child: SingleChildScrollView(
controller: _addressesController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Addresses',
style: constants.Styles.smallBookTextStyleWhite,
),
const SizedBox(height: 25),
2
Widget _station(Size size, StationCubit stationCubit) => ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height / 2,
),
child: SizedBox(
width: size.width,
child: ClipRRect(
borderRadius: BorderRadius.circular(24),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: Container(
padding: const EdgeInsets.only(left: 20, top: 17),
decoration: BoxDecoration(
color: constants.Colors.greyXDark.withOpacity(0.8),
borderRadius: BorderRadius.circular(24),
),
child: SingleChildScrollView(
controller: _stationController,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Station',
style: constants.Styles.smallBookTextStyleWhite,
),
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 MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
Expanded(
child: Container(
color: Colors.deepPurple,
child: ListView.builder(itemBuilder: (c, i) {
return Text("Test $i");
})),
),
Expanded(
child: Container(
color: Colors.deepOrange,
child: ListView.builder(itemBuilder: (c, i) {
return Text("Test $i");
})),
),
],
));
}
}
Try placing both containers in column and wrap both container with flexible/expanded to expand containers in full screen.
Example code:
column(
children: [
Expanded(
child: Container(child: Text("Container 1")
),
Expanded(
child: Container(child: Text("Container 2")
)
]
)
Use 2 Expanded container in single column
column( children: [ Expanded( child: Container(child: Text("Container 1") ), Expanded( child: Container(child: Text("Container 2") ) ] ).
Abdul Rahman Panhyar your answer is right but Max need to show data came from any API so there is a chance of bulk data and just wrapping the container with expanded will disrupt the UI. so what is suggest you can divide your screen in two parts then in each part you can use Listview builder so it will be inner scrollable.
I want to remove the following blue padding from MaterialBanner widget, but it doesn't seem to be customizable. I want to insert an image in the red region.
I looked into MaterialBanner for using across Scaffold widgets because ScaffoldMessenger doesn't allow me to insert widgets other than MaterialBanner.
Is there any suggestion?
dartpad.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: Scaffold(body: JustBanner())));
}
class JustBanner extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _JustBannerState();
}
}
class _JustBannerState extends State<JustBanner> {
#override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () {
final messenger = ScaffoldMessenger.of(context);
messenger.clearMaterialBanners();
messenger.showMaterialBanner(MaterialBanner(
padding: EdgeInsets.zero,
leadingPadding: EdgeInsets.zero,
leading: const SizedBox.shrink(),
backgroundColor: Colors.blue,
content: Container(
color: Colors.red,
width: 200,
height: 50,
),
actions: const [SizedBox.shrink()]));
},
child: const Text('Banner')),
],
);
}
}
Container(
width: MediaQuery.of(context).size.width,
child: MaterialBanner(
content: Text('Hello'),
actions: [
Icon(Icons.add),
],
),
),
Its no possible without copy and re-create the class, buttonBar always appear:
final Widget buttonBar = Container( // <-- problematic widget
alignment: AlignmentDirectional.centerEnd,
constraints: const BoxConstraints(minHeight: 52.0),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: OverflowBar(
overflowAlignment: widget.overflowAlignment,
spacing: 8,
children: widget.actions,
),
);
final double elevation = widget.elevation ?? bannerTheme.elevation ?? 0.0;
final Color backgroundColor = widget.backgroundColor
?? bannerTheme.backgroundColor
?? theme.colorScheme.surface;
final TextStyle? textStyle = widget.contentTextStyle
?? bannerTheme.contentTextStyle
?? theme.textTheme.bodyText2;
Widget materialBanner = Container(
margin: EdgeInsets.only(bottom: elevation > 0 ? 10.0 : 0.0),
child: Material(
elevation: elevation,
color: backgroundColor,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Padding(
padding: padding,
child: Row(
children: <Widget>[
if (widget.leading != null)
Padding(
padding: leadingPadding,
child: widget.leading,
),
Expanded(
child: DefaultTextStyle(
style: textStyle!,
child: widget.content,
),
),
if (isSingleRow)
buttonBar, // <----- here
],
),
),
if (!isSingleRow)
buttonBar, // <----- here
if (elevation == 0)
const Divider(height: 0),
],
),
),
);
I tried a lot to get the behavior of the iOS project https://github.com/ivanvorobei/SPLarkController working in Flutter / Dart. I do not understand how to get another view behind the scaffold (holding also the bottom navigation bar). Any ideas how this can be achieved?
This could be achieved with the help of Stack.
First layer for the buttons on the bottom:
Second layer for the main content:
Then, you can wrap the BottomNavBar inside GestureDetector with onVerticalDragUpdate property.
Complete Code:
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 MaterialApp(
home: Scaffold(
body: Builder(
builder: (context) => MyChild(MediaQuery.of(context).size.height),
),
),
);
}
}
class MyChild extends StatefulWidget {
final double screenHeight;
const MyChild(this.screenHeight, {Key? key}) : super(key: key);
#override
_MyChildState createState() => _MyChildState();
}
class _MyChildState extends State<MyChild> {
double val = 1.0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
padding: const EdgeInsets.only(bottom: 20.0),
color: const Color(0xFF303030),
child: Padding(
padding: const EdgeInsets.only(left: 20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Row(
children: [
ElevatedButton(
onPressed: () {}, child: const Text('Button 1')),
const SizedBox(
width: 20.0,
),
ElevatedButton(
onPressed: () {}, child: const Text('Button 2'))
],
),
const SizedBox(
height: 20,
),
Row(
children: [
ElevatedButton(
onPressed: () {}, child: const Text('Button 3')),
const SizedBox(
width: 20.0,
),
ElevatedButton(
onPressed: () {}, child: const Text('Button 4'))
],
),
],
),
),
),
LayoutBuilder(
builder: (context, constraints) => AnimatedContainer(
duration: const Duration(milliseconds: 500),
curve: Curves.ease,
height: constraints.maxHeight * val,
color: Colors.white,
child: Column(
children: [
Expanded(
child: ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: 25,
itemBuilder: (context, index) => ListTile(
title: Text('ListTile $index'),
),
),
),
GestureDetector(
onVerticalDragUpdate: (details) {
if (details.delta.dy < 0) { // If the user drags upwards
setState(() {
val = 0.7;
});
} else if (details.delta.dy > 0) { // If the user drags downwards
setState(() {
val = 1.0;
});
}
},
// Create your bottom navigation bar here
// and not bottomNavigationBar property of Scaffold
child: Container(
color: Colors.green.shade100,
height: 80,
),
)
],
),
),
),
],
);
}
}
What I need is green box should end where the text ends.
Here's my code
Widget buildFlexible() {
return Flexible(
child: ListView(
shrinkWrap: true,
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
children: [
MessageBubble(title: 'Test1'),
MessageBubble(title: 'Test2'),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: <Widget>[
Row(
children: [
buildFlexible(),
],
),
],
),
);
}
class MessageBubble extends StatelessWidget {
final String title;
MessageBubble({required this.title});
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Material(
child: Text(title),
color: Colors.lightGreenAccent,
// elevation: 5.0,
),
);
}
}
Wrap your Padding widget from your MessageBubble class with the Wrap() widget
So, the final code for the MessageBubble should be:
class MessageBubble extends StatelessWidget {
final String title;
MessageBubble({required this.title});
#override
Widget build(BuildContext context) {
return Wrap(children: [
Padding(
padding: EdgeInsets.all(10.0),
child: Material(
child: Text(title),
color: Colors.lightGreenAccent,
// elevation: 5.0,
),
)
]);
}
}
Alternatively, you can achieve the same result with just using a Container widget and by removing the Material widget and the Padding widget as:
Wrap(children: [
Container(
padding: EdgeInsets.all(10.0),
child: Text('hi'),
color: Colors.lightGreenAccent,
)
])
Instead of:
Wrap(children: [
Padding(
padding: EdgeInsets.all(10.0),
child: Material(
child: Text(title),
color: Colors.lightGreenAccent,
// elevation: 5.0,
),
)
]);
I'm trying to implement the call button inside a card widget,
I want the whole card background to change color to blue (like selected) when I press the call button, and to be changed back to normal when I press any other card, like to make the call button switch for card selection,
tried to use the setState function but it didn't work since it changes color only when I'm tapping the whole card not a specific button in it.
How do I make the whole card selected when I press the call button and released when I press any other card (after I get back from the dialer application)
Here's my code:
_launchCaller() async {
const url = "tel:+972545522973";
if (await canLaunch(url)) {
await launch(url);
} else {
throw 'Could not launch $url';
}
}
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Card(
margin: EdgeInsets.fromLTRB(20.0, 6.0, 20.0, 0.0),
color: Colors.brown[30],
child: ListTile(
isThreeLine: true,
title: Row(
children: <Widget> [
Container(
child:Text(widget.helpRequest.category.description) ,
alignment: Alignment.topLeft,
),
Spacer(),
Container(
child:Text(formatter.format(now)),
alignment: Alignment.topRight,
),
]
)
,
subtitle: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
GetUserName(widget.helpRequest.sender_id, DataBaseService().userInNeedCollection),
Text(widget.helpRequest.description),
]
)
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
onTap: (){
_launchCaller();
** Here i think I should add the code **
},
onLongPress: () => print("Long Press: Call"),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Icon(Icons.call,
size: 20.0,
color: Colors.green,
)
),
),
],
),
),
),
);
And this setState function I tried to use which didn't work well in my case (I was changing state on the onTap function):
void initState() {
super.initState();
color = Colors.transparent;
}
Final Output:
You can set the color of a specific card, but for that to happen, you need to have some way to reference that the selected card was clicked on, with this reference we can decide whether the card is selected and if yes then change the color according to our preference.
In the following example, I am more or less using the same card widget template that you stated in the question, then I am using the ListView.builder to render five cards, each having the same functionality.
Whenever the call button is pressed, the corresponding index of that specific card is assigned to the state selectedIndex and from this, we can assign the color to the selected Card.
Here is the full example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int selectedIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.only(top: 8.0),
child: ListView.builder(
itemCount: 5,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Card(
margin: EdgeInsets.fromLTRB(20.0, 6.0, 20.0, 0.0),
color: index == selectedIndex
? Colors.amberAccent
: Colors.brown[30],
child: ListTile(
isThreeLine: true,
title: Row(children: <Widget>[
Container(
child: Text("Some Text"),
alignment: Alignment.topLeft,
),
Spacer(),
Container(
child: Text("Some Text"),
alignment: Alignment.topRight,
),
]),
subtitle: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("Some Text"),
])),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
selectedIndex = index;
});
},
onLongPress: () => print("Long Press: Call"),
child: Padding(
padding: EdgeInsets.all(16.0),
child: Icon(
Icons.call,
size: 20.0,
color: Colors.green,
),
),
),
],
),
),
),
);
},
),
),
);
}
}
Keep track of the clicked item and pass the index of the list
int clickedItemPosition = -1;
bool isChecked(currentPosition) => clickedItemPosition == currentPosition;
Then in your card
//..
Card(
margin: EdgeInsets.fromLTRB(20.0, 6.0, 20.0, 0.0),
color: isChecked(index) ? Colors.blue : Colors.transparent,
//..
In Gesture detector update the clickedItemPosition
//...
GestureDetector(
onTap: () => setState(() => clickedItemPosition = index),
//..