I'm trying to change my icon after I tap on my List Item. I already tried different things: I tried the onTap method but the icon just does not want to change. I'm very new to flutter and I would love to find some help for my problem :). Here is my code.
I already searched for solutions but I didn't got it working in my project
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'To-Do List',
theme: ThemeData(
primaryColor: Colors.white,
brightness: Brightness.dark,
),
home: Scaffold(
appBar: AppBar(title: Text('To-Do List'),
backgroundColor: Colors.amber,
),
body: BodyLayout(),
),
);
}
}
class BodyLayout extends StatefulWidget {
#override
BodyLayoutState createState() {
return new BodyLayoutState();
}
}
class BodyLayoutState extends State<BodyLayout> {
// The GlobalKey keeps track of the visible state of the list items
// while they are being animated.
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
// backing data
List<String> _data = [];
final _isdone = Set<String>();
// bool selected = false;
List<bool> selected = new List<bool>();
Icon notdone = Icon(Icons.check_box_outline_blank);
Icon done = Icon(Icons.check_box);
TextEditingController todoController = TextEditingController();
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
SizedBox(
height: 445,
child: AnimatedList(
// Give the Animated list the global key
key: _listKey,
initialItemCount: _data.length,
// Similar to ListView itemBuilder, but AnimatedList has
// an additional animation parameter.
itemBuilder: (context, index, animation) {
// Breaking the row widget out as a method so that we can
// share it with the _removeSingleItem() method.
return _buildItem(_data[index], animation);
},
),
),
TextField(
controller: todoController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'To-Do'
),
),
RaisedButton(
child: Text('Insert item', style: TextStyle(fontSize: 20)),
onPressed: () {
_insertSingleItem();
},
),
RaisedButton(
child: Text('Remove item', style: TextStyle(fontSize: 20)),
onPressed: () {
_removeSingleItem();
},
)
],
);
}
// This is the animated row with the Card.
Widget _buildItem(String item, Animation animation) {
final isdone = _isdone.contains(item);
selected.add(false);
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
trailing: Icon(
isdone ? Icons.check_box: Icons.check_box_outline_blank
),
onTap: (){
setState(() {
});
},
),
),
);
}
void _insertSingleItem() {
int insertIndex = 0;
setState(() {
_data.insert(0, todoController.text);
});
// Add the item to the data list.
// Add the item visually to the AnimatedList.
_listKey.currentState.insertItem(insertIndex);
}
void _removeSingleItem() {
int removeIndex = 0;
// Remove item from data list but keep copy to give to the animation.
String removedItem = _data.removeAt(removeIndex);
// This builder is just for showing the row while it is still
// animating away. The item is already gone from the data list.
AnimatedListRemovedItemBuilder builder = (context, animation) {
return _buildItem(removedItem, animation);
};
// Remove the item visually from the AnimatedList.
_listKey.currentState.removeItem(removeIndex, builder);
}
}```
You have already mentioned the icons above. You simply need to use them instead of declaring new ones again.
// This is the animated row with the Card.
Widget _buildItem(String item, Animation animation) {
final isdone = _isdone.contains(item);
selected.add(false);
return SizeTransition(
sizeFactor: animation,
child: Card(
child: ListTile(
title: Text(
item,
style: TextStyle(fontSize: 20),
),
trailing: isdone ? done: notdone, // use the icon variables you have already defined
onTap: (){
setState(() {
// add the item to _isdone set if it is not added and remove it if it is added when tapped on the list item
if(isdone) {
_isdone.remove(item);
} else {
_isdone.add(item);
}
});
},
),
),
);
}
In this code, I have added the item and removed the item in setSate() in the onTap(), so that whenever you tap the list item, _isdone Set gets updated and the build() is reloaded. Which makes your layout and data update itself every time you tap on the list item.
Related
So I'm relatively new to flutter and I've been trying to dynamically add Sections(TextFormFields) that are represented in a form that has Form.Helper as its child and in the process to get the saveAndValidate method to work i had to use a GlobalKey to be able to access the currentState of its so i can validate and save user input and such, but whenever i try add another Section to the screen it display this error massage
════════ Exception caught by widgets library ═══════════════════════════════════
Multiple widgets used the same GlobalKey.
════════════════════════════════════════════════════════════════════════════════
here is the code I wrote and I'd appreciate any help in solving this error please.
#1- the code for the model I used:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AddCourse with ChangeNotifier {
String? sectionName;
List<String>? sections;
List<dynamic>? addVids;
AddCourse({this.sectionName, this.sections, this.addVids});
/*where we save our values later to push them to firbase/database*/
Map<String, dynamic> toJson() {
final Map<String, dynamic> sectionData = <String, dynamic>{};
sectionData['Section #'] =
sections; // where current section number is saved and is stored dynamicly and updates as user adds more or less sections.
sectionData['Section Name'] =
sectionName; // where the input of the textformfield is saved and to be later pushed to the database and also is stored in a list so it can hold multiple section names as such.
return sectionData;
}
/* this is another model data for a functionality thats not implemented yet*/
Map<dynamic, dynamic> toJson2() {
final Map<dynamic, dynamic> vidData = <dynamic, dynamic>{};
vidData['Videos #'] = addVids;
return vidData;
}
}
#2 this the code for the form I created
import 'package:flutter/material.dart';
import 'package:snippet_coder_utils/FormHelper.dart';
import '../provider/course_add_model.dart';
class CourseCardBody extends StatefulWidget {
const CourseCardBody({
Key? key,
}) : super(key: key);
#override
State<CourseCardBody> createState() => _CourseCardBodyState();
}
class _CourseCardBodyState extends State<CourseCardBody> {
/* this is where i set up my global key that has the type of GlobalKey<FormState>*/
/*State associated with a [Form] widget. such as textformfields/forms/textfields..etc// the use of the (FormState) is to be able to Access the Functions "save"/"validate"/"reset" as to use them with forms/textformfields that you want to validate thier input or save it*/
GlobalKey<FormState> globalkey = GlobalKey();
AddCourse coursesModel = AddCourse();
#override
void initState() {
super.initState();
coursesModel.sections = List<String>.empty(growable: true);
coursesModel.sections?.add("");
// adds empty sections to the list of sections when the add button is used
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Add Courses'),
centerTitle: true,
),
body: ListView.separated(
shrinkWrap: true,
physics: const ScrollPhysics(),
itemBuilder: ((context, index) => Column(
children: [
_uiWidget(index),
Center(
// the submit button here needs some work to only be show once but for now sorry for this annoying button.
child: FormHelper.submitButton('Save', () {
if (validateAndSave()) {
print(coursesModel.toJson());
}
}),
),
],
)),
separatorBuilder: ((context, index) => const Divider()),
itemCount: coursesModel.sections!.length,
),
);
}
Widget _uiWidget(index) {
/* this form here is the parent of form fields/Formhelper widgets as seen below*/
return Form(
/* -- note here--
if we use a UniqueKey()
instead of our globalkey
here and comment the ValidateAndSave() function here
the form will work in terms of adding and removing sections
but we won't be able to either
save content/input of the user in the fields or
either validate
them so that sucks. */
/*this form is where global key is first used*/
key: globalkey,
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_sectionsContainer(index),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Flexible(
flex: 1,
fit: FlexFit.loose,
child: FormHelper.inputFieldWidgetWithLabel(
context,
'Add Section$index',
'',
'Section Title',
(onValidate) {
if (onValidate.isEmpty) {
return 'section ${index + 1} name cant be empty';
}
return null;
},
(onSavedVal) {
coursesModel.sections![index++] = index.toString();
onSavedVal = index;
},
onChange: (onChangedval) {
coursesModel.sectionName = onChangedval;
},
initialValue: coursesModel.sectionName ?? "",
borderColor: Colors.black,
borderFocusColor: Colors.black,
fontSize: 14,
labelFontSize: 14,
validationColor: Colors.redAccent,
),
),
Visibility(
visible: index == coursesModel.sections!.length - 1,
child: IconButton(
onPressed: () {
addEmailControl();
},
icon: const Icon(
Icons.add_circle,
color: Colors.greenAccent,
),
),
),
Visibility(
visible: index > 0,
child: SizedBox(
width: 35,
child: IconButton(
onPressed: () {
removeEmailControl(index);
},
icon: const Icon(
Icons.remove_circle,
color: Colors.redAccent,
),
),
),
),
],
),
],
),
),
);
}
Widget _sectionsContainer(index) {
/* the widget used to create the current section displayed on the top left of each textformfields*/
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: Text(
'Section ${index + 1}',
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
),
],
);
}
void addEmailControl() {
setState(() {
coursesModel.sections!.add('');
});
}
void removeEmailControl(index) {
setState(() {
if (coursesModel.sections!.length > 1) {
coursesModel.sections!.removeAt(index);
}
});
}
bool validateAndSave() {
/* we're especially using the <FormState> that is provided by the Globalkey to be able access the currentState of widget/form that has the global key in order to either validate or save the textformfields input or both in the same time*/
// validate each form
if (globalkey.currentState!.validate()) {
// If all data are correct then save data to out variables
// save each form
globalkey.currentState!.save();
return true;
} else {
return false;
}
}
}
I'm trying my best to figure it out on my own as I want to know how to solve this problem properly and where did I go wrong, and any help is very much appreciated thank you!
I suggest to create List<GlobalKey> variable. When you dynamically add or delete sub forms, you add or remove list items accordingly. It is impossible to use same GlobalKey for multiple widgets. So you need to create separate GlobalKeys for each form.
You may create a file of Global variables that may be shared across multiple files to ensure you are using a single instance.
Example globals.dart file
GlobalKey<SomeState> myGlobalKey = GlobalKey<SomeState>();
Example of implementation inside main.dart (or whatever file)
import './[path-to-globals]/globals.dart' // enter the appropriate path for your project
... // some code
Form(
key: myGlobalKey,
... // code
)
... // maybe more code
How can I display Image A on the user's screen if it is false or Image B if it is true, Image A is the first one that appears, when the user clicks on it, the state changes to true and switches to Image B, and switches once the user clicks on it, the state changes to true or false.
Image A = false
Image B = true
Image A - Image B
class _MyAppState extends State<MyApp> {
bool closedImage = false;
bool openImage = true;
bool switchOn = false;
void _onSwitchChanged(bool value) {
setState(() {
switchOn = false;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(scaffoldBackgroundColor: Colors.white),
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
elevation: 0,
),
body:
Center(
child: InkWell(
onTap: () {
Switch(
onChanged: _onSwitchChanged,
value: switchOn,
);
},
child: Container(
color: Colors.white,
child: ClipRRect(
child: switchOn ? Image.asset('lib/assets/closed.png') : Image.asset('lib/assets/open.png')
)
),
),
)
),
);
}
}
Just toggle the switchOn variable like this:
void _onSwitchChanged(bool value) {
setState(() {
switchOn = !switchOn;
});
}
I think your method _onSwitchChanged needs to use the incoming bool value argument (which is supplied by the Switch).
Here's a similar example showing typical usage:
import 'package:flutter/material.dart';
class SwitchFieldPage extends StatefulWidget {
#override
_SwitchFieldPageState createState() => _SwitchFieldPageState();
}
class _SwitchFieldPageState extends State<SwitchFieldPage> {
bool switchVal = false;
String monkey = 'A';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Switch Field'),
),
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Text('Monkey $monkey'),
Switch(
onChanged: (val) { // ← remember to use val (bool)
print('Switch value: $val');
setState(() {
switchVal = val; // this sets the Switch setting on/off
monkey = val ? 'B' : 'A'; // change your monkey source
});
},
value: switchVal,
)
],
),
),
),
);
}
}
You can use a GestureDetector or InkWell to detect when the user presses on the image. For updating the image, I'd suggest learning state management. To make this simple for now, we're going to use StreamBuilder.
screen.dart:
final ScreenBloc _screenBloc = ScreenBloc();
// This is inside your widget build
StreamBuilder<AuthState>(
stream: _screenBloc.pic,
initialData: false,
builder: (context, snapshot) {
return GestureDetector(
onTap: ()=> _screenBloc.toggle(),
child: snapshot.data?Image.asset('lib/assets/closed.png') : Image.asset('lib/assets/open.png'),
);
},
)
screen_bloc.dart:
class ScreenBloc{
bool _currentState=false;
StreamController<bool> _picStream = StreamController<bool>();
Stream<bool> get pic => _picStream.stream;
void toggle(){
_currentState=!_currentState;
_picStream.add(_currentState);
}
}
This is a flutter app using the stepper widget to display other widgets. I need to put two checkbox tile widgets in a row and which will be seperated by columns. I have initialized the checkbox tile widget but it keeps displaying a 'only static members can be accessed in initializers' for the onChanged and value parameters
class MyHome extends StatefulWidget {
#override
MyHomeState createState() => new MyHomeState();
}
class MyHomeState extends State<MyHome> {
void onChanged(bool value){
setState(() {
_isChecked = value;
});
}
static bool _isChecked = false;
// init the step to 0th position
int current_step = 0;
List<Step> my_steps = [
new Step(
// Title of the Step
title: new Text("Residential Data"),
// Content, it can be any widget here. Using basic Text for this example
content: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text("Activity"),
),
Row(
children: <Widget>[
Column(
children: <Widget>[
CheckboxListTile(value: _isChecked, onChanged: (bool value){setState(() {
_isChecked = value;
});})
],
),
Column(
children: <Widget>[
CheckboxListTile(value: _isChecked, onChanged: (bool value){onChanged(value);},)
],
)
],
),
Row(
children: <Widget>[
Column(
children: <Widget>[
CheckboxListTile(value: _isChecked, onChanged: (bool value){onChanged(value);})
],
),
Column(
children: <Widget>[
CheckboxListTile(value: _isChecked, onChanged: (bool value){onChanged(value);},)
],
)
],
)
],
),//new Text("Hello!"),
isActive: true),
new Step(
title: new Text("Step 2"),
content: new Text("World!"),
// You can change the style of the step icon i.e number, editing, etc.
state: StepState.editing,
isActive: true),
new Step(
title: new Text("Step 3"),
content: new Text("Hello World!"),
isActive: true),
];
#override
Widget build(BuildContext context) {
return new Scaffold(
// Appbar
appBar: new AppBar(
// Title
title: new Text("Simple Material App"),
),
// Body
body: new Container(
child: new Stepper(
// Using a variable here for handling the currentStep
currentStep: this.current_step,
// List the steps you would like to have
steps: my_steps,
// Define the type of Stepper style
// StepperType.horizontal : Horizontal Style
// StepperType.vertical : Vertical Style
type: StepperType.vertical,
// Know the step that is tapped
onStepTapped: (step) {
// On hitting step itself, change the state and jump to that step
setState(() {
// update the variable handling the current step value
// jump to the tapped step
current_step = step;
});
// Log function call
print("onStepTapped : " + step.toString());
},
onStepCancel: () {
// On hitting cancel button, change the state
setState(() {
// update the variable handling the current step value
// going back one step i.e subtracting 1, until its 0
if (current_step > 0) {
current_step = current_step - 1;
} else {
current_step = 0;
}
});
// Log function call
print("onStepCancel : " + current_step.toString());
},
// On hitting continue button, change the state
onStepContinue: () {
setState(() {
// update the variable handling the current step value
// going back one step i.e adding 1, until its the length of the step
if (current_step < my_steps.length - 1) {
current_step = current_step + 1;
} else {
current_step = 0;
}
});
// Log function call
print("onStepContinue : " + current_step.toString());
},
)),
);
}
}
You created your widget tree as a field of the class:
class Foo extends StatelessWidget {
String parameter;
Widget widget = Text(parameter); // only static members can be accessed in initializers
}
You shouldn't do this. You cannot initialize a field of an object with other class properties. Instead, create that widget inside the build method:
class Foo extends StatelessWidget {
String parameter;
#override
Widget build(BuildContext context) {
Widget widget = Text(parameter);
// TODO: do something width `widget`
}
}
In my app, I do have a list, on which I have implemented the long press selection of this post of Raouf Rahiche. When the selection is enabled I do have a different appbar, that has an IconButton on it, that should disable the selection. But I do not know how to do that.
Till now it is not working the way it should. The behaviour is displayed in the video below.
The longpress-selection is a StatefulWidget:
class _SelectableItems extends State<SelectableItems> {
bool isSelected = false;
GoogleMaterialColors googleMaterialColors = new GoogleMaterialColors();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new GestureDetector(
onLongPress: () {
setState(() {
isSelected = !isSelected;
});
widget.callback();
},
onTap: () {
setState(() {
isSelected = !isSelected;
});
if (widget.longPressEnabled) {
widget.callback();
} else {
Navigator.push(
context,
MaterialPageRoute(builder: (context)=>RecipeDetails(widget.name))
);
}
},
child: ListTile(
leading: CircleAvatar(
child: (isSelected
? Icon(
Icons.check,
color: Colors.white,
)
: (widget.image != "no image"
? Container(
width: 40.0,
height: 40.0,
decoration: new BoxDecoration(
image: new DecorationImage(
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.2), BlendMode.darken),
image: AssetImage(widget.image),
fit: BoxFit.cover,
),
borderRadius: new BorderRadius.all(new Radius.circular(50.0)),
),
)
: Text(
widget.name[0].toUpperCase(),
style: TextStyle(
color: Colors.white,
fontSize: 21.0,
fontWeight: FontWeight.w400
),
)
)
),
backgroundColor: (isSelected
? googleMaterialColors.primaryColor()
: widget.color.withOpacity(1.00)
)
),
title: Padding(
padding: EdgeInsets.only(top: 25.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
widget.title
],
),
),
),
);
}
}
I am calling this widget inside a SideHeaderListView like this:
bool longPressFlag = false;
List<String> indexList = new List();
//other code
return SideHeaderListView(
hasSameHeader: (int a, int b){
return snapshot.data[a].name[0] == snapshot.data[b].name[0];
},
itemCount: snapshot.data.length,
headerBuilder: (BuildContext context, int index){
return new Padding(
padding: EdgeInsets.only(top: 30.0, left: 20.0, right: 25.0),
child: Container(
width: 10.0,
child: Text(
snapshot.data[index].name[0].toUpperCase(),
style: TextStyle(
color: googleMaterialColors.primaryColor().withGreen(120),
fontFamily: "Google-Sans",
fontSize: 15.0,
fontWeight: FontWeight.w600
),
),
),
);
},
itemExtend: 70.0,
itemBuilder: (BuildContext context, int index){
Color usedColor = convertColor.convertToColor(snapshot.data[index].backgroundColor);
String image = snapshot.data[index].image;
return SelectableItems(
color: usedColor,
name: snapshot.data[index].name,
title: (searchController.text.isEmpty
? Text(snapshot.data[index].name)
: recipeName(searchCondition, snapshot.data[index].name)
),
index: index,
image: image,
longPressEnabled: longPressFlag,
//isSelected: selectedFlag,
callback: () {
if (indexList.contains(snapshot.data[index].name)) {
indexList.remove(snapshot.data[index].name);
} else {
indexList.add(snapshot.data[index].name);
}
longPress();
},
);
},
);
void longPress() {
setState(() {
if (indexList.length == 0) {
longPressFlag = false;
} else {
longPressFlag = true;
}
});
}
I hope somebody would be able to solve my problem. Thanks in advance.
The first thing is that you should add each item a key in constructor like this :
MyItem({Key key}): super(key: key);
Why a key ?
A key allow you to identify your widget correctly.
See in doc :
A new widget will only be used to update an existing element if its
key is the same as the key of the current widget associated with the
element.
Create a GlobalKey (a GLobal key extends Key)
For each item to access the widget from, create a global key.
From the doc :
A key that is unique across the entire app. Global keys uniquely
identify elements. Global keys provide access to other objects that
are associated with elements, such as the a [BuildContext] and, for
[StatefulWidget]s, a [State].
Add in the code the creation of a global key for each item (in your SelectableItem for you) :
...
var key = new GlobalKey<SelectableItem >();
this.items.put(position, key);
return new SelectableItem(key: key,...);
Items is a map where you can save position and Global Key.
Now when you want to select a View from the parent just access the globalKey from the map of items and access the widget to do what you want.(update, uncheck, etc...)
Edit : exemple :
class SideHeaderListView {
Map<int, GlobalKey<_SelectableItems>> map = new Map();
create() {
for (int i = 0; i< 10; i++) {
var key = new GlobalKey<_SelectableItems>();
var item = new SelectableItems(key: key);
map.putIfAbsent(i, () => key);
}
}
redrawItem(int i) {
var widget = this.map[i].currentState;
widget.redraw();
}
}
class SelectableItems extends StatefulWidget {
SelectableItems({key: Key}) : super(key: key);
#override
State<StatefulWidget> createState() {
return new _SelectableItems();
}
}
class _SelectableItems extends State<SelectableItems> {
#override
Widget build(BuildContext context) {
return new Text("test");
}
redraw() {
setState(() {
});
}
}
You have commented part of code - //isSelected: selectedFlag,
I think, you have to add this field to your widget
class SelectableItems extands StatefulWidget {
SelectableItems({this.isSelected = false});
final bool isSelected;
...
class _SelectableItems extends State<SelectableItems> {
bool isSelected;
#override
void initState() {
isSelected = widget.isSelected ?? false;
super.initState();
}
....
And when you're creating list of items:
return SelectableItems(
...
isSelected: indexList.contains(snapshot.data[index].name)
I think this could work
in the Codelab English words example...
https://flutter.io/get-started/codelab/
The iOS Navigation transition is horizontal.. as you would expect a Segue to act in a UINavigationController. Right to left... Pops are left to right.
ANDROID, the same example is VERTICAL, Bottom to Top. Pops are Top to bottom.
MY QUESTION... how would I force a Horizontal transition in ANDROID so it behaves like iOS? I suspect I will have to use MaterialPageRoute
/*
Nguyen Duc Hoang(Mr)
Programming tutorial channel:
https://www.youtube.com/c/nguyenduchoang
Flutter, React, React Native, IOS development, Swift, Python, Angular
* */
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
//Define "root widget"
void main() => runApp(new MyApp());//one-line function
//StatefulWidget
class RandomEnglishWords extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return new RandomEnglishWordsState();//return a state's object. Where is the state's class ?
}
}
//State
class RandomEnglishWordsState extends State<RandomEnglishWords>{
final _words = <WordPair>[];//Words displayed in ListView, 1 row contains 1 word
final _checkedWords = new Set<WordPair>();//set contains "no duplicate items"
#override
Widget build(BuildContext context) {
// TODO: implement build
//Now we replace this with a Scaffold widget which contains a ListView
return new Scaffold(
appBar: new AppBar(
title: new Text("List of English words"),
actions: <Widget>[
new IconButton(icon: new Icon(Icons.list),
onPressed: _pushToSavedWordsScreen)
],
),
body: new ListView.builder(itemBuilder: (context, index) {
//This is an anonymous function
//index = 0, 1, 2, 3,...
//This function return each Row = "a Widget"
if (index >= _words.length) {
_words.addAll(generateWordPairs().take(10));
}
return _buildRow(_words[index], index);//Where is _buildRow ?
}),
);
}
_pushToSavedWordsScreen() {
// print("You pressed to the right Icon");
//To navigate, you must have a "route"
final pageRoute = new MaterialPageRoute(builder: (context) {
//map function = Convert this list to another list(maybe different object's type)
//_checkedWords(list of WordPair) => map =>
// converted to a lazy list(Iterable) of ListTile
final listTiles = _checkedWords.map( (wordPair) {
return new ListTile(
title: new Text(wordPair.asUpperCase,
style: new TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),),
);
});
//Now return a widget, we choose "Scaffold"
return new Scaffold(
appBar: new AppBar(
title: new Text("Checked words"),
),
body: new ListView(children: listTiles.toList(),),//Lazy list(Iterable) => List
);
});
Navigator.of(context).push(pageRoute);
}
Widget _buildRow(WordPair wordPair, int index) {
//This widget is for each row
final textColor = index % 2 == 0 ? Colors.red : Colors.blue;
final isChecked = _checkedWords.contains(wordPair);
return new ListTile(
//leading = left, trailing = right. Is is correct ? Not yet
leading: new Icon(
isChecked ? Icons.check_box : Icons.check_box_outline_blank,
color: textColor,
),
title: new Text(
wordPair.asUpperCase,
style: new TextStyle(fontSize: 18.0, color: textColor),
),
onTap: () {
setState(() {
//This is an anonymous function
if (isChecked) {
_checkedWords.remove(wordPair);//Remove item in a Set
} else {
_checkedWords.add(wordPair);//Add item to a Set
}
});
},
);
}
}
class MyApp extends StatelessWidget {
//Stateless = immutable = cannot change object's properties
//Every UI components are widgets
#override
Widget build(BuildContext context) {
//build function returns a "Widget"
return new MaterialApp(
title: "This is my first Flutter App",
home: new RandomEnglishWords()
);//Widget with "Material design"
}
}
First of all about MaterialPageRoute does not help with your case. Here is the official explanation for it:
The MaterialPageRoute is handy because it transitions to the new
screen using a platform-specific animation.
And those animations you see are the platform-specific animations.
If you want to implement a custom animation, you need to implement it manually by using PageRouteBuilder. Here is how you can do it.
Here is a modified version of your _pushToSavedWordsScreen which does the right to left transition. Tested on Google Pixel.
final pageRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
// YOUR WIDGET CODE HERE
final listTiles = _checkedWords.map((wordPair) {
return new ListTile(
title: new Text(
wordPair.asUpperCase,
style: new TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
);
});
//Now return a widget, we choose "Scaffold"
return new Scaffold(
appBar: new AppBar(
title: new Text("Checked words"),
),
body: new ListView(
children: listTiles.toList(),
), //Lazy list(Iterable) => List
);
},
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(pageRoute);
This is the modified code for the new Navigation. Android and iOS both Navigate Horizontally.
Origianl code: https://flutter.io/get-started/codelab/[1]
pubspec.yaml... Don't forget to add :
dependencies:
flutter:
sdk: flutter
english_words: ^3.1.0 #version 3.1.0 or above
UPDATED CODE: main.dart.
/*
Nguyen Duc Hoang(Mr)
Programming tutorial channel:
https://www.youtube.com/c/nguyenduchoang
Flutter, React, React Native, IOS development, Swift, Python, Angular
* */
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
//Define "root widget"
void main() => runApp(new MyApp());//one-line function
//StatefulWidget
class RandomEnglishWords extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return new RandomEnglishWordsState();//return a state's object. Where is the state's class ?
}
}
//State
class RandomEnglishWordsState extends State<RandomEnglishWords> {
final _words = <WordPair>[
]; //Words displayed in ListView, 1 row contains 1 word
final _checkedWords = new Set<WordPair>(); //set contains "no duplicate items"
#override
Widget build(BuildContext context) {
// TODO: implement build
//Now we replace this with a Scaffold widget which contains a ListView
return new Scaffold(
appBar: new AppBar(
title: new Text("List of English words"),
actions: <Widget>[
new IconButton(icon: new Icon(Icons.list),
onPressed: _pushToSavedWordsScreen)
],
),
body: new ListView.builder(itemBuilder: (context, index) {
//This is an anonymous function
//index = 0, 1, 2, 3,...
//This function return each Row = "a Widget"
if (index >= _words.length) {
_words.addAll(generateWordPairs().take(10));
}
return _buildRow(_words[index], index); //Where is _buildRow ?
}),
);
}
_pushToSavedWordsScreen() {
// print("You pressed to the right Icon");
//To navigate, you must have a "route"
//======================================================================
//======= original solution - ANDROID transitions Vertically
// final pageRoute = new MaterialPageRoute(builder: (context) {
// //map function = Convert this list to another list(maybe different object's type)
// //_checkedWords(list of WordPair) => map =>
// // converted to a lazy list(Iterable) of ListTile
// final listTiles = _checkedWords.map( (wordPair) {
// return new ListTile(
// title: new Text(wordPair.asUpperCase,
// style: new TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),),
// );
// });
// //Now return a widget, we choose "Scaffold"
// return new Scaffold(
// appBar: new AppBar(
// title: new Text("Checked words"),
// ),
// body: new ListView(children: listTiles.toList(),),//Lazy list(Iterable) => List
// );
// });
// Navigator.of(context).push(pageRoute);
// }
//=========== OLD solution... ANDROID transitions Vertically
//==================================================================
//==================================================================
//=========== new solution... transition Horizontal
final pageRoute = new PageRouteBuilder(
pageBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation) {
// YOUR WIDGET CODE HERE
final listTiles = _checkedWords.map((wordPair) {
return new ListTile(
title: new Text(
wordPair.asUpperCase,
style: new TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold),
),
);
});
//Now return a widget, we choose "Scaffold"
return new Scaffold(
appBar: new AppBar(
title: new Text("Checked words"),
),
body: new ListView(
children: listTiles.toList(),
), //Lazy list(Iterable) => List
);
},
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(pageRoute);
}
//========= end of solution
//=============================================================
Widget _buildRow(WordPair wordPair, int index) {
//This widget is for each row
final textColor = index % 2 == 0 ? Colors.red : Colors.blue;
final isChecked = _checkedWords.contains(wordPair);
return new ListTile(
//leading = left, trailing = right. Is is correct ? Not yet
leading: new Icon(
isChecked ? Icons.check_box : Icons.check_box_outline_blank,
color: textColor,
),
title: new Text(
wordPair.asUpperCase,
style: new TextStyle(fontSize: 18.0, color: textColor),
),
onTap: () {
setState(() {
//This is an anonymous function
if (isChecked) {
_checkedWords.remove(wordPair); //Remove item in a Set
} else {
_checkedWords.add(wordPair); //Add item to a Set
}
});
},
);
}
}
class MyApp extends StatelessWidget {
//Stateless = immutable = cannot change object's properties
//Every UI components are widgets
#override
Widget build(BuildContext context) {
//build function returns a "Widget"
return new MaterialApp(
title: "This is my first Flutter App",
home: new RandomEnglishWords()
);//Widget with "Material design"
}
}