Related
I am fairly new to flutter and currently trying to create a NavBar.
It looks like this:
If I click on the icon, the bar moves to the selected one and the content changes.
However, I have to hit the icon perfectly. I would like to have a "box" around it, so I can tap just near it. Basically divide the space into 3.
I tried the following:
Widget build(BuildContext context) {
return Container(
height: 60,
color: Color(0xff282424),
child: Stack(
children: [
Container(
child: Row(
children: items.map((x) => createNavBarItem(x)).toList(),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 200),
alignment: Alignment(active.offset, 0.7),
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 5,
width: 50,
decoration: BoxDecoration(
color: active.color,
borderRadius: BorderRadius.circular(2.5)),
),
),
],
),
);
}
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / items.length,
height: 55,
child: GestureDetector(
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
),
);
}
The items should take 1/3 of the width. It isn't working that way tho. Any idea on how to increase the "tappable" space?
EDIT
Full code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.\
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.red,
),
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> {
var screens = [Text("Button1"), Text("Button2"), Text("Button3")];
int currentScreen = 0;
void changeIndex(int index) => setState(() {
currentScreen = index;
});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.orange,
child: Stack(
children: [
SafeArea(child: screens[currentScreen]),
Container(
alignment: Alignment.bottomCenter, child: NavBar(changeIndex))
],
),
),
);
}
}
class MenuItem {
final String name;
final Color color;
final double offset;
MenuItem(this.name, this.color, this.offset);
}
class NavBar extends StatefulWidget {
#override
State<StatefulWidget> createState() => NavBarState(navBarUpdate);
late Function(int) navBarUpdate;
NavBar(this.navBarUpdate);
}
class NavBarState extends State<NavBar> {
var items = [
MenuItem("Test", Colors.red, -0.76),
MenuItem("Test2", Colors.green, 0),
MenuItem("Test3", Colors.yellow, 0.76)
];
late MenuItem active;
late Function(MenuItem) navBarUpdate;
#override
void initState() {
super.initState();
active = items[0];
}
NavBarState(Function(int) navBarUpdate) {
this.navBarUpdate = (item) {
navBarUpdate(items.indexOf(item));
};
}
#override
Widget build(BuildContext context) {
return Container(
height: 60,
color: Color(0xff282424),
child: Stack(
children: [
Container(
child: Row(
children: items.map((x) => createNavBarItem(x)).toList(),
),
),
AnimatedContainer(
duration: Duration(milliseconds: 200),
alignment: Alignment(active.offset, 0.7),
child: AnimatedContainer(
duration: Duration(milliseconds: 400),
height: 5,
width: 50,
decoration: BoxDecoration(
color: active.color,
borderRadius: BorderRadius.circular(2.5)),
),
),
],
),
);
}
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return SizedBox(
width: width / items.length,
height: 55,
child: GestureDetector(
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
),
);
}
}
You can use behavior: HitTestBehavior.translucent, or opaque on createNavBarItem
child: GestureDetector(
behavior: HitTestBehavior.translucent,
You can swap your GestureDetector on top level widget from Icon.
Widget createNavBarItem(MenuItem item) {
double width = MediaQuery.of(context).size.width;
return GestureDetector(
child: Container(
color: Colors.transparent,
width: width / items.length,
height: 55,
child: Icon(
Icons.access_time,
color: item.color,
size: 30,
),
),
onTap: () {
setState(() {
active = item;
navBarUpdate(item);
});
},
);
}
I'm unable to get the onEnd portion of AnimatedOpacity to get called.
I've got a _visible variable set to false, I'm changing that value with setState that is called on a button press, however the print statement in onEnd is never called.
Declared at the top of my widget and set to false initially.
bool _visible = false;
setState is updating visible from a textbutton onPressed
setState(() {
_visible = !_visible;
});
The visibility widget is toggled once the above state is passed however the container that is the child of the AnimatedOpacity widget is immediately shown ( there is no animated fade out from opacity to solid black ), and the only way to trigger the animation completely is to modify the _visible ? 1.0 : 0.0.
The goal is to get the container once triggered to change from an opacity of 0 to 1 then call onEnd, which currently is not happening.
Visibility(
visible: _visible,
child: Center(
child: AnimatedOpacity(
opacity: _visibility ? 1.0 : 0.0,
duration: const Duration(milliseconds: 1000),
onEnd: () {
print("I am never called");
},
child: Container(
width: double.infinity,
height: double.infinity,
color: Colors.black,
),
),
),
),
import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget> {
bool _visible = false;
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(children: [
TextButton(
style: ElevatedButton.styleFrom(
elevation: 10,
shape: CircleBorder(),
primary: Colors.transparent,
onSurface: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () {
setState(() {
_visible = !_visible;
});
},
child: Icon(Icons.home, color: Colors.white),
),
Visibility(
visible: _visible,
child: Center(
child: AnimatedOpacity(
opacity: _visible ? 1 : 0,
duration: const Duration(milliseconds: 1000),
onEnd: () {
print("I am not called but should be");
},
child: Container(
width: 200,
height: 200,
color: Colors.black,
),
),
),
),
]));
}
}
The problem with you code is that the AnimatedOpacity widget is wrapped inside a Visibility widget, which is not animated. Therefore when you call setState to toggle _visible member, the container will become instantly visible. The animation won't run at all, because by the time the Container becomes visible, the _visible member's value is already 1.
To solve this, simply remove Visibility widget, when the AnimatedOpacity reaches opacity value of 0, the Container will be invisible and on value 1 it will be visible, animating between the two states. The print is also executed in onEnd:
class MyWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyWidgetState();
}
}
class _MyWidgetState extends State<MyWidget> {
bool _visible = false;
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(children: [
TextButton(
style: ElevatedButton.styleFrom(
elevation: 10,
shape: const CircleBorder(),
primary: Colors.transparent,
onSurface: Colors.transparent,
shadowColor: Colors.transparent,
),
onPressed: () {
setState(() {
_visible = !_visible;
});
},
child: const Icon(Icons.home, color: Colors.white),
),
Center(
child: AnimatedOpacity(
opacity: _visible ? 1 : 0,
duration: const Duration(milliseconds: 1000),
onEnd: () {
print("I am not called but should be");
},
child: Container(
width: 200,
height: 200,
color: Colors.black,
),
),
]));
}
}
expected behavior
i tried this code but it give me completely difference result from left side and strange animated
double val = 0;
#override
Widget build(BuildContext context) {
return Stack(
children: [
Container(
height: 400,
color: Colors.red,
),
TweenAnimationBuilder(
duration: const Duration(milliseconds: 150),
tween: Tween<double>(begin: 0 , end: val) ,
builder: (BuildContext context, double? value, Widget? child) {
return (
Transform(
alignment: Alignment.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.001)
..setEntry(0, 3, 200 * value!)
..rotateY((pi/6)*value),
child: DefaultTabController(
length: 5,
child: Scaffold(
body: Center(
child: Container(
color: Colors.yellowAccent,
child: IconButton(
onPressed: () {
setState(() {
setState(() {
val == 0 ? val = 1 : val = 0 ;
});
});
},
icon: Text('tab me'),
),
),
)
)
)
)
);
}
)
],
);
}
also i need only the red Container the one who animated from down to up , but i don't know why the main screen is also animate .. i need it never animated ..
any suggestion most welcome guys .. thanks
Instead of custom animation, you can use AnimatedContainer().
Create a boolean like selected which will tell the animated container when to close and when to open the container. And using setState you can toggle the animation.
Align your AnimatedContainer() with Align() and give alignment: Alignment.bottomCenter. And give height:0 is not selected and when selected give height the half of screen using MediaQuery.of(context)
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
bool selected = false;
#override
Widget build(BuildContext context) {
return Column(children: [
ElevatedButton(
onPressed: () {
setState(() {
selected = !selected;
});
},
child: Text("Tap Me!!"),
),
Spacer(),
GestureDetector(
onTap: () {
setState(() {
selected = !selected;
});
},
child: Align(
alignment: Alignment.bottomCenter,
child: AnimatedContainer(
width: double.infinity,
height: selected ? MediaQuery.of(context).size.height / 2 : 0,
color: selected ? Colors.red : Colors.blue,
alignment:
selected ? Alignment.center : AlignmentDirectional.topCenter,
duration: const Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
child: const FlutterLogo(size: 75),
),
),
)
]);
}
}
You can try the same code in dartpad here
I want widgets that has certain size but shrink if available space is too small for them to fit.
Let's say available space is 100px, and each of child widgets are 10px in width.
Say parent's size got smaller to 90px due to resize.
By default, if there are 10 childs, the 10th child will not be rendered as it overflows.
In this case, I want these 10 childs to shrink in even manner so every childs become 9px in width to fit inside parent as whole.
And even if available size is bigger than 100px, they keep their size.
Wonder if there's any way I can achieve this.
return Expanded(
child: Row(
children: [
...List.generate(Navigation().state.length * 2, (index) => index % 2 == 0 ? Flexible(child: _Tab(index: index ~/ 2, refresh: refresh)) : _Seperator(index: index)),
Expanded(child: Container(color: ColorScheme.brightness_0))
]
)
);
...
_Tab({ required this.index, required this.refresh }) : super(
constraints: BoxConstraints(minWidth: 120, maxWidth: 200, minHeight: 35, maxHeight: 35),
...
you need to change Expanded to Flexible
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(appBar: AppBar(), body: Body()),
);
}
}
class Body extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
width: 80,
color: Colors.green,
child: Row(
children: List.generate(10, (i) {
return Flexible(
child: Container(
constraints: BoxConstraints(maxWidth: 10, maxHeight: 10),
foregroundDecoration: BoxDecoration(border: Border.all(color: Colors.yellow, width: 1)),
),
);
}),
),
);
}
}
two cases below
when the row > 100 and row < 100
optional you can add mainAxisAlignment property to Row e.g.
mainAxisAlignment: MainAxisAlignment.spaceBetween,
Try this
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 10,maxHeigth:10),
child: ChildWidget(...),
)
The key lies in a combination of using Flexible around each child in the column, and setting the child's max size using BoxContraints.loose()
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(
title: 'Make them fit',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int theHeight = 100;
void _incrementCounter() {
setState(() {
theHeight += 10;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Playing with making it fit'),
),
body: Container(
color: Colors.blue,
child: Padding(
// Make the space we are working with have a visible outer border area
padding: const EdgeInsets.all(8.0),
child: Container(
height: 400, // Fix the area we work in for the sake of the example
child: Column(
children: [
Expanded(
child: Column(
children: [
Flexible(child: SomeBox('A')),
Flexible(child: SomeBox('A')),
Flexible(child: SomeBox('BB')),
Flexible(child: SomeBox('CCC')),
Flexible(
child: SomeBox('DDDD', maxHeight: 25),
// use a flex value to preserve ratios.
),
Flexible(child: SomeBox('EEEEE')),
],
),
),
Container(
height: theHeight.toDouble(), // This will change to take up more space
color: Colors.deepPurpleAccent, // Make it stand out
child: Center(
// Child column will get Cross axis alighnment and stretch.
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Press (+) to increase the size of this area'),
Text('$theHeight'),
],
),
),
)
],
),
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
class SomeBox extends StatelessWidget {
final String label;
final double
maxHeight; // Allow the parent to control the max size of each child
const SomeBox(
this.label, {
Key key,
this.maxHeight = 45,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ConstrainedBox(
// Creates box constraints that forbid sizes larger than the given size.
constraints: BoxConstraints.loose(Size(double.infinity, maxHeight)),
child: Padding(
padding: const EdgeInsets.all(2.0),
child: Container(
decoration: BoxDecoration(
color: Colors.green,
border: Border.all(
// Make individual "child" widgets outlined
color: Colors.red,
width: 2,
),
),
key: Key(label),
child: Center(
child: Text(
label), // pass a child widget in stead to make this generic
),
),
),
);
}
}
I have a Row that is made out of either one or two elements. Depending on the latter, I want to animate the width of the first element to either be fully expanded, or to accommodate for the second element.
I am using AnimatedContainers within the row to achieve this, but there is no animation. I thought this probably has to do with Flutter not recognizing it as the same after re-rendering based on its parent's state, so I tried using a Unique Key. This however didn't work either.
class TabsBar extends StatelessWidget {
TabsBar({this.currentIndex, this.onIndexChanged, Key key}) : super(key: key);
final int currentIndex;
final Function(int) onIndexChanged;
#override
Widget build(BuildContext context) {
final tabsBloc = BlocProvider.of<TabsBarBloc>(context);
return BlocBuilder<TabsBarBloc, TabsBarBlocState>(
builder: (context, tabsBarState) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: standardSpacing),
child: Row(
children: [
Expanded(
child: AnimatedContainer(
key: key,
height: 60,
duration: Duration(milliseconds: 200),
//....//
),
//HERE: IF INDEX IS == 2, I WANT THE CONTAINER ABOVE TO ANIMATE INTO ITS NEW SIZE WHICH IS LESS WIDE TO ACCOMMODATE FOR THE CONTAINER BELOW
currentIndex == 2
? GestureDetector(
onTap: () {},
behavior: HitTestBehavior.translucent,
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
padding: const EdgeInsets.only(left: standardSpacing),
height: 60,
child: Image.asset(
'${imagesPath}add_piece.png',
),
),
)
: HorizontalSpacing(0),
],
),
),
);
});
I also tried using a Layout Builder to define the width explicitly and using an Animated Container as a parent to the Row. Neither worked.
Can anybody point me towards what I might be missing?
I don't fully understand what value of the AnimatedContainer are you trying to change but it could be easier to just animate the second container of the row and change it's width to 0 so it dissapears from the screen
class Home extends StatefulWidget {
#override
State<Home> createState() => HomeState();
}
class HomeState extends State<Home> {
int currentIndex = 2; //or you could use a boolean type
#override
Widget build(BuildContext context) {
return Column(mainAxisSize: MainAxisSize.min, children: [
MyWidget(
currentIndex: currentIndex,
),
FlatButton(
onPressed: () => setState(() {
currentIndex = currentIndex == 1 ? 2 : 1;
}),
child: Text('Change'))
]);
}
}
class MyWidget extends StatelessWidget {
final int currentIndex;
static const double standardSpacing = 8.0;
MyWidget({this.currentIndex, Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: standardSpacing),
child: Row(
children: [
Expanded(
child: Container( //Normal Container
height: 60,
color: Colors.blue)
),
GestureDetector(
onTap: () {},
behavior: HitTestBehavior.translucent,
child: AnimatedContainer(
duration: Duration(milliseconds: 200),
padding: const EdgeInsets.only(left: standardSpacing),
height: 60,
width: currentIndex == 2 ? 36 : 0, //when currentIndex is not 2, it's size its zero
child: const Icon(Icons.save),
),
)
],
),
),
);
}
}