I am trying to create a custom floating bottom navigation bar and i create a widget and added a margin to create a floating effect but it adds a white background.
I need to create it without the white background.
Here is my code;
Scaffold(
bottomNavigationBar: AnimatedBottomBar(
currentIcon: viewModel.currentIndex,
onTap: (int index) => viewModel.updateIndex(index),
icons: viewModel.icons,
),
body: viewModel.pages[viewModel.currentIndex],
);
Then the animated bottom bar
import 'package:flutter/material.dart';
import 'package:woneserve_updated_mobile_app/app/theming/colors.dart';
import 'package:woneserve_updated_mobile_app/models/icon_model.dart';
class AnimatedBottomBar extends StatelessWidget {
final int currentIcon;
final List<IconModel> icons;
final ValueChanged<int>? onTap;
const AnimatedBottomBar({
Key? key,
required this.currentIcon,
required this.onTap,
required this.icons,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
color: Colors.transparent,
child: Container(
margin: const EdgeInsets.all(40),
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 2,
blurRadius: 5,
offset: const Offset(0, 2), // changes position of shadow
),
],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: icons
.map(
(icon) => GestureDetector(
onTap: () => onTap?.call(icon.id),
child: AnimatedSize(
duration: const Duration(milliseconds: 900),
child: Icon(
icon.icon,
size: currentIcon == icon.id ? 26 : 23,
color: currentIcon == icon.id ? primaryColor : Colors.black,
),
),
),
)
.toList(),
),
),
);
}
}
How can i create the same effect without the white background? Any help would be appreciated.
You might need to add extendBody: true in Scaffold.
my friend
for solve this problem you have 3 way.
use extendBody: true in your Scaffold.
use theme in MaterialApp Widget.(see below)
ThemeData(
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
backgroundColor: Colors.transparent,
),
),
use floatingActionButton instead of bottomNavigationBar in Scaffold.(see below)
Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: AnimatedBottomBar(...),
...
)
Try remove first Container in build method. If not work, remove margin and padding in second Container.
Related
Changing the width of a container defaults to resizing from the right side.
The pseudocode below has an example where dx is a variable that can change. When it increases or decreases, the container will always grow or shrink from the right side.
Is there a simple way to switch the direction so that the width will increase or decrease from the left side instead of the right side?
Container(
width: dx,
height:200
)
Here is a dartpad gist that shows how the right side of the container's width changes when dragged. What I'm asking is if there is a quick and simple way to make the left side expand/contract without having to animate the position of the container: https://dartpad.dev/?id=ebbe57041bf950018fe5733674c68b20
I checked out your dartpad code. To achieve what you want, I suggest you put two empty Containers on either side of your handles and decrease their size when the handles are dragged (your center Container should also be inside an Expanded widget to take up all the allowed space). here is the example code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
// Application name
title: 'Flutter Stateful Clicker Counter',
theme: ThemeData(
// Application theme data, you can set the colors for the application as
// you want
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Clicker Counter Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
double? rightContainerWidth, leftContainerWidth;
#override
Widget build(BuildContext context) {
rightContainerWidth ??= MediaQuery.of(context).size.width / 2 - 20;
leftContainerWidth ??= MediaQuery.of(context).size.width / 2 - 20;
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Wrap(
children: <Widget>[
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
// left handle
Container(
width: leftContainerWidth,
),
GestureDetector(
onHorizontalDragUpdate: (DragUpdateDetails details) {
setState(() {
leftContainerWidth = details.globalPosition.dx;
});
},
child: Container(
decoration: BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
bottomLeft: Radius.circular(20),
)),
width: 10,
height: 200)),
Expanded(
child: Container(
// padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: ClipRect(
child: Container(
// width: _counter+0.2,
height: 200,
color: Colors.green,
))),
),
GestureDetector(
onHorizontalDragStart: (DragStartDetails details) {
print("st: ${details.localPosition.dx}");
// dx for start is the x offset of the mouse relative to the container
// changeX = (_counter as double) - details.localPosition.dx.floor();
},
onHorizontalDragUpdate: (DragUpdateDetails details) {
setState(() {
// print(details.localPosition.dx);
rightContainerWidth = MediaQuery.of(context).size.width -
details.globalPosition.dx;
});
},
child: Container(
width: 10,
height: 200,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
bottomRight: Radius.circular(20),
)))),
Container(
width: rightContainerWidth,
),
])
],
),
),
);
}
}
Caution: I did not add conditional statements to prevent overflows, make sure you also add them!
Use Wrap Widget
A widget that displays its children in multiple horizontal or vertical runs.
A Wrap lays out each child and attempts to place the child adjacent to the previous child in the main axis, given by direction, leaving spacing space in between. If there is not enough space to fit the child, Wrap creates a new run adjacent to the existing children in the cross axis.
After all the children have been allocated to runs, the children within the runs are positioned according to the alignment in the main axis and according to the crossAxisAlignment in the cross axis.
The runs themselves are then positioned in the cross axis according to the runSpacing and runAlignment.
Example:
Wrap(
spacing: 8.0, // gap between adjacent chips
runSpacing: 4.0, // gap between lines
children: <Widget>[
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: const Text('AH')),
label: const Text('Hamilton'),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: const Text('ML')),
label: const Text('Lafayette'),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: const Text('HM')),
label: const Text('Mulligan'),
),
Chip(
avatar: CircleAvatar(backgroundColor: Colors.blue.shade900, child: const Text('JL')),
label: const Text('Laurens'),
),
],
)
I have a Flutter app with screens rendered conditionally with an array. Anyway, I need to have a popup screen like this :
If have stored all my "popup screens" in an array and rendered the main screen and the popup screen in a stack. I don't know if this is the best solution and I guess I will have performance issues.
Here is the PopupContainerclass, this Widget is rendered on every Popup Screen with the child passed as content :
class PopupContainer extends StatefulWidget {
final Widget? child;
const PopupContainer({
Key? key,
this.child,
}) : super(key: key);
#override
State<PopupContainer> createState() => _PopupContainerState();
}
class _PopupContainerState extends State<PopupContainer> {
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
return Consumer<ScreenManager>(
builder: (context, manager, child) => Stack(
alignment: Alignment.bottomCenter,
children: [
BackdropFilter(
filter: ImageFilter.blur(sigmaX: 6, sigmaY: 6),
child: Container(
decoration: BoxDecoration(color: Colors.white.withOpacity(0.0)),
),
),
Container(
height: height * 0.8,
width: double.infinity,
padding: const EdgeInsets.all(32),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
),
boxShadow: [
BoxShadow(
blurRadius: 37,
spreadRadius: 0,
color: Color.fromRGBO(28, 48, 72, 0.24),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
alignment: Alignment.topRight,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
padding: EdgeInsets.zero,
primary: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () => manager.closePopup(),
child: SvgPicture.asset('assets/close.svg'),
),
),
widget.child ?? const SizedBox.shrink(),
],
),
),
],
),
);
}
}
The consumer is used for handling the screens states :
enum ScreensName {
homeScreen,
favoriteProductsScreen,
archivedListsScreen,
recipesScreen,
}
enum PopupsName {
newProductPopup,
archivedListPopup,
editProductPopup,
newRecipePopup,
}
const screens = <ScreensName, Widget>{
ScreensName.homeScreen: HomeScreen(),
ScreensName.favoriteProductsScreen: FavoriteProductsScreen(),
ScreensName.archivedListsScreen: ArchivedListsScreen(),
ScreensName.recipesScreen: RecipesScreen(),
};
const popups = <PopupsName, Widget>{
PopupsName.newProductPopup: NewProductPopup(),
};
class ScreenManager extends ChangeNotifier {
static ScreensName screenName = ScreensName.homeScreen;
static PopupsName? popupName = PopupsName.newProductPopup;
get currentScreen => screens[screenName];
get currentPopup => (popups[popupName] ?? Container());
/// Open the given popup.
void openPopup(PopupsName newPopupName) {
popupName = newPopupName;
notifyListeners();
}
/// Closes the current popup.
void closePopup() {
popupName = null;
notifyListeners();
}
/// Change the screen.
void setScreen(ScreensName newScreenName) {
screenName = newScreenName;
notifyListeners();
}
}
And finally, the main component build method (I also have some theme styling but useless here) :
Widget build(BuildContext context) {
DatabaseHelper.initDb();
return Consumer<ScreenManager>(
builder: (context, screenManager, child) => Material(
child: MaterialApp(
title: _title,
theme: _customTheme(),
home: Stack(
alignment: Alignment.bottomCenter,
children: <Widget>[
screenManager.currentScreen,
screenManager.currentPopup,
],
),
),
),
);
}
PS : I am a web developer so I know the main programming principles but Dart and mobile dev is brand new for me. Also, I could share my code with you, however, this project is splitted into files and it would take too much space in the post. Ask if you need it !
Maybe an easier solution would be to use the showDialog function where you need to trigger the popup. Check out the docs https://api.flutter.dev/flutter/material/showDialog.html
showDialog(context: context, builder: (context) => AlertDialog(title: Text('Title'), content: Text('Here content'),));
I'm trying to implement a layout, where the Sliver App Bar has rounded bottom corners when expanded, but when it is collapsed I do not want those rounded corners.
Actual Behaviour:
enter image description here
Expected Behaviour:
Here's my SliverAppBar code:
`SliverAppBar(
systemOverlayStyle: const SystemUiOverlayStyle(
statusBarColor: Color(0xFFE0E64B),
),
backgroundColor: Color(0xFFE0E64B),
expandedHeight: 300.0,
floating: false,
pinned: true,
collapsedHeight: 60.0,
onStretchTrigger: () async {
setState(() {});
},
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text(
'Pokedex',
style: TextStyle(
color: Colors.white,
),
),
Text(
'#025',
style: TextStyle(
color: Colors.white,
),
),
],
),
flexibleSpace: FlexibleSpaceBar(
collapseMode: CollapseMode.parallax,
background: Container(
decoration: const BoxDecoration(
color: Color(0xFFE0E64B),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(50.0),
bottomRight: Radius.circular(50.0),
),
),
child: Hero(
tag: 'pokemon_container$index',
child: Column(
children: [
const SizedBox(
height: 120.0,
),
Expanded(
child: ClipRRect(
child: Image.network(
imageUrl,
fit: BoxFit.scaleDown,
),
),
),
const SizedBox(
height: 30.0,
),
],
),
),
),
),
),`
shape: ContinuousRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(30),
bottomRight: Radius.circular(30))),
Here is your code. Put it inside sliverAppBar
NestedScrollView / SliverAppBar solution
This is definitely achievable. SliverAppBar does support what we need, it has support for rounded borders, the shadow effect and changing sizes. For handling the border requirement we can use a RoundedRectangleBorder.
Although for getting a smooth transition for the border change, we need to update the values frequently, when changing the size of the SliverAppBar.
Example code
Do note that the package flutter_riverpod (version 1.0.3) is used for state management in this example.
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
class RoundedSliverExampleScreen extends StatelessWidget {
const RoundedSliverExampleScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: NestedScrollView(
floatHeaderSlivers: true,
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[
ExpandingAppBar(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// Flexible is important for the children widgets added here.
Flexible(child: Container(color: Colors.yellow, width: 50, height: 50,))
],
)
];
},
body: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[
Text("Hello!")
],
),
)
);
}
}
/// An SliverAppBar widget with alternating rounded border depending on the
/// expandedHeight.
///
/// Provides easy support for adding children widgets in the
/// expanded area as if it was a Column, although children widgets should be
/// wrapped in a Flexible widget.
class ExpandingAppBar extends ConsumerWidget {
const ExpandingAppBar({
Key? key,
this.children = const <Widget>[],
this.mainAxisAlignment = MainAxisAlignment.start
}) : super(key: key);
final List<Widget> children;
final MainAxisAlignment mainAxisAlignment;
#override
Widget build(BuildContext context, WidgetRef ref) {
RoundedHeaderState state = ref.watch(roundedHeaderProvider);
return SliverAppBar(
expandedHeight: state.highestHeight,
pinned: true,
primary: true,
forceElevated: true,
title: const Text('Pokèdex'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(bottom: Radius.circular(state.radius)),
),
flexibleSpace: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// We update the state here.
ref.read(roundedHeaderProvider.notifier).updateHeight(constraints.maxHeight);
return Opacity(
opacity: state.scrollFraction,
child: Padding(
padding: EdgeInsets.only(top: state.smallestHeight),
child: Column(mainAxisAlignment: mainAxisAlignment, children: children),
),
);
},
),
);
}
}
#immutable
class RoundedHeaderState {
final double highestHeight = 256;
final double smallestHeight = kToolbarHeight + 24;
final double currentHeight;
final double contentOpacity = 1;
const RoundedHeaderState({this.currentHeight = 256});
double get scrollFraction => min(max((currentHeight - smallestHeight) / (highestHeight - smallestHeight), 0), 1);
double get radius => 64 * scrollFraction;
}
class RoundedHeaderNotifier extends StateNotifier<RoundedHeaderState> {
RoundedHeaderNotifier(): super(const RoundedHeaderState());
updateHeight(double currentHeight) {
final newState = RoundedHeaderState(currentHeight: currentHeight);
// Check that the new state is not equal to the next (prevents rebuild loop)
if(state.currentHeight != newState.currentHeight) {
// Setting state triggers an rebuild, the PostFrameCallback let Flutter
// postpone the upcoming rebuild at a later time.
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
state = newState;
});
}
}
}
final roundedHeaderProvider = StateNotifierProvider<RoundedHeaderNotifier, RoundedHeaderState>((ref) {
return RoundedHeaderNotifier();
});
// Pay attention to the ProviderScope wrapping the MaterialApp. Riverpod requires this.
void main() => runApp(
const ProviderScope(
child: MaterialApp(home: RoundedSliverExampleScreen())
)
);
Result - Gif of the SliverAppBar's transition.
Let's say for example when I press on the Like button (from the Like_button package) it will set its boolean to true, meaning that it will turn red. But when I pop this page out of the navigation and go back to it, how do I get it to show that I have previously pressed the button already? (right now what it does is it shows the same state as if i didn't press on it before) I want to make it into like 'favourite article' system.
import 'package:flutter/material.dart';
import 'package:like_button/like_button.dart';
class Article extends StatefulWidget {
#override
_ArticleState createState() => _ArticleState();
}
class _ArticleState extends State<Article> {
bool isLiked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[50],
body: SingleChildScrollView(
GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/N1');
},
child: Container(
margin: const EdgeInsets.symmetric(vertical:10),
child: Padding(
padding: EdgeInsets.fromLTRB(30, 0, 30, 0),
child: Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(25.0)),
child: Column(
children: [
Stack(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(25.0),
child: Image.asset('assets/CoverImage1.jpeg')),
Positioned(
top: 15,
right: 15,
child: Container(
width:50,
height:50,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(15.0)),
border: Border.all(color: Color.fromRGBO(141, 141, 141, 1.0).withAlpha(40)),
),
child: Center(
child: LikeButton(
size: 25,
isLiked: isLiked,
likeBuilder: (isLiked) {
final color = isLiked ? Colors.red : Colors.grey;
return Icon(Icons.favorite, color:color, size:25);
},
onTap: (isLiked) async {
this.isLiked = !isLiked;
return !isLiked;
}
),
),
),
),
],
),
you can store the value of variable with shared preferences.
you can check the documentation here: https://pub.dev/packages/shared_preferences/example
try these steps:
store value with something like prefs.setBool()
get the value with getBool in initState(). initState() always perform before UI widget is built.
with these steps enables the app to get the value even when apps is closed
My Code:
bool _isClicked = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 3.0),
child: Container(
decoration: BoxDecoration(
color: _isClicked ? Colors.orange[300] : Colors.white,
borderRadius: BorderRadius.circular(30.0),
),
child: FlatButton(
splashColor: Colors.orange[300],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
onPressed: () {
setState(() {
_isClicked = !_isClicked;
});
},
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 20.0,
),
child: Text(
foodItem,
style: TextStyle(
fontSize: 20.0,
color: _isClicked ? Colors.white : Colors.grey[700],
),
),
),
),
),
),
);
Reality:
Expectation:
When I click one button, only that turns orange the rest stay white.
When I click it back again, it turns grey again just like the rest.
I believe you want to achieve some kind toggle behavior for the buttons. Though ToggleBar widget is good for this it is not flexible with it expectations about child widgets. So a ButtonBar widget would be helpful with some kind internal state about the buttons which are clicked. Here is a working solution which might help you. The same code is available as a codepen here.
Approach
Extracted your code for the button into a widget called TButton with parameters as follows
isClicked - a boolean flag to denote if the button is clicked.
foodItem - the text to be displayed on the button.
onPressed - a callback function to be called when the button is pressed.
In the parent widget MyButtons hold a list of bool indicating the status of click for each button.
MyButtons accepts a list of foodItems. Iterate this list and generate a list of TButton widget and pass it to the ButtonBar as children.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
final Color darkBlue = Color.fromARGB(255, 18, 32, 47);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue, scaffoldBackgroundColor: darkBlue),
home: Scaffold(
body: MyButtons(foodItems: ['Pizza', 'Burger', 'Kebab']),
),
);
}
}
class MyButtons extends StatefulWidget {
MyButtons({Key key, this.foodItems}) : super(key: key);
final List<String> foodItems;
#override
_MyButtonsState createState() => _MyButtonsState();
}
class _MyButtonsState extends State<MyButtons> {
List<bool> isSelected;
#override
initState() {
super.initState();
// initialize the selected buttons
isSelected = List<bool>.generate(widget.foodItems.length, (index) => false);
}
#override
Widget build(BuildContext context) {
return Padding(
// just for aesthetics
padding: const EdgeInsets.only(top: 80.0),
child: ButtonBar(
// use the alignment to positon the buttons in the screen horizontally
alignment: MainAxisAlignment.center,
// iterate over the foodItems and generate the buttons.
children: widget.foodItems.asMap().entries.map((entry) {
return TButton(
isClicked: isSelected[entry.key],
foodItem: entry.value,
onPressed: () {
setState(() {
isSelected[entry.key] = !isSelected[entry.key];
});
});
}).toList(),
),
);
}
}
class TButton extends StatelessWidget {
final bool isClicked;
final String foodItem;
/// OnPressed is passed from the parent. This can be changed to handle it using any state management.
final Function onPressed;
TButton(
{#required this.isClicked,
#required this.foodItem,
#required this.onPressed});
#override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
color: isClicked ? Colors.orange[300] : Colors.white,
borderRadius: BorderRadius.circular(30.0),
),
child: FlatButton(
splashColor: Colors.orange[300],
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
onPressed: onPressed,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: 20.0,
),
child: Text(
foodItem,
style: TextStyle(
fontSize: 20.0,
color: isClicked ? Colors.white : Colors.grey[700],
),
),
),
),
);
}
}