Flutter TextField does not follow ListTiles within ReorderableListView - flutter

I'm trying to use a TextFormField within a ReorderableListView and can't seem to get the text to follow the moving ListTile. Below is a minimally modified copy of the ReorderableListView sample code that reproduces the issue. All that's added is a list of Strings and a TextFormField per ListTile.
The example includes a Text widget that tracks with the movement of the ListTile. The difference I see with Text is that it is possible to update the Text constructor in the build method of the ListTile.
Using an itemBuilder: instead of the static children: property seems to make no difference.
What am I missing here? Thanks in advance!
import 'package:flutter/material.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 MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<int> _items = List<int>.generate(50, (int index) => index);
final List<String> _textValues = List<String>.generate(50, (int index) => '');
#override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
return ReorderableListView(
padding: const EdgeInsets.symmetric(horizontal: 40),
children: <Widget>[
for (int index = 0; index < _items.length; index += 1)
ListTile(
key: Key('$index'),
tileColor: _items[index].isOdd ? oddItemColor : evenItemColor,
title: Text('Item ${_items[index]}: ${_textValues[index]}'),
subtitle: TextFormField(
initialValue: _textValues[index],
onChanged: (String? value) {
setState(() {
_textValues[index] = value!;
});
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
),
],
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final int item = _items.removeAt(oldIndex);
_items.insert(newIndex, item);
final String textValue = _textValues.removeAt(oldIndex);
_textValues.insert(newIndex, textValue);
});
},
);
}
}

It appears as though this works if I wrap the int and String in an object, and then use a single ObjectKey as the key for the ListTile. I guess the lesson here is that even if multiple lists of properties are in sync, some widgets cache or something and aren't properly rebuilt, while others are.
import 'package:flutter/material.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 MyStatefulWidget(),
),
);
}
}
class Item {
int? item;
String? text;
Item(this.item, this.text);
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<Item> _listItems =
List<Item>.generate(50, (int index) => Item(index, ''));
#override
Widget build(BuildContext context) {
final ColorScheme colorScheme = Theme.of(context).colorScheme;
final Color oddItemColor = colorScheme.primary.withOpacity(0.05);
final Color evenItemColor = colorScheme.primary.withOpacity(0.15);
return ReorderableListView(
padding: const EdgeInsets.symmetric(horizontal: 40),
children: <Widget>[
for (int index = 0; index < _listItems.length; index += 1)
ListTile(
key: ObjectKey(_listItems[index]),
tileColor:
_listItems[index].item!.isOdd ? oddItemColor : evenItemColor,
title: Text(
'Item ${_listItems[index].item}: ${_listItems[index].text}'),
subtitle: TextFormField(
initialValue: _listItems[index].text,
onChanged: (String? value) {
setState(() {
_listItems[index].text = value!;
});
},
decoration: const InputDecoration(
border: OutlineInputBorder(),
),
),
),
],
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (oldIndex < newIndex) {
newIndex -= 1;
}
final Item listItem = _listItems.removeAt(oldIndex);
_listItems.insert(newIndex, listItem);
});
},
);
}
}

Related

How to remove space between expanded ExpansionPanels in ExpansionPanelList?

This is an example code for ExpansionPanelList
import 'package:flutter/material.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 MyStatefulWidget(),
),
);
}
}
// 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<Item>.generate(numberOfItems, (int index) {
return Item(
headerValue: 'Panel $index',
expandedValue: 'This is item number $index',
);
});
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
final List<Item> _data = generateItems(8);
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Container(
child: _buildPanel(),
),
);
}
Widget _buildPanel() {
return ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
_data[index].isExpanded = !isExpanded;
});
},
children: _data.map<ExpansionPanel>((Item item) {
return ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text(item.headerValue),
);
},
body: ListTile(
title: Text(item.expandedValue),
subtitle:
const Text('To delete this panel, tap the trash can icon'),
trailing: const Icon(Icons.delete),
onTap: () {
setState(() {
_data.removeWhere((Item currentItem) => item == currentItem);
});
}),
isExpanded: item.isExpanded,
);
}).toList(),
);
}
}
And it gives the following result:
As you see there is grey space between Panel 0 and Panel 1, and between Panel 1 and Panel 2. Could anyone say how to remove this space, if it is possible?
This space is added by MaterialGap inside source code.
if (_isChildExpanded(index) && index != 0 && !_isChildExpanded(index - 1))
items.add(MaterialGap(
key: _SaltedKey<BuildContext, int>(context, index * 2 - 1)));
You can remove/comment this part or better create a local project file and comment this part.
To use your customized ExpansionPanelList, import your file like
import 'customized_expansionlist.dart' as customExp;
...
customExp.ExpansionPanelList(... customExp.ExpansionPanel(...))

Flutter - How to change button color on the click and disable other buttons?

I have a listview with several green buttons, and I need to change the color of a button to red on click. The problem is that in doing that all the other buttons need to go back to their base color green.
On this example below (working version at https://www.dartpad.dev/?id=b4ea6414b6a4ffcc7135579e673be845) All buttons change the color on click independently of the other buttons, but the desired effect is that all the other buttons should be green when the clicked button is red.
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MyWidget(
text: 'Button 1',
onPressed: () => print('Click'),
),
MyWidget(
text: 'Button 2',
onPressed: () => print('Click'),
),
MyWidget(
text: 'Button 3',
onPressed: () => print('Click'),
),
MyWidget(
text: 'Button 4',
onPressed: () => print('Click'),
),
],
)),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({
Key? key,
required this.text,
required this.onPressed,
}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
final String text;
final VoidCallback onPressed;
}
class _MyWidgetState extends State<MyWidget> {
bool isFavourte = false;
#override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: isFavourte ? Colors.red : Colors.green,
),
onPressed: () {
setState(() => isFavourte = !isFavourte);
widget.onPressed();
},
child: Text(widget.text));
}
}
How this can be done?
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
final selectedIndexNotifier = ValueNotifier<int?>(null);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: ValueListenableBuilder<int?>(
valueListenable: selectedIndexNotifier,
builder: (_, selectedIndex, __) => Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
for (int i = 1; i <= 4; i++)
MyWidget(
key: ValueKey(i),
text: 'Button $i',
isFavorite: selectedIndex == i,
onPressed: () => selectedIndex == i ? selectedIndexNotifier.value = null : selectedIndexNotifier.value = i
)
],
))),
),
);
}
}
class MyWidget extends StatelessWidget {
const MyWidget({
Key? key,
required this.text,
required this.isFavorite,
required this.onPressed,
}) : super(key: key);
#override
Widget build(BuildContext context) => ElevatedButton(
style: ElevatedButton.styleFrom(
primary: isFavorite ? Colors.red : Colors.green,
),
onPressed: onPressed,
child: Text(text));
final String text;
final bool isFavorite;
final VoidCallback onPressed;
}
Here's an example doing exactly what you want to achieve, by saving the state of each button on a List and updating them all as one changes:
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
List<Map> buttonList = [
{
'label': 'button1',
'active': true,
},
{
'label': 'button2',
'active': true,
},
{
'label': 'button3',
'active': true,
},
{
'label': 'button4',
'active': true,
},
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: buttonList.length,
itemBuilder: (context, index){
return ElevatedButton(
onPressed: () => onPressed(buttonList[index]),
style: ButtonStyle(
backgroundColor: buttonList[index]['active']
? MaterialStateProperty.all(Colors.green)
: MaterialStateProperty.all(Colors.red),
),
child: Text(buttonList[index]['label']),
);
},
),
);
}
void onPressed(Map button){
setState(() {
for (var element in buttonList) {
element['active'] = false;
}
button['active'] = true;
});
}
}
created selectedValue variable in myWidget2 and id for evrey button so when ever you press a button it going to set selectedValue = id so that only the button whit the id = selectedValue going to turn red
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
int selectedValue = 0 ;
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: MyWidget2() ,
);
}
}
class ValueChanged extends Notification {
final int selectedValue ;
ValueChanged(this.selectedValue);
}
class MyWidget2 extends StatefulWidget {
const MyWidget2({
Key? key,
}) : super(key: key);
#override
State<MyWidget2> createState() => _MyWidget2State();
}
class _MyWidget2State extends State<MyWidget2> {
int selectedValue = 0 ;
#override
Widget build(BuildContext context) {
return Scaffold(
body: NotificationListener<ValueChanged>(
onNotification: (n) {
setState(() {
selectedValue = n.selectedValue ;
// Trigger action on parent via setState or do whatever you like.
});
return true;
},
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
MyWidget(
text: 'Button 1',
onPressed: () => print('Click'),
id: 1,
selectedValue :selectedValue ,
),
MyWidget(
text: 'Button 2',
onPressed: () => print('Click'),
id: 2,
selectedValue :selectedValue ,
),
MyWidget(
text: 'Button 3',
onPressed: () => print('Click'),
id:3,
selectedValue :selectedValue ,
),
MyWidget(
text: 'Button 4',
onPressed: () => print('Click'),
id:4,
selectedValue :selectedValue ,
),
],
)),
),
);
}
}
class MyWidget extends StatefulWidget {
const MyWidget({
Key? key,
required this.text,
required this.onPressed,
required this.id,
required this.selectedValue,
}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
final int id;
final String text;
final VoidCallback onPressed;
final int selectedValue ;
}
class _MyWidgetState extends State<MyWidget> {
#override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: widget.id == widget.selectedValue ? Colors.red : Colors.green,
),
onPressed: () {
setState(() => ValueChanged(widget.id).dispatch(context));
widget.onPressed();
},
child: Text(widget.text));
}
}
First I've made MyWidget Stateless and Create Two new things:
ButtonData Class: Separate The Actual data that needs to be controlled and makes it scalable.
MyButtonList: StatefulWidget that contains a List of boolean values to track the current active Button
here's an example:
Create new file and copy the following code and see the result:
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: Colors.blue[800],
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: MyButtonList(
buttons: [
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
ButtonData(text: 'Test'),
],
),
),
),
);
}
}
class MyButtonList extends StatefulWidget {
const MyButtonList({Key? key, required this.buttons}) : super(key: key);
final List<ButtonData> buttons;
#override
State<MyButtonList> createState() => _MyButtonListState();
}
class _MyButtonListState extends State<MyButtonList> {
late List<bool> favoriateState;
#override
void initState() {
favoriateState = List.generate(
widget.buttons.length, (index) => widget.buttons[index].isFavorite);
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
for (var i = 0; i < widget.buttons.length; i++)
MyWidget(
text: widget.buttons[i].text,
onPressed: () {
for (var j = 0; j < favoriateState.length; j++) {
favoriateState[j] = false;
}
setState(() {
favoriateState[i] = true;
if (widget.buttons[i].onPressed != null) {
widget.buttons[i].onPressed!();
}
});
},
isFavourte: favoriateState[i],
),
],
);
}
}
class ButtonData {
final String text;
final Function()? onPressed;
final bool isFavorite;
ButtonData({required this.text, this.onPressed, this.isFavorite = false});
}
class MyWidget extends StatelessWidget {
const MyWidget(
{Key? key,
required this.text,
required this.onPressed,
this.isFavourte = false})
: super(key: key);
final String text;
final Function()? onPressed;
final bool isFavourte;
#override
Widget build(BuildContext context) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
primary: isFavourte ? Colors.red : Colors.green,
),
onPressed: onPressed,
child: Text(text));
}
}
You seem to want to emulate RadioButtons by using TextButtons.
Withing a group of RadioListTile-s only one can be active. And this is what you want to achieve, if I understood you correctly.
May I suggest to use RadioListTile-s instead and then style (or theme) these as you like: Green for inactive Tiles, Red for active Tiles.
The following just demonstrates the usage of RadioListTile, further info on styling active-/nonactive-Tiles can be found easily.
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
/// main application widget
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Application';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
/// stateful widget that the main application instantiates
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
enum Fruit { apple, banana }
/// private State class that goes with MyStatefulWidget
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
Fruit? _fruit = Fruit.apple;
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: <Widget>[
RadioListTile<Fruit>(
title: const Text('Apple'),
value: Fruit.apple,
groupValue: _fruit,
onChanged: (Fruit? value) {
setState(() {
_fruit = value;
});
},
),
RadioListTile<Fruit>(
title: const Text('Banana'),
value: Fruit.banana,
groupValue: _fruit,
onChanged: (Fruit? value) {
setState(() {
_fruit = value;
});
},
),
],
),
);
}
}
Source
https://googleflutter.com/flutter-radiolisttile/

Update Text with Dissmissble setState

I want to update my Text() value whenever I dismiss an item from the screen .
This is the MainScreen() :
Text.rich(
TextSpan(
text: total().toString() + " DT",
style: TextStyle(
fontSize: 16,
color: Colors.black,
fontWeight: FontWeight.bold),
),
The function total() is located in Product Class like this :
class Product {
final int? id;
final String? nameProd;
final String? image;
final double? price;
Product({this.id, this.nameProd, this.image, this.price});
}
List<Product> ListProduitss = [
Product(
price: 100, nameProd: 'Produit1', image: 'assets/images/freedomlogo.png')
];
double total() {
double total = 0;
for (var i = 0; i < ListProduitss.length; i++) {
total += ListProduitss[i].price!;
}
print(total);
return total;
}
I have this in the main screen .
After I remove the item from list , I want to reupdate the Text() because the function is printing a new value in console everytime I dismiss a product :
This is from statefulWidget CartItem() that I render inside MainScreen() :
ListView.builder(
itemCount: ListProduitss.length,
itemBuilder: (context, index) => Padding(
padding: EdgeInsets.symmetric(vertical: 10),
child: Dismissible(
key: Key(ListProduitss.toString()),
direction: DismissDirection.endToStart,
onDismissed: (direction) {
setState(() {
ListProduitss.removeAt(index);
total();
// What to add here to update Text() value everytime
});
},
I tried to refresh the main screen but It didn't work .
onDismissed: (direction) {
setState(() {
ListProduitss.removeAt(index);
MainScreen();
});
},
One way is to declare a local string variable to use within the text. Then initialise the variable using total() within initState(). Then in setState do the same process.
However, it may be beneficial for you to look into a state management pattern such as BLoC pattern. https://bloclibrary.dev/#/
late String text;
void initState() {
super.initState();
text = Product.total();
}
Widget build(BuildContext context) {
return Scaffold(
extendBody: true,
appBar: AppBar(),
body: Column(
children: [
Text(text),
ElevatedButton(child: Text("Update"), onPressed:() => setState(() {
text = Product.total();
}),)
],
)
);
}
I am going to add another example as there was confusion to the above example. Below is an example of updated a text field with the length of the list. It is updated every time an item is removed.
import 'package:flutter/material.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 MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
List<int> items = List<int>.generate(100, (int index) => index);
late String text;
#override
void initState() {
text = items.length.toString(); // << this is total;
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Text(text),
Expanded(
child: ListView.builder(
itemCount: items.length,
padding: const EdgeInsets.symmetric(vertical: 16),
itemBuilder: (BuildContext context, int index) {
return Dismissible(
background: Container(
color: Colors.green,
),
key: ValueKey<int>(items[index]),
onDismissed: (DismissDirection direction) {
setState(() {
items.removeAt(index);
text = items.length.toString(); // < this is total()
});
},
child: ListTile(
title: Text(
'Item ${items[index]}',
),
),
);
},
),
),
],
);
}
}

update item number in appbar from listview.builder

I am learning flutter and have some experience in javascript.
I want to add length of _suggestions to _appBar.
I know I need setState(), but I can't find the right place to insert setState().
When I add setState in build(), the flutter framework issues error.
I understand the setState requires the build to be called, so if setState() is in build(), the condition is recursive.
And the ListView.builder seems to have no event handler. If there is event handler, I can register setState() there.
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
toolbarHeight: 100.0,
),
body: const Center(
child: RandomWords(),
),
),
);
}
}
/*
https://stackoverflow.com/questions/60902203/flutter-update-the-text-in-appbar
http://fluttersamples.com/
https://bendyworks.com/blog/a-month-of-flutter-rendering-a-list-view-with-stream-builder
https://stackoverflow.com/questions/48481590/how-to-set-update-state-of-statefulwidget-from-other-statefulwidget-in-flutter
*/
class RandomWords extends StatefulWidget {
const RandomWords({Key? key}) : super(key: key);
#override
State<RandomWords> createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
String title = 'Startup Name Generator';
final _appBar = const CustomAppBar();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar,
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if(i.isOdd) return const Divider();
final index = i ~/ 2;
if(index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return ListTile(
title: Text(
_suggestions[index].asPascalCase,
style: _biggerFont,
),
);
},
),
);
}
}
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
const CustomAppBar({Key? key}) : super(key: key);
#override
final Size preferredSize = const Size.fromHeight(56); // default is 56.0
#override
State<CustomAppBar> createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> {
String title = "Title";
_changeTitle(String title) {
setState(() {
this.title = title;
});
}
#override
Widget build(BuildContext context) {
return AppBar(
title: Text(title),
);
}
}
flutter codelab
Tested this code. but not works.
// Copyright 2018 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: const Text('Welcome to Flutter'),
toolbarHeight: 100.0,
),
body: const Center(
child: RandomWords(),
),
),
);
}
}
/*
https://stackoverflow.com/questions/60902203/flutter-update-the-text-in-appbar
http://fluttersamples.com/
https://bendyworks.com/blog/a-month-of-flutter-rendering-a-list-view-with-stream-builder
https://stackoverflow.com/questions/48481590/how-to-set-update-state-of-statefulwidget-from-other-statefulwidget-in-flutter
*/
class RandomWords extends StatefulWidget {
const RandomWords({Key? key}) : super(key: key);
#override
State<RandomWords> createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(totalSuggestions: _suggestions.length.toString()),
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if(i.isOdd) return const Divider();
final index = i ~/ 2;
if(index >= _suggestions.length) {
setState(()
{
_suggestions.addAll(generateWordPairs().take(10));
});
}
return ListTile(
title: Text(
_suggestions[index].asPascalCase,
style: _biggerFont,
),
);
},
),
);
}
}
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
final String totalSuggestions;
const CustomAppBar({Key? key, required this.totalSuggestions}) : super(key: key);
#override
final Size preferredSize = const Size.fromHeight(56); // default is 56.0
#override
State<CustomAppBar> createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> {
#override
Widget build(BuildContext context) {
return AppBar(
title: Row(
children: [
Text("Startup Name Generator"),
Spacer(),
Text(widget.totalSuggestions),
],
),
);
}
}
class RandomWords extends StatefulWidget {
const RandomWords({Key? key}) : super(key: key);
#override
State<RandomWords> createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _biggerFont = const TextStyle(fontSize: 18.0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(totalSuggestions: _suggestions.length);,
body: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if(i.isOdd) return const Divider();
final index = i ~/ 2;
if(index >= _suggestions.length) {
setState((){
_suggestions.addAll(generateWordPairs().take(10));
})
}
return ListTile(
title: Text(
_suggestions[index].asPascalCase,
style: _biggerFont,
),
);
},
),
);
}
}
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
final String totalSuggestions;
const CustomAppBar({Key? key, requiered this.totalSuggestions}) : super(key: key);
#override
final Size preferredSize = const Size.fromHeight(56); // default is 56.0
#override
State<CustomAppBar> createState() => _CustomAppBarState();
}
class _CustomAppBarState extends State<CustomAppBar> {
#override
Widget build(BuildContext context) {
return AppBar(
title:Row(
chidren: [
Text("Startup Name Generator"),
Spacer(),
Text(widget.totalSuggestions)
],
),
);
}
}
This may help you. Welcome to flutter

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++;})