Flutter - Poor app performance with multiple TextFields inside a Table - flutter

I'm building an app where there is a scrollable Table composed of columns of TexField.
When I try to edit a field or scroll through the table, I notice a huge performance hit. How can I solve this problem?
Below is a sample app to test my problem
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: App(),
),
);
}
}
class App extends StatelessWidget {
List<Item> items = List.generate(3 * 48, (index) => Item(index));
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Table(
border: TableBorder.all(),
defaultColumnWidth: FixedColumnWidth(100),
children: [
TableRow(
children: items
.map((item) =>
SizedBox(height: 48, child: Text(item.value.toString())))
.toList()),
...List.generate(
6,
(index) => TableRow(
children: items
.map((item) => SizedBox(
height: 48,
child: CellTextField(item: item, onChange: (_) {})))
.toList()))
],
),
);
}
}
class Item {
int value;
Item(this.value);
}
class CellTextField extends StatelessWidget {
final Item item;
final Function(num newValue) onChange;
CellTextField({Key? key, required this.item, required this.onChange})
: super(key: key);
#override
Widget build(BuildContext context) {
return TextField(
controller: TextEditingController(text: item.value.toString())
..selection =
TextSelection.collapsed(offset: item.value.toString().length),
onChanged: (newValue) {
try {
final _newValue = int.parse(newValue);
if (item.value != _newValue) {
onChange(_newValue);
}
} catch (e) {}
},
decoration: InputDecoration(
suffixText: '€',
),
);
}
}
EDIT: https://github.com/flutter/flutter/issues/100536

Related

'RenderBox was not laid out' when using ExpansionPanelList

I want to show a GridView on the left and a ExpansionPanelList on the right. I put them in a Row Widget but it did not work, error is :
Assertion failed:
D:\…\rendering\box.dart:1929
hasSize
"RenderBox was not laid out: RenderRepaintBoundary#bb5e9 NEEDS-LAYOUT NEEDS-PAINT"
The relevant error-causing widget was
Row
lib\main.dart:69
this is all my code in main.dart:
// ignore_for_file: prefer_const_constructors
// ignore_for_file: prefer_const_literals_to_create_immutables
import 'package:flutter/material.dart';
void main() => runApp(MyApp(UniqueKey()));
class MyApp extends StatelessWidget {
const MyApp(Key key) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(UniqueKey()),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage(Key key) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
List<int> getDataList() {
List<int> list = [];
for (int i = 0; i < 96; i++) {
list.add(i);
}
return list;
}
List<Widget> getWidgetList() {
return getDataList().map((item) => getItemContainer(item)).toList();
}
var i = 0;
Widget getItemContainer(int item) {
return Block(item);
}
//创建gridview
Widget buildGrid() {
return GridView.count(
//水平子Widget之间间距
crossAxisSpacing: 10.0,
//垂直子Widget之间间距
mainAxisSpacing: 30.0,
//GridView内边距
padding: EdgeInsets.all(10.0),
//一行的Widget数量
crossAxisCount: 4,
//子Widget宽高比例
childAspectRatio: 2.0,
//子Widget列表
children: getWidgetList(),
);
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("this is title"),
),
body: Container(
child: Row(
children: <Widget>[
Expanded(
child: buildGrid(),
),
ExpansionPanelPage(UniqueKey()),
],
)),
);
}
}
class Block extends StatelessWidget {
final int itemNo;
const Block(this.itemNo, {Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Listener(
onPointerHover: (event) => print(itemNo.toString()),
onPointerMove: (event) => print("aa" + itemNo.toString()),
child: Container(
alignment: Alignment.center,
child: Text(
"item",
style: TextStyle(color: Colors.white, fontSize: 20),
),
color: Colors.blue,
),
);
}
}
// stores ExpansionPanel state information
class Item {
Item({
required this.expandedValue,
required this.headerValue,
this.isExpanded = false,
});
String expandedValue;
String headerValue;
bool isExpanded;
}
List<Item> generateItems(int numberOfItems) {
return List.generate(numberOfItems, (int index) {
return Item(
headerValue: 'Panel $index',
expandedValue: 'This is item number $index',
);
});
}
class ExpansionPanelPage extends StatefulWidget {
ExpansionPanelPage(Key key) : super(key: key);
#override
_ExpansionPanelPageState createState() => _ExpansionPanelPageState();
}
class _ExpansionPanelPageState extends State<ExpansionPanelPage> {
List<Item> _data = generateItems(1);
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
width: 10,
height: 10,
child: _buildPanel(),
),
);
}
Widget _buildPanel() {
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_data[index].isExpanded = !isExpanded;
});
},
children: _data.map<ExpansionPanel>((Item item) {
return ExpansionPanelRadio(
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
body: Column(
children :<Widget>[
Text("1"),
Text("1"),
Text("1"),
Text("1"),
]
),
value: item.headerValue,
);
}).toList(),
);
}
}
..........................................................................
..........................................................................
I know it might be a bit late to answer your question but for those who are facing the same issue, try wrapping ExpansionPanelList with the SingleChildScrollView widget. It helps your panel list to take all available space and to shrink-wrap in both axes. Check the docs for more info!

Flutter Scroll view to focused widget on a column

I'm developing an app for Android TV, and use DPAD navigation.
I have multiple widgets inside a column. when i navigate to a widget which is outside the view, the widget/view is not moving to reflect the selected widget.
// ignore_for_file: avoid_print
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatelessWidget(),
),
);
}
}
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final TextTheme textTheme = Theme.of(context).textTheme;
return DefaultTextStyle(
style: textTheme.headline4!,
child: ChangeNotifierProvider<SampleNotifier>(
create: (context) => SampleNotifier(), child: const CardHolder()),
);
}
}
class CardHolder extends StatefulWidget {
const CardHolder({Key? key}) : super(key: key);
#override
_CardHolderState createState() => _CardHolderState();
}
class _CardHolderState extends State<CardHolder> {
late FocusNode _focusNode;
late FocusAttachment _focusAttachment;
#override
void initState() {
super.initState();
_focusNode = FocusNode(debugLabel: "traversal_node");
_focusAttachment = _focusNode.attach(context, onKey: _handleKeyPress);
_focusNode.requestFocus();
}
#override
Widget build(BuildContext context) {
_focusAttachment.reparent();
return Focus(
focusNode: _focusNode,
autofocus: true,
onKey: _handleKeyPress,
child: Consumer<SampleNotifier>(
builder: (context, models, child) {
int listSize = Provider.of<SampleNotifier>(context).listSize;
return SingleChildScrollView(
child: SampleRow(cat: "Test", models: models.modelList),
);
},
),
);
}
KeyEventResult _handleKeyPress(FocusNode node, RawKeyEvent event) {
if (event is RawKeyDownEvent) {
print("t:FocusNode: ${node.debugLabel} event: ${event.logicalKey}");
if (event.logicalKey == LogicalKeyboardKey.arrowRight) {
Provider.of<SampleNotifier>(context, listen: false).moveRight();
return KeyEventResult.handled;
} else if (event.logicalKey == LogicalKeyboardKey.arrowLeft) {
Provider.of<SampleNotifier>(context, listen: false).moveLeft();
return KeyEventResult.handled;
}
}
// debugDumpFocusTree();
return KeyEventResult.ignored;
}
}
class SampleCard extends StatefulWidget {
final int number;
final SampleModel model;
final bool focused;
const SampleCard(
{required this.number,
required this.focused,
required this.model,
Key? key})
: super(key: key);
#override
_SampleCardState createState() => _SampleCardState();
}
class _SampleCardState extends State<SampleCard> {
late Color _color;
#override
void initState() {
super.initState();
_color = Colors.red.shade900;
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: widget.focused
? Container(
width: 150,
height: 300,
color: Colors.white,
child: Center(
child: Text(
"${widget.model.text} ${widget.model.num}",
style: TextStyle(color: _color),
),
),
)
: Container(
width: 150,
height: 300,
color: Colors.black,
child: Center(
child: Text(
"${widget.model.text} ${widget.model.num}",
style: TextStyle(color: _color),
),
),
),
);
}
}
class SampleRow extends StatelessWidget {
final String cat;
final List<SampleModel> models;
SampleRow({Key? key, required this.cat, required this.models}) : super(key: key);
#override
Widget build(BuildContext context) {
final int selectedIndex =
Provider.of<SampleNotifier>(context).selectedIndex;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(left: 16, bottom: 8),
),
models.isNotEmpty
? SizedBox(
height: 200,
child: ListView.custom(
padding: const EdgeInsets.all(8),
scrollDirection: Axis.horizontal,
childrenDelegate: SliverChildBuilderDelegate(
(context, index) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: SampleCard(
focused: index == selectedIndex,
model: models[index],
number: index,
),
),
childCount: models.length,
findChildIndexCallback: _findChildIndex,
),
),
)
: SizedBox(
height: 200,
child: Container(
color: Colors.teal,
),
)
],
);
}
int _findChildIndex(Key key) => models.indexWhere((model) =>
"$cat-${model.text}_${model.num}" == (key as ValueKey<String>).value);
}
class SampleNotifier extends ChangeNotifier {
final List<SampleModel> _models = [
SampleModel(0, "zero"),
SampleModel(1, "one"),
SampleModel(2, "two"),
SampleModel(3, "three"),
SampleModel(4, "four"),
SampleModel(5, "five"),
SampleModel(6, "six"),
SampleModel(7, "seven"),
SampleModel(8, "eight"),
SampleModel(9, "nine"),
SampleModel(10, "ten")
];
int _selectedIndex = 0;
List<SampleModel> get modelList => _models;
int get selectedIndex => _selectedIndex;
int get listSize => _models.length;
void moveRight() {
if (_selectedIndex < _models.length - 1) {
_selectedIndex = _selectedIndex + 1;
}
notifyListeners();
}
void moveLeft() {
if (_selectedIndex > 0) {
_selectedIndex = _selectedIndex - 1;
}
notifyListeners();
}
}
class SampleModel {
int num;
String text;
SampleModel(this.num, this.text);
}
I need a way to move/scroll the widget into view. Is there any way to do this, using the DPAD navigation on android tv
Here is the gist
You could use the scrollable_positioned_list package.
Instead of a ListView.custom which scrolls based on pixels, this widgets its based on index:
final ItemScrollController itemScrollController = ItemScrollController();
ScrollablePositionedList.builder(
itemCount: 500,
itemBuilder: (context, index) => Text('Item $index'),
itemScrollController: itemScrollController,
itemPositionsListener: itemPositionsListener,
);
So you could maintain an index of the current scroll position and on DPAD press just :
itemScrollController.jumpTo(index: currentItem);
setState((){currentItem++;})

Flutter web tabbar scroll issue with non primary scrollcontroller

In continuation with question
The solution provided above is good. But hard for me to implement in my project.
Expected results:
I've created two tabs.
In each tab I have SingleChildScrollView wrapped with Scrollbar.
I can not have the primary scrollcontroller in both the tabs, because that throws me exception: "ScrollController attached to multiple scroll views."
For Tab ONE I use primary scrollcontroller, for Tab TWO I created Scrollcontroller and attached it.
Widgets in both the tabs should be scrollabale using keyboard and mouse.
Actual results:
For Tab ONE with primary scrollcontroller I can scroll both by keyboard and dragging scrollbar.
But for Tab TWO with non primary scrollcontroller, I have to scroll only by dragging scrollbar. This tab doesn't respond to keyboard page up /down keys.
When keyboard keys are used in Tab TWO actually contents of tab ONE are getting scrolled.
Check 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(
home: TabExample(),
);
}
}
class TabExample extends StatefulWidget {
const TabExample({Key key}) : super(key: key);
#override
_TabExampleState createState() => _TabExampleState();
}
class _TabExampleState extends State<TabExample> {
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(icon: Text('Tab ONE')),
Tab(icon: Text('Tab TWO')),
],
),
title: Text('Tabs Demo'),
),
body: TabBarView(
children: [
WidgetC(),
WidgetD(),
],
),
),
);
}
}
class WidgetC extends StatefulWidget {
const WidgetC({Key key}) : super(key: key);
#override
_WidgetCState createState() => _WidgetCState();
}
class _WidgetCState extends State<WidgetC>
with AutomaticKeepAliveClientMixin<WidgetC> {
List<Widget> children;
#override
void initState() {
children = [];
for (int i = 0; i < 20; i++) {
children.add(
Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Container(
height: 100,
width: double.infinity,
color: Colors.blue,
child: Center(child: Text('$i')),
),
),
);
}
super.initState();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Scrollbar(
key: PageStorageKey('WidgetC'),
isAlwaysShown: true,
showTrackOnHover: true,
child: SingleChildScrollView(
child: Column(
children: children,
),
),
);
}
#override
bool get wantKeepAlive => true;
}
class WidgetD extends StatefulWidget {
const WidgetD({Key key}) : super(key: key);
#override
_WidgetDState createState() => _WidgetDState();
}
class _WidgetDState extends State<WidgetD>
with AutomaticKeepAliveClientMixin<WidgetD> {
List<Widget> children;
ScrollController _scrollController;
#override
void initState() {
_scrollController = ScrollController();
children = [];
for (int i = 0; i < 20; i++) {
children.add(
Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Container(
height: 100,
width: double.infinity,
color: Colors.green,
child: Center(child: Text('$i')),
),
),
);
}
super.initState();
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Scrollbar(
key: PageStorageKey('WidgetD'),
isAlwaysShown: true,
showTrackOnHover: true,
controller: _scrollController,
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: children,
),
),
);
}
#override
bool get wantKeepAlive => true;
}
This has been accepted as a bug in flutter.
Pl follow for progress here: https://github.com/flutter/flutter/issues/83711
Note for other developers facing same issue.
To overcome the mentioned problem, I changed my design layout. Instead of tabbar view I used Navigationrail widget. This solved my problem.
NavigationRail widget allowed me to attach primary scroll controller to multiple widgets without giving me exception: "ScrollController attached to multiple scroll views."
Sample code.
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// This is the main application widget.
class MyApp extends StatelessWidget {
const MyApp({Key key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return const MaterialApp(
title: _title,
home: MyStatefulWidget(),
);
}
}
/// This is the stateful widget that the main application instantiates.
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
/// This is the private State class that goes with MyStatefulWidget.
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int _selectedIndex = 0;
WidgetC _widgetC = WidgetC();
WidgetD _widgetD = WidgetD();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('NavigationRail Demo'), centerTitle: true),
body: Row(
children: <Widget>[
NavigationRail(
elevation: 8.0,
selectedIndex: _selectedIndex,
onDestinationSelected: (int index) {
setState(() {
_selectedIndex = index;
});
},
labelType: NavigationRailLabelType.all,
groupAlignment: 0.0,
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.favorite_border),
selectedIcon: Icon(Icons.favorite),
label: Text('Tab ONE'),
),
NavigationRailDestination(
icon: Icon(Icons.bookmark_border),
selectedIcon: Icon(Icons.book),
label: Text('Tab TWO'),
),
],
),
const VerticalDivider(thickness: 1, width: 1),
// This is the main content.
Expanded(
child: _getPageAtIndex(_selectedIndex),
)
],
),
);
}
Widget _getPageAtIndex(int index) {
switch (index) {
case 0:
return _widgetC;
case 1:
return _widgetD;
}
return Container();
}
}
class WidgetC extends StatefulWidget {
const WidgetC({Key key}) : super(key: key);
#override
_WidgetCState createState() => _WidgetCState();
}
class _WidgetCState extends State<WidgetC>
with AutomaticKeepAliveClientMixin<WidgetC> {
List<Widget> children;
#override
void initState() {
children = [];
for (int i = 0; i < 20; i++) {
children.add(
Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Container(
height: 100,
width: double.infinity,
color: Colors.primaries[Random().nextInt(Colors.primaries.length)],
child: Center(child: Text('$i')),
),
),
);
}
super.initState();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Scrollbar(
key: PageStorageKey('WidgetC'),
isAlwaysShown: true,
showTrackOnHover: true,
child: SingleChildScrollView(
child: Column(
children: children,
),
),
);
}
#override
bool get wantKeepAlive => true;
}
class WidgetD extends StatefulWidget {
const WidgetD({Key key}) : super(key: key);
#override
_WidgetDState createState() => _WidgetDState();
}
class _WidgetDState extends State<WidgetD>
with AutomaticKeepAliveClientMixin<WidgetD> {
List<Widget> children;
// ScrollController _scrollController;
#override
void initState() {
// _scrollController = ScrollController();
children = [];
for (int i = 0; i < 20; i++) {
children.add(
Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Container(
height: 100,
width: double.infinity,
color: Colors.primaries[Random().nextInt(Colors.primaries.length)],
child: Center(child: Text('$i')),
),
),
);
}
super.initState();
}
#override
void dispose() {
// _scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
return Scrollbar(
key: PageStorageKey('WidgetD'),
isAlwaysShown: true,
showTrackOnHover: true,
// controller: _scrollController,
child: SingleChildScrollView(
// controller: _scrollController,
child: Column(
children: children,
),
),
);
}
#override
bool get wantKeepAlive => true;
}

Access List from statefulwidget to state

I want to pass a List from screen 1 to screen 2 statefulwidget and want to add data to it.
List type Question,
class Question {
String questionText;
String answerText;
Question({this.questionText, this.answerText});
}
I passed the list to 2nd screen
class CardPage extends StatefulWidget {
final List<Question> questionBank;
CardPage({#required this.questionBank});
#override
......
I added the content to the list from state,
TextField(onChanged: (text) {question = text;}),
TextField(onChanged: (text) {answer = text;}),
FlatButton(
child: Text("Create"),
onPressed: () {setState(() {
questionBank.add(Question(questionText: question, answerText: answer));});
}
)
Bt I don't know how to connect the List in stateful widget to the state to access it. I know there is widget for it but don't know how to completely import the list to state with it.
Anyone help me
You can pass a function instead of list to your CardPage. It should be called when you create a new question. I think it is the most simple solution.
You CardPage should be like this:
class CardPage extends StatefulWidget {
final Function(Question) createQuestion;
CardPage({Key key, #required this.createQuestion}) : super(key: key);
#override
State<StatefulWidget> createState() => _CardPageState();
}
class _CardPageState extends State<CardPage> {
String _question = '';
String _answer = '';
#override
Widget build(BuildContext context) {
return Column(children: [
TextField(onChanged: (text) {
_question = text;
}),
TextField(onChanged: (text) {
_answer = text;
}),
FlatButton(
child: Text("Create"),
onPressed: () {
setState(() {
final question =
Question(questionText: _question, answerText: _answer);
widget.createQuestion(question);
});
})
]);
}
}
Question lists owner state should be like this:
class _FirstWidgetState extends State<FirstWidget> {
final List<Question> questionBank = [];
#override
Widget build(BuildContext context) {
return ...
CardPage(createQuestion: _createQuestion);
...
}
void _createQuestion(Question question) {
setState(() {
questionBank.add(question);
});
}
}
Try this
simple demo.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: MyApp(),
));
}
class MyApp extends StatefulWidget {
#override
_State createState() => _State();
}
class _State extends State<MyApp> {
final List<String> names = <String>['apple', 'samsung', 'shirsh'];
TextEditingController nameController = TextEditingController();
void addItemToList(){
setState(() {
names.insert(0,nameController.text);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Example'),
),
body: Column(
children: <Widget>[
Row(
children: [
Expanded(
flex: 5,
child: Padding(
padding: EdgeInsets.all(20),
child: TextField(
controller: nameController,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Contact Name',
),
),
),
),
Expanded(
flex: 2,
child: RaisedButton(
child: Text('Add Item'),
onPressed: () {
if(nameController.text.toString()!="")
addItemToList();
},
),
)
],
),
Expanded(
child: ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: names.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
margin: EdgeInsets.all(2),
color: Colors.cyan,
child: Center(
child: Text('${names[index]}',
style: TextStyle(fontSize: 18),
)
),
);
}
)
)
]
)
);
}
}

statfulWidget with key concept

i am studying key in flutter. and in explanation, when i want swap widget in statefulWidget i need to add key value. because when flutter check element structure if type, state are not same they don't response. this is how i understand.
void main() => runApp(new MaterialApp(home: PositionedTiles()));
class PositionedTiles extends StatefulWidget {
#override
State<StatefulWidget> createState() => PositionedTilesState();
}
class PositionedTilesState extends State<PositionedTiles> {
List<Widget> tiles = [
StatefulColorfulTile(key: UniqueKey()), // Keys added here
StatefulColorfulTile(key: UniqueKey()),
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(children: tiles),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles),
);
}
swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}
}
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key key}) : super(key: key); // NEW CONSTRUCTOR
#override
ColorfulTileState createState() => ColorfulTileState();
}
class ColorfulTileState extends State<ColorfulTile> {
Color myColor;
#override
void initState() {
super.initState();
myColor = UniqueColorGenerator.getColor();
}
#override
Widget build(BuildContext context) {
return Container(
color: myColor,
child: Padding(
padding: EdgeInsets.all(70.0),
));
}
}
but i saw this code.
Widget build(BuildContext context) {
return Column(
children: [
value
? const SizedBox()
: const Placeholder(),
GestureDetector(
onTap: () {
setState(() {
value = !value;
});
},
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
!value
? const SizedBox()
: const Placeholder(),
],
);
}
this code is also use statefulWidget. in this code when user taps Box it's changed but i think there're no key value and in element structure there are different type(one is SizedBox and the other is placeHolder) so i think there aren't changed. why they're changed? what i misunderstand?