How to make AnimatedSwitcher animate if parent widget is rebuilt in Flutter? - flutter

I am trying to implement a selectable ListTile. When ListTile is selected it's leading icon changes and its background color changes. I am trying to use AnimatedSwitcher to animate the transition when icons are changed. And it works (as long as I don't change background color of the list tile).
Changing the background color of the ListTile causes the animation to not work anymore. And I think I know why that is. When background color of the ListTile is changed, entire ListTile gets rebuilt. Which causes AnimatedSwitcher to also be rebuilt instead of transitioning between icons. Is there a way I can implement what I am trying to do. Here is my full code.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
// This widget is the root of your application.
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool change = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
body: Center(
child: SomeStatelessWidget(
AnimatedSwitcher(
duration: Duration(seconds: 2),
child: change
? CircleAvatar(
key: UniqueKey(),
child: Icon(Icons.check),
)
: CircleAvatar(
key: UniqueKey(),
child: Text("A"),
),
transitionBuilder: (child, animation) {
return RotationTransition(
turns: animation,
child: child,
);
},
),
change),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.swap_horiz),
onPressed: () {
setState(() {
change = !change;
});
},
),
floatingActionButtonLocation:
FloatingActionButtonLocation.centerFloat,
));
}
}
class SomeStatelessWidget extends StatelessWidget {
final Widget child;
final bool changed;
SomeStatelessWidget(this.child, this.changed);
#override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(8.0),
decoration: changed
? BoxDecoration(
color: Theme.of(context).selectedRowColor,
borderRadius: BorderRadius.all(Radius.circular(8.0)),
shape: BoxShape.rectangle)
: null,
child: ListTile(
leading: child,
title: Padding(
padding: const EdgeInsets.all(8.0),
child: Text("This is an example"),
),
),
);
}
}

Related

How to show next page (Stateless widget) on click only in specific Container in SplitView, not all over the page

I have TestApp, where I have SplitView with 2 horizontal Containers. By clicking button in the first container on the left(blue) I want to show new page (DetailPage widget) but not all over the page, but only in the first Container. Now it shows on the whole screen. What is a best approach to do it?
import 'package:flutter/material.dart';
import 'package:split_view/split_view.dart';
void main() {
runApp(MaterialApp(
title: 'Test',
home: TestApp(),
));
}
class TestApp extends StatelessWidget {
const TestApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: SplitView(
children: [
Container(
color: Colors.blue,
child: ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => DetailPage()));
},
child: const Text('CLICK')),
),
Container(color: Colors.yellow),
],
viewMode: SplitViewMode.Horizontal,
indicator: SplitIndicator(viewMode: SplitViewMode.Horizontal),
activeIndicator: SplitIndicator(
viewMode: SplitViewMode.Horizontal,
isActive: true,
),
controller: SplitViewController(limits: [null, WeightLimit(max: 1)]),
),
);
}
}
class DetailPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('')), body: Container(color: Colors.red));
}
}
When pushing a new page you will be overriding the old one, meaning the new page will not have a spiltView, the best way to do this is by changing the widget displayed inside of the splitView like this :
import 'package:flutter/material.dart';
import 'package:split_view/split_view.dart';
void main() {
runApp(MaterialApp(
title: 'Test',
home: TestApp(),
));
}
class TestApp extends StatefulWidget { // I have already changed the widgte to stateful here
const TestApp({Key? key}) : super(key: key);
#override
_TestAppState createState() => _TestAppState();
}
class _TestAppState extends State<TestApp> {
#override
Widget build(BuildContext context) {
bool Bool;
return MaterialApp(
home: SplitView(
children: [
if (Bool == false){
Container(
color: Colors.blue,
child: ElevatedButton(
onPressed: () {
setState(() {
Bool = !Bool; // this the method for inverting the boolean, it just gives it the opposite value
});
},
child: const Text('CLICK')),
),
}
else{
DetailPage()
},
Container(color: Colors.yellow),
],
viewMode: SplitViewMode.Horizontal,
indicator: SplitIndicator(viewMode: SplitViewMode.Horizontal),
activeIndicator: SplitIndicator(
viewMode: SplitViewMode.Horizontal,
isActive: true,
),
controller: SplitViewController(limits: [null, WeightLimit(max: 1)]),
),
);
}
}
class DetailPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('')), body: Container(color: Colors.red));
}
}
Above I defined a bool called Bool, when rendering the page it checks if Bool is false, in that case it returns the blue widget, if it is true then it returns the red one, and when you click on the button it inverts the bool and updates the page.
Please note that for updating the page you have to use setState which rebuilds the widget, and to use it you have to use a stateful widget since stateless widget is static and cannot be changed.
Also I haven't tested the code because I don't have split_view package, but you should be able to copy and paste it just fine, if you get any errors please let me know.
When you use Navigator.push your routing to a new page and creating a new state. I think you should use showGeneralDialog instead.
showGeneralDialog(
context: context,
pageBuilder: (BuildContext context,
Animation<double> animation, Animation<double> pagebuilder) {
return Align(
alignment: Alignment.centerLeft,
child: Card(
child: Container(
alignment: Alignment.topLeft,
color: Colors.amber,
//show half the screen width
width: MediaQuery.of(context).size.width / 2,
child: IconButton(
icon: const Icon(Icons.cancel),
onPressed: () {
Navigator.pop(context);
}))),
);
});
try to create new Navigator within Container:
GlobalKey<NavigatorState> _navKey = GlobalKey();
home: SplitView(
children: [
Container(
child: Navigator(
key: _navKey,
onGenerateRoute: (_) => MaterialPageRoute<dynamic>(
builder: (_) {
return Container(
color: Colors.blue,
child: ElevatedButton(
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => DetailPage()));
},
child: const Text('CLICK')),
);
},
),
),),

With Flutter, how to make a scrollable screen contain a widget that can expand or not depending on whether the soft keyboard is opened or closed?

I am a Flutter beginner and I am currently trying to implement a login screen which must satisfy to the following requirements:
first the screen is made of a widget containing the username and password text fields, that occupies all the screen
the sign in button is anchored at the bottom of the screen
when the soft keyboard is opened, the first widget is no longer expanded to take all the screen
when the soft keyboard is closed, the screen should look like as the one described in the 1st bullet point
the screen should be scrollable (when the soft keyboard is opened, if all the widgets don't fit in the remaining screen not hidden by the keyboard, I still want to scroll to access all the screen's content)
Here are wireframes that describe what I would like to achieve with Flutter:
state: soft keyboard closed
state: soft keyboard opened
Is this feasible with Flutter? Currently here is what I have attempted:
import 'package:flutter/material.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.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: 'Flutter Demo',
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> {
bool _isSoftKeyboardOpen;
#override
void initState() {
super.initState();
var keyboardVisibilityController = KeyboardVisibilityController();
_isSoftKeyboardOpen = keyboardVisibilityController.isVisible;
// Subscribe
keyboardVisibilityController.onChange.listen((bool visible) {
setState(() {
_isSoftKeyboardOpen = visible;
});
});
}
#override
Widget build(BuildContext context) {
var mAppBar = AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
);
return Scaffold(
appBar: mAppBar,
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: LayoutBuilder(
builder: (context, constraint) {
return SingleChildScrollView(
padding: EdgeInsets.only(left: 16, right: 16),
child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraint.maxHeight),
child: LayoutBuilder(
builder: (containerContext, constraint) {
return Container(
height: MediaQuery.of(containerContext).size.height - mAppBar.preferredSize.height - MediaQuery.of(context).padding.top,
color: Colors.green,
child: Column(
children: <Widget> [
Expanded(
flex: _isSoftKeyboardOpen ? 0 : 1,
child: Column(
children: <Widget> [
TextFormField(
decoration: InputDecoration(
labelText: "Username",
),
),
TextFormField(
decoration: InputDecoration(
labelText: "Password",
),
)
],
),
),
ElevatedButton(onPressed: null, child: Text("Sign in")
),
],
),
);
}
),
)
);
},
)
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
As you can see when the soft keyboard opens, the screen scrolls but there is unnecessary space below the button (which is the last element of the screen). Is there a way for me to change the screen height dynamically in my code to achieve what I want? Or is there another way to implement the sign in screen which fulfills my requirements.
You can user the below widget to gain your requirements:
return KeyboardVisibilityBuilder(
builder: (context, child, isKeyboardVisible) {
if (isKeyboardVisible) {
// build layout for visible keyboard
} else {
// build layout for invisible keyboard
}
},
child: child, // this widget goes to the builder's child property. Made for better performance.
);
Hi #Jane you can achieve the desired output using the below 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(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: LoginPage(),
);
}
}
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('SIGN IN'),
),
body: LayoutBuilder(builder: (context, constraint) {
return ListView(
shrinkWrap: true,
padding: const EdgeInsets.all(16),
children: [
GestureDetector(
onTap: () {
FocusScope.of(context).unfocus();
},
child: Container(
color: Colors.white,
height: constraint.maxHeight -
32, // -32 to remove vertical padding
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.min,
children: [
Column(
children: [
TextFormField(
decoration: InputDecoration(
labelText: "Username",
),
),
TextFormField(
decoration: InputDecoration(
labelText: "Password",
),
),
],
),
ElevatedButton(onPressed: null, child: Text("Sign in"))
],
),
),
),
],
);
}));
}
}
Also, try to use plugins only in dire situations when you can't achieve a particular task with flutter available resources.

how to get a grid element accessible in flutter by clicking on it?

is there anyone who can help me ?
I am currently on a project where i want to visualize pathfinding-algorithms by using flutter (i want to use it as app later on).
My Problem:
I have a gridPaper and it's perfectly formatted for my needs... but how can i make the single elements in it accessible by clicking on them ?
I want to create a 'wall' between the start- and endnode to make it harder for the pathfinding-algorithm. (if that makes sense)
But at first i need to create a start- end endnode as well.
Here is what i have so far:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final appTitle = 'Path Finder';
final Color gridColor = Colors.lightBlue[100];
#override
Widget build(BuildContext context) {
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends StatelessWidget {
final String title;
MyHomePage({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(title)),
body: GridPaper(
child: Container(),
color: Colors.lightBlue[100],
interval: 20,
divisions: 1,
subdivisions: 1,
),
drawer: Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Startpunkt'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
ListTile(
title: Text('Ziel'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),
);
}
}
LG Robsen
Since your GridPaper is defined with intervals of 20, it will be quite easy to use the localPosition of the details of an onTapDown callback provided by a GestureDetector on the whole GridPaper:
Full source code
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
final appTitle = 'Path Finder';
final Color gridColor = Colors.lightBlue[100];
#override
Widget build(BuildContext context) {
return MaterialApp(
title: appTitle,
home: MyHomePage(title: appTitle),
);
}
}
class MyHomePage extends HookWidget {
final double cellSize = 20.0;
final String title;
MyHomePage({
Key key,
this.title,
}) : super(key: key);
#override
Widget build(BuildContext context) {
final _activated = useState<List<Offset>>([]);
void _toggle(Offset offset) {
if (!_activated.value.remove(offset)) _activated.value.add(offset);
_activated.value = [..._activated.value];
}
return Scaffold(
appBar: AppBar(title: Text(title)),
body: GestureDetector(
onTapDown: (details) => _toggle(details.localPosition ~/ cellSize),
child: GridPaper(
child: Stack(
children: [
Container(color: Colors.white),
..._activated.value.map((offset) {
print('OFFSET: $offset');
return Positioned(
left: offset.dx * cellSize,
top: offset.dy * cellSize,
width: cellSize,
height: cellSize,
child: ColoredBox(color: Colors.green.shade200),
);
}).toList(),
],
),
color: Colors.lightBlue[100],
interval: cellSize,
divisions: 1,
subdivisions: 1,
),
),
drawer: Drawer(
// Add a ListView to the drawer. This ensures the user can scroll
// through the options in the drawer if there isn't enough vertical
// space to fit everything.
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Startpunkt'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
ListTile(
title: Text('Ziel'),
onTap: () {
// Update the state of the app
// ...
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),
);
}
}

Flutter: make container occupy other container size within a Stack

I have a flutter app in which I have a stack and within a Container with some elements. I want to give some gesture detection and show an overlay layer when the user clicks on it. I have think of creating another container with the color and shape I want and then make it occupy the same space of the other container but above it.
The problem is that I can't figure out how to make the overlay container to have the same size than the main container.
This is what I have basically:
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(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> {
bool _layerVisible = false;
#override
Widget build(BuildContext context) {
List<Widget> array = List();
array.add(Stack(
children: <Widget>[
GestureDetector(
onTapDown: (TapDownDetails details) {
setState(() {
_layerVisible = true;
});
},
onTapUp: (TapUpDetails details) {
setState(() {
_layerVisible = false;
});
},
child: Container(
padding: EdgeInsets.all(8),
color: Colors.yellow,
child: Column(
children: <Widget>[
Padding(
child: Text("Title text"),
padding: EdgeInsets.only(bottom: 10),
),
Text(
"Veeery long text. This is a dynamic value so we don't really know how long it's going to be...this means that the parent container is going to grow.")
],
)),
),
Visibility(
visible: _layerVisible,
child: Container(
color: Colors.grey,
child: Text(
"This is the container that I want to fit exactly in the yellow container."),
),
)
],
));
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
width: 300,
child: ListView(
children: array,
),
));
}
}
What I want is to make the last container within the stack to occupy the same space of the other container, this way I can create an overlay layer when I click on it.
I try using Expanded, using Positioned.fill, but none of this solution works for me.
Thanks.
Try using an IntrinsicHeight above your Stack. It'll give an explicit size based off the largest widget in the stack to the Stack itself. Then, make sure you add a fit: StackFit.passthrough, on the Stack. That'll pass down the sizing information from IntrinsicHeight to all of it's children.

Flutter Navigator, Horizontal Transiation with PageRoutebuilder

I'm trying to force a horizontal Transiation in the Flutter Navigator Widget. Navigator uses the platform Default Transition from one screen to the next. In iOS, the transition is Right to left. Pops Left to Right. In Android it is Bottom to Top. I believe solution is to use a PageRouteBuilder, but no luck getting it to work. I marked the method with the Navigator Widget that I'd like to modify with a PageRouteBuilder to get desired Horizontal transition.
Code Snippet 2, is the Transition code I have been trying to make work with no luck.
This code works as default transition.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
initialRoute: "/",
routes: {
'/Second': (context) => SecondScreen(),
},
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ButtonMaterial02(),
new Text('NAV DEMO...',),
new Text('How do I get a Horizontal Transition in Android?',),
],
),
),
// This trailing comma makes auto-formatting nicer for build methods.
);
}
//================================================================
//================================================================
Padding ButtonMaterial02() {
return Padding(
padding: const EdgeInsets.all(18.0),
child: new MaterialButton(
onPressed: MatButton02_onPress,
child: new Text("Material Button 02"),
padding: EdgeInsets.all(50.0),
minWidth: double.infinity,
color: Theme.of(context).primaryColor,
),
);
}
// add Horizontal code here
void MatButton02_onPress() {
print(" MaterialButton02 onPressed...");
Navigator.pushNamed(context, '/Second');
//*************************************************
//*************************************************
// HOW do I replace the Navigator above to use
// PageRouteBuilder so I can force ANDROID to
// Transition Right to Left instead of BottomToTop?
//*************************************************
//*************************************************
}
}
//================================================================
//================================================================
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("Second Screen"),
),
body: Center(
child: RaisedButton(
child: new Text("RETURN"),
onPressed: (){
Navigator.pop(context);
},
),
),
);
}
}
//================================================================
//================================================================
Code Snippet 2... transition code I have been trying to use.
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) {
return SlideTransition(
position: new Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: Offset.zero,
).animate(animation),
child: new SlideTransition(
position: new Tween<Offset>(
begin: Offset.zero,
end: const Offset(1.0, 0.0),
).animate(secondaryAnimation),
child: child,
),
);
},
);
Navigator.of(context).push('Second');
You're interested in using the CupertinoPageRoute.
It animates from right to left, designed to imitate iOS's transition animation.
Following the example here, replace MaterialPageRoute references with CupertinoPageRoute and you'll be set!