I basically want to create a table full of dynamically created radioButtons where I can handle their individual clicks.
The row and column headers will both contain their own text headings, and the accompanying cells contain the radioButtons.
I've been able to answer the question. Find attached the code and screenshot for your perusal. Scrolling is horizontal when the screen is portrait and vertical in landscape mode. Enjoy!
import 'package:flutter/material.dart';
class TableView extends StatefulWidget {
#override
_TableViewState createState() => _TableViewState();
}
class _TableViewState extends State<TableView> {
List rowHeaders = new List();
List columnHeaders = new List();
Map selected = new Map();
#override
void initState() {
super.initState();
saveHeaders(); //Iterate and store all Row and column Headers
}
saveHeaders() {
//Saving All Headers
columnHeaders.addAll(["Excellent", "Very Good", "Good", "Poor"]);
rowHeaders.addAll(["MTN", "Vodafone", "Tigo", "Expresso", "Glo"]);
}
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: new Text("TableView"),
),
body: new OrientationBuilder(builder: (context, orientation) {
return Center(
child: SingleChildScrollView(
scrollDirection: orientation ==
Orientation
.portrait //Handle Scroll when Orientation is changed
? Axis.horizontal
: Axis.vertical,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Container(
color: Colors.blueGrey[200],
padding: EdgeInsets.only(top: 20.0, bottom: 10.0),
alignment: FractionalOffset.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
//headers
new Container(
margin: EdgeInsets.all(0.0),
child: new Row(
children: [
new Container(
alignment: FractionalOffset.center,
width: 140.0,
margin: EdgeInsets.all(0.0),
padding: const EdgeInsets.only(
top: 5.0, bottom: 5.0, right: 3.0, left: 3.0),
child: Text(
//Leave an empty text in Row(0) and Column (0)
"",
style: TextStyle(color: Colors.grey[800]),
textAlign: TextAlign.center,
),
)
]..addAll(columnHeaders
.map((header) => new Container(
alignment: FractionalOffset.center,
width: 120.0,
margin: EdgeInsets.all(0.0),
padding: const EdgeInsets.only(
top: 5.0,
bottom: 5.0,
right: 3.0,
left: 3.0),
child: new Text(
header,
style:
TextStyle(color: Colors.grey[800]),
textAlign: TextAlign.center,
),
))
.toList())),
),
],
),
)
]..addAll(createRows()), //Create Rows
),
));
}));
}
List<Widget> createRows() {
List<Widget> allRows = new List(); //For Saving all Created Rows
for (int i = 0; i < rowHeaders.length; i++) {
List<Widget> singleRow = new List(); //For creating a single row
for (int j = 0; j < columnHeaders.length; j++) {
singleRow.add(Container(
alignment: FractionalOffset.center,
width: 120.0,
padding: const EdgeInsets.only(
top: 6.0, bottom: 6.0, right: 3.0, left: 3.0),
child: Radio(
value: j, //Index of created Radio Button
groupValue: selected[rowHeaders[i]] !=
null //If groupValue is equal to value, the radioButton will be selected
? selected[rowHeaders[i]]
: "",
onChanged: (value) {
this.setState(() {
selected[rowHeaders[i]] =
value; //Adding selected rowName with its Index in a Map tagged "selected"
print("${rowHeaders[i]} ==> $value");
});
},
)));
}
//Adding single Row to allRows widget
allRows.add(new Container(
child: new Row(
children: [
new Container(
alignment: FractionalOffset.centerLeft,
width: 140.0,
padding: const EdgeInsets.only(
top: 6.0, bottom: 6.0, right: 3.0, left: 10.0),
child:
Text(rowHeaders[i], style: TextStyle(color: Colors.grey[800])),
)
]..addAll(singleRow), //Add single row here
)));
}
return allRows; //Return all single rows
}
}
first you must create an array type of radio button with suitable identifier :
3 * 3 Table of radio buttons Just like this :
* * *
* * *
* * *
RadioButton [,] r=new RadioButton[3,3];
then set location and call initialize method for in :
for(int i=0;i<3;i++)
{
for(int j=0;j<3;j++)
{
r[i,j]=new RadioButton();
r[i,j].Tag=I.toString()+"-"+j.toString();
r[i,j].top=30+j*30;
r[i,j].top=30+i*30;
r[i,j].click+=new EvantHandler(*/the method that you whant to call when click in each radio button*/);
}
}
when event handler method called you can easily find that which radio button was clicked! from tag value of each radio button
Related
Here is the image i am trying to build. Tried Stack with Alignment.topRight but i need to build a TextField after the IconButton that's the bottleneck.
Little details: The grey box is the input field, which i will append some static text at start, as you can see in the pic below. Static Text = Happy Birthday, then the button with cross icon and after that a TextField as shown in the design.
I tried different approaches but not coming-up with accurate results.
Row(
children: [
Container(
margin: const EdgeInsets.only(top: 5, left: 5),
padding: const EdgeInsets.only(top: 10, left: 10, bottom: 10),
decoration:
BoxDecoration(color: Constants.colorWhite.withOpacity(0.90)),
child: Text(Constants.happyBirthday),
),
Container(
margin: const EdgeInsets.only(top: 5, right: 0),
padding:
const EdgeInsets.only(left: 5, top: 10, right: 0, bottom: 10),
decoration:
BoxDecoration(color: Constants.colorWhite.withOpacity(0.50)),
child: Stack(
children: [
Text(widget.userName.toString()),
const Positioned(top: 0, right: 0, child: Icon(Icons.close))
],
)),
Container(
margin: const EdgeInsets.only(top: 5, left: 0),
padding: const EdgeInsets.only(top: 10, left: 10, bottom: 10),
decoration:
BoxDecoration(color: Constants.colorWhite.withOpacity(0.90)),
child: Text('${Constants.happyBirthday} '
'${Constants.happyBirthday}'
'${Constants.happyBirthday}'),
),
],
)
You can create your own TextEditingController and override buildTextSpan.
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
/// #techbusiness
///
/// NOTE: Needs to be improved. Feel free to play with the styling
/// to get your desired result. This is just to show you the
/// possibility of your design.
class TaggerTextEditingController extends TextEditingController {
TaggerTextEditingController({
this.onPressTag,
this.onDeleteTag,
this.tagger = '#',
this.tagStyle,
this.tagBackgroundColor,
this.deleteIconSize = 15,
super.text,
});
final ValueChanged<String>? onPressTag;
final ValueChanged<String>? onDeleteTag;
final String tagger;
final TextStyle? tagStyle;
final Color? tagBackgroundColor;
final double deleteIconSize;
void _onDeleteTag(String tag) {
text = text.replaceFirst(tag, '');
onDeleteTag?.call(tag.replaceFirst(tagger, ''));
}
WidgetSpan _buildTag(String tag, TextStyle? style) => WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Stack(
clipBehavior: Clip.none,
children: <Widget>[
InkWell(
onTap: () => onPressTag?.call(tag.replaceFirst(tagger, '')),
borderRadius: const BorderRadius.all(Radius.circular(15)),
child: Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(15)),
color: tagBackgroundColor ?? Colors.grey.shade200,
),
child: Text(
tag.replaceFirst(tagger, ''),
style: style,
),
),
),
Positioned(
top: -deleteIconSize / 4,
right: -deleteIconSize / 4,
child: GestureDetector(
onTap: () => _onDeleteTag(tag),
child: Icon(
CupertinoIcons.xmark_circle_fill,
size: deleteIconSize,
color: Colors.red,
),
),
),
],
),
);
#override
TextSpan buildTextSpan({
required BuildContext context,
TextStyle? style,
required bool withComposing,
}) {
final List<TaggerText> texts = TaggerText.getTags(text, tagger);
return TextSpan(
children: texts
.map(
(TaggerText value) => value.isTag
? _buildTag(value.text, tagStyle ?? style)
: TextSpan(
text: value.text,
style: style,
),
)
.toList(),
);
}
}
//------------------------------------------------------------------------------
class TaggerText {
const TaggerText(this.text, this.isTag);
final String text;
final bool isTag;
static List<TaggerText> getTags(String text, String tagger) {
final List<TaggerText> tags = <TaggerText>[];
StringBuffer textPortion = StringBuffer();
String prevChar = '';
bool isTextPortionATag() => textPortion.toString().startsWith(tagger);
void addTaggerText() => tags.add(
TaggerText(textPortion.toString(), isTextPortionATag()),
);
for (final String char in text.characters) {
if (char == tagger && prevChar == ' ') {
addTaggerText();
textPortion.clear();
} else if (char == ' ' && isTextPortionATag()) {
addTaggerText();
textPortion.clear();
}
textPortion.write(char);
prevChar = char;
}
if (textPortion.isNotEmpty) {
addTaggerText();
}
return tags;
}
}
How to use.
TextField(
controller: TaggerTextEditingController(),
maxLines: 5,
),
I wrote the code to get data from List to chips and when click chips the colour changed to blue. But I want to fetch data from firestore instead "words list".
Instead this words list ...
Database collection image
I want to display "WordName" field in the chips.
My code..
class uitry extends StatefulWidget {
const uitry({Key? key}) : super(key: key);
#override
State<uitry> createState() => _uitryState();
}
class _uitryState extends State<uitry> {
List<String> wordList = [
'Shopping',
'Brunch',
'Music',
'Road Trips',
'Tea',
'Trivia',
'Comedy',
'Clubbing',
'Drinking',
'Wine',
];
List<String> selectedWord = [];
List<String>? deSelectedWord = [];
#override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
return Scaffold(
body: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(Config.app_background4), fit: BoxFit.fill),
),
child: SafeArea(
child: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 14, right: 0),
child: Column(
children: [
SizedBox(
width: width * 0.94,
height: height * 0.30,
child: Column(
children: <Widget>[
const SizedBox(height: 16),
Wrap(
children: wordList.map(
(word) {
bool isSelected = false;
if (selectedWord!.contains(word)) {
isSelected = true;
}
return GestureDetector(
onTap: () {
if (!selectedWord!.contains(word)) {
if (selectedWord!.length < 50) {
selectedWord!.add(word);
deSelectedWord!.removeWhere(
(element) => element == word);
setState(() {});
print(selectedWord);
}
} else {
selectedWord!.removeWhere(
(element) => element == word);
deSelectedWord!.add(word);
setState(() {
// selectedHobby.remove(hobby);
});
print(selectedWord);
print(deSelectedWord);
}
},
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 5, vertical: 4),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 5, horizontal: 12),
decoration: BoxDecoration(
color: isSelected
? HexColor('#0000FF')
: HexColor('#D9D9D9'),
borderRadius: BorderRadius.circular(18),
border: Border.all(
color: isSelected
? HexColor('#0000FF')
: HexColor('#D9D9D9'),
width: 2)),
child: Text(
word,
style: TextStyle(
color: isSelected
? Colors.black
: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w600),
),
),
),
);
},
).toList(),
),
],
),
),
],
),
),
],
))),
),
);
}
}
How get that from firestore? I hope You can understand what I ask. Thank you!
I would do the following:
Initialize your list of words to an empty list
Use the initState method of the stateful widget to make a call to firestore to fetch all the documents that have the wordName property and get the word from the result and set it to a new list
Assign the new list to the wordList property and setState to re-render.
This would be it to get the words and set the chips with fetched words.
Keep in mind that since you are making an async call to firestore you should show some form of loading to tell the user you are fetching the data otherwise you would show and empty chip list until you fetch the data.
I have a list of elements (icons), I need to get the index of rows in the list. That is, 1 row = index, I need to get this index. I can't loop through the array to get the position of each row correctly. How can I get the index of each row in the list and pass it to the _itemPicture method? I need to show the first 3 lines in purple, the remaining 4, 5 and 6 lines in green.
List<Widget> _convertListItems(List<dynamic> list, bool isSecondList) {
late List<Widget> children;
if (isSecondList) {
children = [
for (int i in list)
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
for (var j = 1; j <= i; j++) Padding(
padding: const EdgeInsets.only(right: 3),
child: _itemPicture(list),
),
],
),
)
];
}
return children;
Widget _itemPicture(List<dynamic>? list) {
return const SizedBox(
height: 14,
width: 9,
child: Icon(Icons.bolt, color: constants.Colors.purpleMain),
);
}
list
List<int> list = [
1,
2,
3,
1,
2,
3,
];
You can use for loop like this.
for (var i= 0; i < list.length; i++) Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(children: [
for (int j = 1; j <= list.elementAt(i); j++) Padding(
padding: const EdgeInsets.only(right: 3),
child: _itemPicture(list, i),
),
SizedBox(width: 8), Text("$i")
],
),
],
),
)
Your _itemPicture widget may look like this.
Widget _itemPicture(List<dynamic>? list,int i) {
if(i<3){
return SizedBox(
height: 14,
width: 9,
child: Icon(Icons.bolt, color: Colors.purple),
);
}
return SizedBox(
height: 14,
width: 9,
child: Icon(Icons.bolt, color: Colors.green),
);
}
You can use the asMap method on your list. It will transform your List into a Map and each key will be an integer starting from 0. Maybe it can help you.
list
.asMap()
.map((index, i) {
return MapEntry(
index,
Padding(...)
)
})
.values
.toList()
Edit :
To transfer the index to _itemPicture :
Widget _itemPicture(List<dynamic>? list, int index) {
return const SizedBox(
height: 14,
width: 9,
child: Icon(Icons.bolt, color: constants.Colors.purpleMain),
);
}
...
child: _itemPicture(list, index),
...
If I get it right, you want to draw a number of icons(e.g icons.blot) that equals the index of the item list you are on (or the item itself not sure)... However, if so then just use ListView.builder when you can specify the number of widgets you wanna build and how they look: just like this:
ListView.builder
itemCount: i, //OR list[i]
itemBuilder: (_, __) => _itemPicture(),
)
Widget _itemPicture() {
return const SizedBox(
height: 14,
width: 9,
child: Icon(Icons.bolt, color: constants.Colors.purpleMain),
);
}
Maybe it needs some changes or fixes to exactly work as u want (I didn't test it)... but in general this should work
I am using graphql_flutter to load data from the server and I should update moreId for the update page in my server and get more data to load, and I need to use infinite for it.
How can I do it?
class MoreEpd extends StatefulWidget {
final String moreId;
const MoreEpd({Key? key, required this.moreId}) : super(key: key);
#override
_MoreEpdState createState() => _MoreEpdState();
}
class _MoreEpdState extends State<MoreEpd> {
double pageWidth = 0;
double pageHeigh = 0;
int pageNum = 0;
final String leftArrow = 'assets/icons/left-arrow.svg';
String getSearchResult = """
query homeview(\$moreId: ID!, \$page: Int! ){
homeview(HM_ID: \$moreId, page: \$page){
HM_ID
HM_Type_ID
HM_Type_Name
HM_NAME
Priority
Details{
HM_Det_ID
HM_ID
Ep_ID
Pod_ID
Link
Image
title
Pod_title
}
}
}
""";
#override
Widget build(BuildContext context) {
pageWidth = MediaQuery.of(context).size.width;
pageHeigh = MediaQuery.of(context).size.height;
return Container(
child: Query(
options: QueryOptions(
document: gql(getSearchResult),
variables: {'moreId': widget.moreId, 'page': pageNum},
),
builder: (
QueryResult result, {
Refetch? refetch,
FetchMore? fetchMore,
}) {
return handleResult(result);
},
),
);
}
Widget handleResult(QueryResult result) {
var data = result.data!['homeview']['Details'] ?? [];
return Container(
child: ListView.builder(
padding: EdgeInsets.only(top: 15),
shrinkWrap: true,
itemCount: data.length ,
itemBuilder: (context, index) {
return InkWell(
onTap: () {},
child: Padding(
padding: EdgeInsets.only(
top: pageWidth * 0.0,
right: pageWidth * 0.08,
left: pageWidth * 0.08,
bottom: pageWidth * 0.0),
child: Container(
child: Stack(
children: [
Column(
children: [
Padding(
padding:
EdgeInsets.only(bottom: pageWidth * 0.060),
child: Row(
children: [
Padding(
padding:
EdgeInsets.only(left: pageWidth * 0.01),
child: Container(
// alignment: Alignment.centerRight,
width: pageWidth * 0.128,
height: pageWidth * 0.128,
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: CachedNetworkImageProvider(
data[index]['Image'],
)),
borderRadius: BorderRadius.all(
Radius.circular(15)),
// color: Colors.redAccent,
border: Border.all(
color: MyColors.lightGrey,
width: 1,
)),
),
),
Expanded(
child: Row(
children: [
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Container(
width: pageWidth * 0.5,
alignment: Alignment.centerRight,
child: Text(
data[index]['title'],
textAlign: TextAlign.right,
overflow: TextOverflow.ellipsis,
maxLines: 1,
// softWrap: true,
style: TextStyle(
// fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
],
),
],
),
)
],
),
),
],
),
],
),
),
),
);
}));
}
}
First error is happening because of not handling the states of Query. In order to do that on builder:
delearing data on state level var data = [];
builder: (
QueryResult result, {
Refetch? refetch,
FetchMore? fetchMore,
}) {
if (result.hasException) {
return Text(result.exception.toString());
}
if (result.isLoading) {
return Column(
children: [
Expanded(
child: handleResult(data)), // show data while loading
const Center(
child: CircularProgressIndicator(),
),
],
);
}
data.addAll(result.data!['homeview']['Details'] ?? []);
return handleResult(data);
},
All we need now to increase the pageNum to get more data. I'm using load more button, better will be creating the load button end of list by increasing itemLength+=1.
Update using ScrollController.
// on state class
final ScrollController controller = ScrollController();
bool isLoading = false;
Load data on scroll
#override
void initState() {
super.initState();
controller.addListener(() {
/// load date at when scroll reached -100
if (controller.position.pixels >
controller.position.maxScrollExtent - 100) {
print("Scroll on loading");
if (!isLoading) {
print("fetching");
setState(() {
pageNum++;
isLoading = true;
});
}
}
});
}
Full Snippet on Gist
And about the position issue, you can check this
I was looking at a UI which have different cards placed vertically, I tried adding onTap feature to open a separate page for different cards each.In this UI there's vertical cards placed with the data of images,title and a read later button,Adding a gesture detector above clipReact to add the onTap material page route feature and adding onTap below the read later button but where should I place the onTap
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (_) => /*what to define here*/)),
Is there a way to add separate onTap feature for separate page for different cards ?
class CardScrollWidget extends StatelessWidget {
var currentPage;
var padding = 20.0;
var verticalInset = 20.0;
CardScrollWidget(this.currentPage);
#override
Widget build(BuildContext context) {
return new AspectRatio(
aspectRatio: widgetAspectRatio,
child: LayoutBuilder(builder: (context, contraints,) {
var width = contraints.maxWidth;
var height = contraints.maxHeight;
var safeWidth = width - 2 * padding;
var safeHeight = height - 2 * padding;
var heightOfPrimaryCard = safeHeight;
var widthOfPrimaryCard = heightOfPrimaryCard * cardAspectRatio;
var primaryCardLeft = safeWidth - widthOfPrimaryCard;
var horizontalInset = primaryCardLeft / 2;
List<Widget> cardList = new List();
for (var i = 0; i < images.length; i++) {
var delta = i - currentPage;
bool isOnRight = delta > 0;
var start = padding + max(primaryCardLeft -horizontalInset * -delta * (isOnRight ? 15 : 1),
0.0);
var cardItem = Positioned.directional(
top: padding + verticalInset * max(-delta, 0.0),
bottom: padding + verticalInset * max(-delta, 0.0),
start: start,
textDirection: TextDirection.rtl,
child: GestureDetector(
child: ClipRRect(
borderRadius: BorderRadius.circular(16.0),
child:Container(
decoration: BoxDecoration(color: Colors.white, boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(3.0, 6.0),
blurRadius: 10.0)
]),
child: AspectRatio(
aspectRatio: cardAspectRatio,
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Image.asset(images[i], fit: BoxFit.cover),
Align(
alignment: Alignment.bottomLeft,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 8.0),
child: Text(title[i],
style: TextStyle(
color: Colors.white,
fontSize: 25.0,
fontFamily: "SF-Pro-Text-Regular")),
),
SizedBox(
height: 10.0,
),
Padding(
padding: const EdgeInsets.only(
left: 12.0, bottom: 12.0),
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 22.0, vertical: 6.0),
decoration: BoxDecoration(
color: Colors.blueAccent,
borderRadius: BorderRadius.circular(20.0)),
child: Text("Read Later",
style: TextStyle(color: Colors.white)),),),],),)],)),),),
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => )),
),);
cardList.add(cardItem);
}
return Stack(
children: cardList,
);}),);}}
data-
List<String> images = [
"assets/image_04.jpg",
"assets/image_03.jpg",
"assets/image_02.jpg",
"assets/image_01.png",
];
List<String> title = [
"Hounted Ground",
"Fallen In Love",
"The Dreaming Moon",
"Jack the Persian and the Black Castel",
];
you can change your data structure to place your destination page and put it to MaterialPageRoute builder method.
List pages = [
{
"image" : "assets/image_01.png",
"title" : "Hounted Ground 1",
"page" : YOURWIDGET(),
},
{
"image" : "assets/image_02.png",
"title" : "Hounted Ground2",
"page" : YOURWIDGET(),
},
{
"image" : "assets/image_03.png",
"title" : "Hounted Ground 3",
"page" : YOURWIDGET(),
}
];
// in your cards page build method
// note that you should place below code in a multi widget support widget like Column(), ListView(), ...
Column(
children: pages.map((page) {
return Card(
// card content and attributes
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext ctx) {
return page['page'];
}
));
}
);
}).toList()
);
onTap: () => Navigator.of(context).push(MaterialPageRoute(
builder: (_) => (i == 0)
? pageOne()
: (i == 1)
? pageTwo()
: (i == 2)
? pageThree()
: pageFour())),