How do I place ToggleButtons inside a GridView - flutter

I've created a GridView that has ToggleButtons. I was able to select a single ToggleButton at one time but I needed to place the ToggleButtons in Rows of 3 so there are 9 ToggleButtons in total. To do this I placed them inside a GridView but it's come back with an error saying 'children.length == isSelected.length': is not true.' as seen below.
Here is the code below with the GridView together with the ToggleButtons:
class Backgrounds extends StatefulWidget {
#override
_BackgroundsState createState() => _BackgroundsState();
}
class _BackgroundsState extends State<Backgrounds> {
List<bool> isSelected;
void initState() {
isSelected = [true, false, false, false, false, false, false, false, false];
super.initState();
}
#override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 2,
children: <Widget> [
Image.asset('images/image1.png'),
Image.asset('images/image2.png'),
Image.asset('images/image3.png'),
Image.asset('images/image4.png'),
Image.asset('images/image5.png'),
Image.asset('images/image6.png'),
Image.asset('images/image7.png'),
Image.asset('images/image8.png'),
Image.asset('images/image9.png')
].asMap().entries.map((widget) {
Container(
height: 100,
width: 107,
child: ToggleButtons(
children: [widget.value],
onPressed: (int index) {
setState(() {
for (int i = 0; i < isSelected.length; i++) {
isSelected[i] = i == index;
}
});
},
isSelected: (isSelected),
selectedBorderColor: Color(0xff2244C7),
borderWidth: 3,
borderRadius: BorderRadius.all(Radius.circular(8)
),
),
);
}).toList(),
);
}
}
I've attached a picture of the solution.

You should put 9 widgets in children of ToggleButtons. The children of Toggle buttons should have equal number of widget as isSelected lenght. Another problem is when you use GridView with this code It generate 81 Toggle buttons and each 9 buttons are top of themselves. I offer you try this code:
class Backgrounds extends StatefulWidget {
#override
_BackgroundsState createState() => _BackgroundsState();
}
class _BackgroundsState extends State<Backgrounds> {
List<String> imagePath = [
'images/image1.png',
'images/image2.png',
'images/image3.png',
'images/image4.png',
'images/image5.png',
'images/image6.png',
'images/image7.png',
'images/image8.png',
'images/image9.png'
];
#override
Widget build(BuildContext context) {
return GridView.count(
scrollDirection: Axis.vertical,
crossAxisCount: 3,
children: List.generate(
9,
(index) => index == 0
? ToggleButtonWidget(
isFirst: true,
imagePath: imagePath[index],
)
: ToggleButtonWidget(
imagePath: imagePath[index],
),
),
);
}
}
class ToggleButtonWidget extends StatefulWidget {
final bool isFirst;
final String imagePath;
ToggleButtonWidget({this.isFirst = false, this.imagePath});
#override
_ToggleButtonWidgetState createState() => _ToggleButtonWidgetState();
}
class _ToggleButtonWidgetState extends State<ToggleButtonWidget> {
List<bool> _isSelected;
#override
void initState() {
_isSelected = [widget.isFirst ? true : false];
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
height: 100,
width: 107,
child: ToggleButtons(
children: [
Image.asset(widget.imagePath),
],
isSelected: _isSelected,
onPressed: (int index) {
setState(() {
_isSelected[0] = !_isSelected[0];
});
},
selectedBorderColor: Color(0xff2244C7),
borderWidth: 3,
borderRadius: BorderRadius.all(Radius.circular(8)),
),
);
}
}

Related

getX value not changing if im not change the value from first textfield

I made question yesterday but is unclear. my unclear question :(
I have made changes for my question here. I got some trouble getting the value from Rx object. When the first time page load, it does perfectly listen the value change, but when I add some category amount inside DateClass, the getX not working. But it working after I changing the first amount from textfield then the second textfield is listen the value change. Did I miss something?
here my code and I attach some screenshot below
First page, I adding the value then total is listening
then I add new category to insert new amount, the total not listening
I made change to first textfield value, total is listening
after I change the first value, total is listening to second textfield value
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:collection/collection.dart';
class MainClass extends StatefulWidget {
Function callback;
MainClass({Key key, this.callback}) : super(key: key);
#override
MainClassState createState() => MainClassState();
}
class MainClassState extends State<MainClass> {
RxList<DateClass> dynamicPerDate = <DateClass>[].obs;
GlobalKey<MainClassState> mKey = GlobalKey();
String stringTotal = "";
addPerDate() async {
dynamicPerDate.add(DateClass());
setState(() {});
}
calculate() {
int total = 0;
List<int> listAmount = [];
for (int i = 0; i < dynamicPerDate.value.length; i++) {
for (int j = 0; dynamicPerDate.value[i].listCategory.length > j; j++) {
var a = dynamicPerDate[i].listCategory[j].amountCategory;
print(a);
if (a != null) {
listAmount.add(a.value);
}
total = listAmount.sum;
}
}
return total.toString();
}
#override
void initState() {
// TODO: implement initState
super.initState();
addPerDate();
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: mKey,
body: Container(
child: ListView(
children: [
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: dynamicPerDate.length,
itemBuilder: (_, index) => dynamicPerDate.isNotEmpty
? dynamicPerDate[index]
: Container()),
FlatButton(
onPressed: () {
addPerDate();
// calculate2();
},
child: Text('Add Date')),
SizedBox(height: 20),
Text('Total All Amount From Category Class'),
Obx(() => Text(calculate())),
],
),
),
);
}
}
class DateClass extends StatefulWidget {
RxList<CategoryClass> listCategory = <CategoryClass>[].obs;
DateClass({Key key, this.listCategory}) : super(key: key);
#override
_DateClassState createState() => _DateClassState();
}
class _DateClassState extends State<DateClass> {
addCategoryPerDate() {
widget.listCategory.add(CategoryClass());
setState(() {});
}
#override
void initState() {
// TODO: implement initState
super.initState();
widget.listCategory = <CategoryClass>[].obs;
addCategoryPerDate();
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text('Date', style: TextStyle(color: Colors.blue)),
ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.listCategory.length,
itemBuilder: (_, index) => widget.listCategory.isNotEmpty
? widget.listCategory[index]
: Container()),
FlatButton(
onPressed: () {
addCategoryPerDate();
},
child: Text('Add Category'))
],
),
),
);
}
}
class CategoryClass extends StatefulWidget {
RxInt amountCategory = 0.obs;
TextEditingController amountController;
CategoryClass({Key key, this.amountCategory}) : super(key: key);
#override
_CategoryClassState createState() => _CategoryClassState();
}
class _CategoryClassState extends State<CategoryClass> {
MainClass mainClass = MainClass();
#override
void initState() {
// TODO: implement initState
super.initState();
widget.amountCategory = 0.obs;
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Text('Amount'),
TextFormField(
controller: widget.amountController,
onChanged: (value) {
setState(() {
widget.amountCategory.value = int.parse(value);
widget.amountCategory.obs;
// MainClass().method();
});
},
keyboardType: TextInputType.number,
)
],
);
}
}

Load more datas when scrolling on a GridView Flutter

I am making an application where I can run through items in a GridView on a profile page, like on Instagram when we scroll our posts.
I want to load more items (15 per 15) when I scroll on my GridView.
I want an infinite loading.
So I added a ScrollListener to my GridView.
If I put an "initialScrollOffset" to "5.0" in attribute to my ScrollListener, it will load the 15 first items and make one loading, so it's add 15 items (work only 1 time), but if I let the default value, it loads no items.
I would like to have an infinite loading.
My GridView code :
import 'dart:developer';
import 'package:dresskip/model/item_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class ItemSection extends StatefulWidget {
const ItemSection({Key? key}) : super(key: key);
#override
_ItemSectionState createState() => _ItemSectionState();
}
class _ItemSectionState extends State<ItemSection> {
List<Item> items = [];
bool isLoading = false;
int pageCount = 1;
ScrollController _scrollController = ScrollController(initialScrollOffset: 5.0);
#override
void initState() {
super.initState();
///LOADING FIRST DATA
addItemsToList(1);
_scrollController.addListener(_scrollListener);
}
#override
Widget build(BuildContext context) {
return Column(
children: [
Row(),
GridView.count(
controller: _scrollController,
crossAxisCount: 3,
shrinkWrap: true,
//physics: const ScrollPhysics() /*AlwaysScrollableScrollPhysics()*/,
mainAxisSpacing: 0,
children: items.map((value) {
return Image.network(value.picture);
}).toList(),
)
],
);
}
//// ADDING THE SCROLL LISTINER
_scrollListener() {
//inspect(_scrollController.offset);
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent &&
!_scrollController.position.outOfRange) {
setState(() {
print("comes to bottom $isLoading");
isLoading = true;
if (isLoading) {
print("RUNNING LOAD MORE");
pageCount = pageCount + 1;
addItemsToList(pageCount);
}
});
}
}
addItemsToList(int page) {
//if (page < 5) {}
Item myItem = Item(
name: "test",
brand: "test",
color: ["0xFF39BDC8", "0xFFdb8abc", ""],
picture:
"https://images.pexels.com/photos/9676177/pexels-photo-9676177.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
//"https://scontent.fcdg2-1.fna.fbcdn.net/v/t1.6435-9/171944671_3950113148381954_7059062044076097927_n.jpg?_nc_cat=110&ccb=1-5&_nc_sid=09cbfe&_nc_ohc=gxbPXmRQmN8AX9V5Bx5&_nc_ht=scontent.fcdg2-1.fna&oh=ac2a57c8c1d1b0b01fcf131ac42c4023&oe=6190A9BF",
solo: false,
clean: true,
type: "test");
for (int i = (pageCount * 15) - 15; i < pageCount * 15; i++) {
items.add(myItem);
isLoading = false;
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
}
Item model class
class Item {
String name;
String brand;
List<String> color;
String picture;
bool solo;
bool clean;
String type;
Item({
required this.name,
required this.brand,
required this.color,
required this.picture,
required this.solo,
required this.clean,
required this.type,
});
}
The first part (profile section) code
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import '/assets/constants.dart' as constants;
import '../../assets/dresskip_icon_icons.dart' as DresskipIcons;
class ProfileSection extends StatelessWidget {
final List<String> description;
final VoidCallback onClicked;
const ProfileSection({
Key? key,
required this.description,
required this.onClicked,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(10, 10, 10, 10),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment
.spaceBetween, //pour que chaque colonnes dans la ligne ait leurs propres tailles
crossAxisAlignment: CrossAxisAlignment
.start, //pour tout coller en haut du container
children: <Widget>[
const Icon(Icons.local_laundry_service),
Stack(children: [
buildImage(),
Positioned(
bottom: 0,
right: 4,
child:
buildEditIcon(Color(constants.COLOR_BLUE_DRESSKIP)))
]),
const Icon(Icons.settings),
],
),
// Partie description
Container(
child: Text(description[0]),
margin: EdgeInsets.fromLTRB(50, 5, 50, 5)),
// Partie Instagram
Container(
child: Row(
children: <Widget>[
Container(
child: const Icon(DresskipIcons.DresskipIcon.instagram),
margin: const EdgeInsets.fromLTRB(0, 0, 20, 0),
),
Expanded(child: Text(description[1]))
],
),
margin: const EdgeInsets.fromLTRB(20, 5, 20, 5)),
// Partie Facebook
Container(
child: Row(
children: <Widget>[
Container(
child: const Icon(Icons.facebook),
margin: const EdgeInsets.fromLTRB(0, 0, 20, 0),
),
Expanded(child: Text(description[2]))
],
),
margin: const EdgeInsets.fromLTRB(20, 5, 20, 5)),
// Partie Twitter
Container(
child: Row(
children: <Widget>[
Container(
child:
const Icon(DresskipIcons.DresskipIcon.twitter_square),
margin: const EdgeInsets.fromLTRB(0, 0, 20, 0),
),
Expanded(child: Text(description[3]))
],
),
margin: const EdgeInsets.fromLTRB(20, 5, 20, 5)),
],
));
}
// Widget pour afficher l'image
Widget buildImage() {
// use if is an image on the web with the link : final image = NetworkImage(imagePath);
return ClipOval(
child: Material(
color: Colors.transparent,
child: Ink.image(
image: const AssetImage("assets/undraw_female_avatar.png"),
fit: BoxFit.cover,
width: 128,
height: 128,
child: InkWell(onTap: onClicked),
)));
}
// Widget pour l'ajout de l'icone à coté de l'image
// Ici, il y a 2 fois buildCircle pour arrondir l'icone et ensuite mettre le trait blanc arrondi entre la photo et l'icône
Widget buildEditIcon(Color color) => buildCircle(
color: Colors.white,
all: 1,
child: buildCircle(
color: color,
all: 8,
child: Icon(Icons.add_a_photo, color: Colors.white, size: 20)));
// Widget permettant d'arrondir l'image
Widget buildCircle({
required Widget child,
required double all,
required Color color,
}) =>
ClipOval(
child: Container(
padding: EdgeInsets.all(all), color: color, child: child));
}
The parent page code
import 'package:dresskip/model/user_model.dart';
import 'package:flutter/material.dart';
import 'itemSection_widget.dart';
import 'profileSection_widget.dart';
import '/assets/constants.dart' as constants;
import 'dart:convert';
class AccountPage extends StatelessWidget {
const AccountPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
ProfileSection(
description: getUserInformation().properties,
onClicked: () async {}),
const Divider(
color: Color(constants.COLOR_BLUE_DRESSKIP),
thickness: 2,
indent: 50,
endIndent: 50,
),
ItemSection()
],
)));
}
getUserInformation() {
User myUser = User(username: "test", email: "test#test.test", properties: [
"22 yo\nFlexeur/Epicurien/Philanthrope\nJ'adore la vie\nEFREI Paris",
"instagram_test",
"facebook_test",
"twitter"
]);
return myUser;
}
}
There are 2 screenshots of my App.
The problem here, it's just loading 15 + 15 items (the first 30) and I can't load more data on scrolling.
EDIT
I find a way to resolv this problem. The attribute "shrinkwrap" block the possibility to scroll more because my widget which contains the gridview is into a Column Widget.
So i removed it, but just the Gridview is scrolling, I would like to do like Instagram's profil, when you scroll on your pictures, all the page scroll and not only the GridView.
Do you have an idea ?
Endless / Infinite Scroll GridView
This example uses a GridView.builder & doesn't need a ScrollController.
When the end of the current dataset is reached, it will request more data and rebuild the GridView.
We can pad the end of the dataset with a special item. When this special item is built by GridView.builder, it will:
show a loading indicator
request more data from datasource
rebuild the GridView when data arrives
import 'package:flutter/material.dart';
class InfiniteScrollPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Infinite Scroll'),
),
body: EndlessGrid());
}
}
class EndlessGrid extends StatefulWidget {
#override
_EndlessGridState createState() => _EndlessGridState();
}
class _EndlessGridState extends State<EndlessGrid> {
NumberGenerator _numGen = NumberGenerator();
List<int> _data = [];
#override
void initState() {
super.initState();
_data = _numGen.nextPage();
}
#override
Widget build(BuildContext context) {
return GridView.builder(
itemCount: _data.length + 1, // pad data with an extra item at end
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisSpacing: 10, mainAxisSpacing: 10, crossAxisCount: 2),
itemBuilder: (context, i) {
if (i < _data.length) {
return gridItem(i);
}
else { // extra item will request next page & rebuild widget
getNextPage();
return CircularProgressIndicator();
}
},
);
}
Widget gridItem(int i) {
return Container(
alignment: Alignment.center,
decoration: BoxDecoration(
color: Colors.lightBlue
),
padding: EdgeInsets.all(5),
child: Text('$i'),
);
}
/// Request next page of data and call setState to rebuild GridView with
/// new data.
Future<void> getNextPage() async {
var _nextPage = await Future.delayed(Duration(seconds: 2), _numGen.addPage);
setState(() {
_data = _nextPage;
});
}
}
/// Mock data to fill GridView
class NumberGenerator {
static const PAGESIZE = 15;
List<int> dataset = [];
final int start;
NumberGenerator({this.start = 0});
List<int> addItem() {
dataset.add(lastItem + 1);
return dataset;
}
List<int> addPage() {
dataset.addAll(nextPage());
return dataset;
}
int get lastItem => dataset.isNotEmpty ? dataset.last : start;
List<int> nextPage({int start, int size = PAGESIZE}) {
start ??= lastItem;
return List<int>.generate(size, (i) => start + i + 1);
}
}
Firstly, I'm not aware of Instagram UI.
The problem is here with parent, while SingleChildScrollView is the parent and you want to scroll the full page and generate GridItem based on it, therefore set we don't need to ScrollController for GridView instead use it on SingleChildScrollView.
The code structure will be
parent class generate data for GridView and will be StatefulWidget.
while there are two scrollable widgets so that GridView < physics: NeverScrollableScrollPhysics(),>
AccountPage: SingleChildScrollView will have that _scrollController
ItemSection can be StatelessWidget
Full Code snippet with dummyHeaderWidget
import 'package:flutter/material.dart';
class AccountPage extends StatefulWidget {
const AccountPage({Key? key}) : super(key: key);
#override
State<AccountPage> createState() => _AccountPageState();
}
class _AccountPageState extends State<AccountPage> {
ScrollController _scrollController = ScrollController();
bool isLoading = true; //
int pageCount = 1;
List items = [];
//// ADDING THE SCROLL LISTINER
void _scrollListener() {
print(
"current ${_scrollController.offset} max: ${_scrollController.position.maxScrollExtent}");
if (_scrollController.offset >=
_scrollController.position.maxScrollExtent &&
!_scrollController.position.outOfRange) {
setState(() {
print("comes to bottom $isLoading");
isLoading = true;
if (isLoading) {
print("RUNNING LOAD MORE");
pageCount = pageCount + 1;
addItemsToList(pageCount);
}
});
}
}
addItemsToList(int page) {
//if (page < 5) {}
Item myItem = Item(
name: "test",
brand: "test",
color: ["0xFF39BDC8", "0xFFdb8abc", ""],
picture:
"https://images.pexels.com/photos/9676177/pexels-photo-9676177.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=750&w=1260",
//"https://scontent.fcdg2-1.fna.fbcdn.net/v/t1.6435-9/171944671_3950113148381954_7059062044076097927_n.jpg?_nc_cat=110&ccb=1-5&_nc_sid=09cbfe&_nc_ohc=gxbPXmRQmN8AX9V5Bx5&_nc_ht=scontent.fcdg2-1.fna&oh=ac2a57c8c1d1b0b01fcf131ac42c4023&oe=6190A9BF",
solo: false,
clean: true,
type: "test");
for (int i = (pageCount * 15) - 15; i < pageCount * 15; i++) {
items.add(myItem);
isLoading = false;
}
}
#override
void initState() {
super.initState();
_scrollController.addListener(_scrollListener);
addItemsToList(1);
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: [
Container(
height: 300,
color: Colors.green,
),
const Divider(
thickness: 2,
indent: 50,
endIndent: 50,
),
ItemSection(
items: items,
),
],
),
),
);
}
}
class ItemSection extends StatelessWidget {
final List items;
const ItemSection({
Key? key,
required this.items,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 3,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
mainAxisSpacing: 0,
children: items.map((value) {
return Image.network(value.picture);
}).toList(),
);
}
}
class Item {
String name;
String brand;
List<String> color;
String picture;
bool solo;
bool clean;
String type;
Item({
required this.name,
required this.brand,
required this.color,
required this.picture,
required this.solo,
required this.clean,
required this.type,
});
}
Read the code, hope you will get concept and make changes on your project.

Flutter - select only single item in list view

In my app I am generating a ListView and items can be highlighted by tapping on them. That works fine and I also have a callback function that gives me the key for the just selected item. I can currently manually deselect the item by tapping on it again, but will ultimately take that functionality out.
My problem is that I want one and only one item to be selected at a time. In order to create the list I currently take some initial content in the form of a list, generate the tiles and add them to another list. I then use that list to create the ListView. My plan was on the callback from a new selection, run through the list of tiles and deselect them before highlighting the new chosen tile and carrying out the other functions. I have tried various methods to tell each tile to deselect itself but have not found any way to address each of the tiles. Currently I get the error:
Class 'OutlineTile' has no instance method 'deselect'.
Receiver: Instance of 'OutlineTile'
Tried calling: deselect()
I have tried to access a method within the tile class and to use a setter but neither worked so far. I am quite new to flutter so it could be something simple I am missing. My previous experience was with Actionscript where this system would have worked fine and I could access a method of an object (in this case the tile) easily as long s it is a public method.
I'd be happy to have another way to unselect the old item or to find a way to access a method within the tile. The challenge is to make the tiles show not highlighted without them being tapped themselves but when a different tile is tapped.
The code in my parent class is as follows:
class WorkingDraft extends StatefulWidget {
final String startType;
final String name;
final String currentContent;
final String currentID;
final List startContent;
WorkingDraft(
{this.startType,
this.name,
this.currentContent,
this.currentID,
this.startContent});
#override
_WorkingDraftState createState() => _WorkingDraftState();
}
class _WorkingDraftState extends State<WorkingDraft> {
final _formKey = GlobalKey<FormState>();
final myController = TextEditingController();
//String _startType;
String _currentContent = "";
String _name = "Draft";
List _startContent = [];
List _outLineTiles = [];
int _counter = 0;
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
void initState() {
super.initState();
_currentContent = widget.currentContent;
_name = widget.name;
_startContent = widget.startContent;
_counter = 0;
_startContent.forEach((element) {
_outLineTiles.add(OutlineTile(
key: Key("myKey$_counter"),
outlineName: element[0],
myContent: element[1],
onTileSelected: clearHilights,
));
_counter++;
});
}
dynamic clearHilights(Key myKey) {
_outLineTiles.forEach((element) {
element.deselect(); // this throws an error Class 'OutlineTile' has no instance method 'deselect'.
Key _foundKey = element.key;
print("Element Key $_foundKey");
});
}
.......
and further down within the widget build scaffold:
child: ListView.builder(
itemCount: _startContent.length,
itemBuilder: (context, index) {
return _outLineTiles[index];
},
),
Then the tile class is as follows:
class OutlineTile extends StatefulWidget {
final Key key;
final String outlineName;
final Icon myIcon;
final String myContent;
final Function(Key) onTileSelected;
OutlineTile(
{this.key,
this.outlineName,
this.myIcon,
this.myContent,
this.onTileSelected});
#override
_OutlineTileState createState() => _OutlineTileState();
}
class _OutlineTileState extends State<OutlineTile> {
Color color;
Key _myKey;
#override
void initState() {
super.initState();
color = Colors.transparent;
}
bool _isSelected = false;
set isSelected(bool value) {
_isSelected = value;
print("set is selected to $_isSelected");
}
void changeSelection() {
setState(() {
_myKey = widget.key;
_isSelected = !_isSelected;
if (_isSelected) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
});
}
void deselect() {
setState(() {
isSelected = false;
color = Colors.transparent;
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Card(
elevation: 10,
margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
child: SizedBox(
width: 180,
child: Container(
color: color,
child: ListTile(
title: Text(widget.outlineName),
onTap: () {
if (widget.outlineName == "Heading") {
Text("Called Heading");
} else (widget.outlineName == "Paragraph") {
Text("Called Paragraph");
widget.onTileSelected(_myKey);
changeSelection();
},
),
........
Thanks for any help.
Amended Code sample and explanation, that builds to a complete project, from here:
Following the advice from phimath I have created a full buildable sample of the relevant part of my project.
The problem is that the tiles in my listview are more complex with several elements, many of which are buttons in their own right so whilst phimath's solution works for simple text tiles I have not been able to get it working inside my own project. My approach is trying to fundamentally do the same thing as phimath's but when I include these more complex tiles it fails to work.
This sample project is made up of three files. main.dart which simply calls the project and passes in some dummy data in the way my main project does. working_draft.dart which is the core of this issue. And outline_tile.dart which is the object that forms the tiles.
Within working draft I have a function that returns an updated list of the tiles which should show which tile is selected (and later any other changes from the other buttons). This gets called when first going to the screen. When the tile is tapped it uses a callback function to redraw the working_draft class but this seems to not redraw the list as I would expect it to. Any further guidance would be much appreciated.
The classes are:
first class is main.dart:
import 'package:flutter/material.dart';
import 'package:listexp/working_draft.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: WorkingDraft(
startType: "Basic",
name: "Draft",
currentID: "anID",
startContent: [
["Heading", "New Heading"],
["Paragraph", "New Text"],
["Image", "placeholder"],
["Signature", "placeholder"]
],
));
}
}
Next file is working_draft.dart:
import 'package:flutter/material.dart';
import 'package:listexp/outline_tile.dart';
class WorkingDraft extends StatefulWidget {
final String startType;
final String name;
final String currentContent;
final String currentID;
final List startContent;
final int selectedIndex;
WorkingDraft(
{this.startType,
this.name,
this.currentContent,
this.currentID,
this.startContent,
this.selectedIndex});
#override
_WorkingDraftState createState() => _WorkingDraftState();
}
class _WorkingDraftState extends State<WorkingDraft> {
int selectedIndex;
String _currentContent = "";
String _name = "Draft";
List _startContent = [];
var _outLineTiles = [];
int _counter = 0;
int _selectedIndex;
bool _isSelected;
dynamic clearHilights(int currentIndex) {
setState(() {
_selectedIndex = currentIndex;
});
}
updatedTiles() {
if (_selectedIndex == null) {
_selectedIndex = 0;
}
_currentContent = widget.currentContent;
_name = widget.name;
_startContent = widget.startContent;
_counter = 0;
_outLineTiles = [];
_startContent.forEach((element) {
_isSelected = _selectedIndex == _counter ? true : false;
_outLineTiles.add(OutlineTile(
key: Key("myKey$_counter"),
outlineName: element[0],
myContent: element[1],
myIndex: _counter,
onTileSelected: clearHilights,
isSelected: _isSelected,
));
_counter++;
});
}
#override
Widget build(BuildContext context) {
updatedTiles();
return Scaffold(
body: Center(
child: Column(children: [
SizedBox(height: 100),
Text("Outline", style: new TextStyle(fontSize: 15)),
Container(
height: 215,
width: 300,
decoration: BoxDecoration(
border: Border.all(
color: Colors.lightGreenAccent,
width: 2,
),
borderRadius: BorderRadius.circular(2),
),
child: ListView.builder(
itemCount: _startContent.length,
itemBuilder: (context, index) {
return _outLineTiles[index];
},
),
),
]),
));
}
}
and finally is outline_tile.dart
import 'package:flutter/material.dart';
class OutlineTile extends StatefulWidget {
final Key key;
final String outlineName;
final Icon myIcon;
final String myContent;
final int myIndex;
final Function(int) onTileSelected;
final bool isSelected;
OutlineTile(
{this.key,
this.outlineName,
this.myIcon,
this.myContent,
this.myIndex,
this.onTileSelected,
this.isSelected});
#override
_OutlineTileState createState() => _OutlineTileState();
}
class _OutlineTileState extends State<OutlineTile> {
Color color;
// Key _myKey;
bool _isSelected;
#override
void initState() {
super.initState();
_isSelected = widget.isSelected;
if (_isSelected == true) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
}
void deselect() {
setState(() {
_isSelected = widget.isSelected;
if (_isSelected == true) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Card(
elevation: 10,
margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
child: SizedBox(
width: 180,
child: Container(
color: color,
child: ListTile(
title: Text(widget.outlineName),
onTap: () {
if (widget.outlineName == "Heading") {
Text("Called Heading");
} else if (widget.outlineName == "Paragraph") {
Text("Called Paragraph");
} else if (widget.outlineName == "Signature") {
Text("Called Signature");
} else {
Text("Called Image");
}
var _myIndex = widget.myIndex;
widget.onTileSelected(_myIndex);
deselect();
},
),
),
),
),
SizedBox(
height: 60,
child: Column(
children: [
SizedBox(
height: 20,
child: IconButton(
iconSize: 30,
icon: Icon(Icons.arrow_drop_up),
onPressed: () {
print("Move Up");
}),
),
SizedBox(height: 5),
SizedBox(
height: 20,
child: IconButton(
iconSize: 30,
icon: Icon(Icons.arrow_drop_down),
onPressed: () {
print("Move Down");
}),
),
],
),
),
SizedBox(
height: 60,
child: Column(
children: [
SizedBox(
height: 20,
child: IconButton(
iconSize: 20,
icon: Icon(Icons.add_box),
onPressed: () {
print("Add another");
}),
),
SizedBox(
height: 10,
),
SizedBox(
height: 20,
child: IconButton(
iconSize: 20,
icon: Icon(Icons.delete),
onPressed: () {
print("Delete");
}),
),
],
),
),
],
),
);
}
}
Thanks again
Instead of manually deselecting tiles, just keep track of which tile is currently selected.
I've made a simple example for you. When we click a tile, we just set the selected index to the index we clicked, and each tile looks at that to see if its the currently selected tile.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: Home()),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int selectedIndex;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item: $index'),
tileColor: selectedIndex == index ? Colors.blue : null,
onTap: () {
setState(() {
selectedIndex = index;
});
},
);
},
);
}
}

How to get info from multiple widgets hovered over simoultaneously?

Issue
As per the drawing below I have a 4x4 grid of Widgets. The red line indicates the touch gesture I make. The green coloured squares are the squares the gesture touches. The blue coloured squares are the ones not touched by the gesture.
How can I make flutter get all the widgets i touch this way and store a reference so that I can access their content (for example an image or a string)?
What did I do so far?
I was looking at the "Draggable" widget type, it works for dragging a single widget and I could get that to work with a single widget. But I could not get it to work with multiple widgets being dragged over/touched this way. So I suspect I may need another solution. At this time I am just not sure what could make this functionality work.
Example image
Thanks for your help.
Please try the below example. You may need to modify it as per your requirement.
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark()
.copyWith(scaffoldBackgroundColor: Color.fromARGB(255, 18, 32, 47)),
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(body: CustomPage());
}
}
class CustomPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return CustomPageState();
}
}
class CustomPageState extends State<CustomPage> {
double progress = 0;
var arr = [
[false, false, false],
[false, false, false],
[false, false, false]
];
var keys = [
[new GlobalKey(), new GlobalKey(), new GlobalKey()],
[new GlobalKey(), new GlobalKey(), new GlobalKey()],
[new GlobalKey(), new GlobalKey(), new GlobalKey()]
];
#override
Widget build(BuildContext context) {
return Container(
color: Colors.blueAccent,
child: Center(
//Wraps the second container in RawGestureDetector
child: Center(
child: GestureDetector(
onHorizontalDragUpdate: (details) {
changeState(details.globalPosition);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: List.generate(
3,
(i) => Column(
mainAxisSize: MainAxisSize.min,
children: List.generate(
3,
(j) => Container(
key: keys[i][j],
margin: EdgeInsets.all(2),
color: arr[i][j] ? Colors.green : Colors.red,
width: 50,
height: 50,
)),
)),
),
)),
),
);
}
void changeState(Offset pos) {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
Rect re = _getWidgetGlobalRect(keys[i][j]);
if (re.contains(pos)) {
selectItem(i, j);
}
}
}
}
Rect _getWidgetGlobalRect(GlobalKey key) {
RenderBox renderBox = key.currentContext.findRenderObject();
var offset = renderBox.localToGlobal(Offset.zero);
return Rect.fromLTWH(
offset.dx, offset.dy, renderBox.size.width, renderBox.size.height);
}
void selectItem(int i, int j) {
if (arr[i][j]) {
return;
}
setState(() {
arr[i][j] = !arr[i][j];
});
}
}

RangeError (index): Invalid value: Valid value range is empty: 0 returned while using Checkbox and a for loop

class HostAMealCard extends StatefulWidget {
HostAMealCard(this.combo);
final Combo combo;
#override
_HostAMealCardState createState() => _HostAMealCardState();
}
class _HostAMealCardState extends State<HostAMealCard> {
#override
Widget build(BuildContext context) {
return Container(
height: (50 * widget.combo.items.length + 75).toDouble(),
child: Card(
child: ListView.builder(
itemCount: widget.combo.items.length,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return OneMeal(widget.combo, index);
},
)),
);
}
}
class OneMeal extends StatefulWidget {
OneMeal(this.combo, this.index);
final Combo combo;
final int index;
//int count = combo.items.length;
#override
State<StatefulWidget> createState() => OneMealState();
}
class OneMealState extends State<OneMeal> {
List<bool> Vals = new List();
#override
void initState() {
super.initState();
int count = widget.combo.items.length;
for (int i = 0; i < count; i++) { //This for loop gives exception
Vals[i] = false;
}
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Align(
child: Text(widget.combo.items[widget.index].item.toString()),
alignment: Alignment(-1, 0),
),
Align(
child: Text(widget.combo.items[widget.index].price.toString()),
alignment: Alignment(0.2, 0)),
Align(
child: Checkbox( //This checkbox gives exception
value: Vals[widget.index],
onChanged: (bool value) {
setState(() {
Vals[widget.index] = value;
});
},
),
alignment: Alignment(0.6, 0.4)),
],
);
}
}
The class HostAMealCard is a card which contains a combination of food items. Each item has a Checkbox associated with it. The number of items in each card is dynamic.
The exceptions are thrown by a for loop and a Checkbox.
Also the class HostAMealCard is returned as the itemBuilder of a FutureBuilder.
Is there a simpler way to achieve what I'm trying to do here?
Vals has length 0, so accessing to any position of it will, of course, throw an exception.
you should do this:
Vals.add(false)
Or even better you could replace the for loop and initialize Vals in your initState this way:
Vals = List<bool>.generate(widget.combo.items.length, (_) => false);