How to randomly position widgets in a layout - flutter

Lets say I want to randomly position the widgets in a specific layout, like in the image attached below, how could I achieve it?
I was thinking of using a wrap widget, but that did not quit work, because it is not randomizing the children in a line. My code until now
return Wrap(
spacing: 30,
children: [
buildprofile(),
buildprofile(),
buildprofile(),
buildprofile(),
],
);
buildprofile() {
return Column(
children: [
CircleAvatar(
radius: 64,
backgroundColor: Colors.pink,
child: (CircleAvatar(
radius: 62,
backgroundImage: NetworkImage(profilepic),
)),
),
SizedBox(
height: 10,
),
Text(
"Sivaram",
style: mystyle(16, Colors.black, FontWeight.w700),
)
],
);
}

You could use flutter_staggered_grid_view
StaggeredGridView.count(
crossAxisCount: 4,
children: List.generate(
3,
(index) => Center(
child: CircleAvatar(
radius: 64,
backgroundColor: Colors.pink,
),
)),
staggeredTiles: [
StaggeredTile.count(2, 2), // takes up 2 rows and 2 columns space
StaggeredTile.count(2, 1), // takes up 2 rows and 1 column
StaggeredTile.count(1, 2), // takes up 1 row and 2 column space
], // scatter them randomly
);

You can create class Person, and store profile name and image,
class Person {
String name;
String imageUrl;
}
and in your code can store all your persons in array
List<Person> persons = [Person(), Person(),....]
Wrap(
spacing: 30,
children: _children
);
List<Widget> _children {
List<Widget> _widgets = List<Widget>();
List<Persons> _randomList = persons.shuffle();
_randomList.forEach((person) {
_widgets.add(_buildProfile(person))
});
return _widgets;
}

Related

How to hide first 3 list items and show other items on button click?

I have two scrollable lists (I attached a screenshot below). The first three elements of the list are purple, the remaining 4, 5 and 6 elements are green. I need to show only purple colors when clicking on the corresponding button, or show only green colors. I managed to make it so that when pressed, leave 3 purple elements and hide the green ones. But how can I make it so that I can hide more purple elements (the first 3 elements) and show only green ones?
List<Widget> _convertListItems(List<dynamic> list, bool isSecondList) {
late List<Widget> children;
if (isSecondList) {
children = [
for (var i = 0; !widget.isPowerAC || widget.isPowerAll ? i < list.length : i < 3; i++)
Padding(
padding: const EdgeInsets.only(bottom: 10),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
for (var j = 1; j <= list.elementAt(i); j++) Padding(
padding: const EdgeInsets.only(right: 3),
child: _itemPicture(list, i),
),
],
),
)
];
}
else {
children = [
for (int i = 0; !widget.isPowerAC || widget.isPowerAll ? i < list.length : i < 3 ; i++) _item(list[i], i),
];
}
return children;
}
Widget _item(String? text, int pos) {
return Align(
alignment: Alignment.centerLeft,
child: Container(
height: 48,
width: double.infinity,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: currentPosition == pos
? pos < 3
? constants.Colors.purpleMain
: constants.Colors.green
: Colors.transparent,
),
),
child: Text(
text!,
style: currentPosition == pos
? constants.Styles.smallHeavyTimerTextStyleWhite
: constants.Styles.smallerBookTextStyleWhite.copyWith(
color: constants.Colors.white.withOpacity(0.5),
),
),
),
);
}
Widget _itemPicture(List<dynamic>? list, int pos) {
return SizedBox(
height: 14,
width: 9,
child: Icon(Icons.bolt, color: pos <= 2 ? constants.Colors.purpleMain : constants.Colors.green),
);
}
}
list
List<String> list= [
'7.4kW',
'11kW',
'22kW',
'30kW',
'150kW',
'350kW',
];
List<int> secondList = [
1,
2,
3,
1,
2,
3,
];
before pass the list to the list view, make for loop and create new list that contain only purple ones and pass new list two list view.this way you can filter it and show what ever you want.

Flutter - Compare two lists

I'm trying to compare two lists to show an image, depending on its result.
The basic idea is to show a list of pictures (with a lowered opacity) and when one element is part of both lists to show the picture without opacity.
when using print() on both lists I get the following results:
s: [Aquatic, Desert, Grassland, Temperate, Tigra, Tropical, Tundra]
biomes: [Grassland, Tropical]
so the idea is, that only Grassland and Tropical (in this example) gets fully shown, while the others stay translucent.
Unfortunately all pictures stay translucent and I'm not quite sure what I'm missing.
Widget BiomeElement(List<String> s, biomes) {
List<Widget> list = new List<Widget>();
for (var i = 0; i < s.length; i++) {
list.add(
Padding(
padding: const EdgeInsets.all(4.0),
child: new Opacity(
opacity: (s.contains(biomes) ? 1 : 0.3),
child: Column(
children: [
Image.asset(
'assets/biomes/biome_' + s[i].toLowerCase() + '.png',
height: 35,
width: 35),
],
),
),
),
);
}
return new Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: list
);
}
You need to change how you're checking for the match.
From your console result, biomes is a list and you're passing it to the .contains method which takes an Object and not a List of Objects.
So this check, s.contains(biomes) wouldn't work. You would have detected it if you assigned a type to biomes in your BiomeElement method.
SOLUTION:
Since you're iterating over s, you can check if the s element at the current index is contained in the biomes list like below:
biomes.contains(s[i])
I hope this can help you, first convert it into an object list
final s = ["Aquatic", "Desert", "Grassland", "Temperate", "Tigra", "Tropical", "Tundra"];
final biomes = ["Grassland", "Tropical"];
final hasil = s.map((e) => {"name": e, "opacity": biomes.contains(e)}).toList();
print(hasil);
and the result is like this:
[
{name: Aquatic, opacity: false},
{name: Desert, opacity: false},
{name: Grassland, opacity: true},
{name: Temperate, opacity: false},
{name: Tigra, opacity: false},
{name: Tropical, opacity: true},
{name: Tundra, opacity: false}
]
for your case
Widget BiomeElement(List<String> hasil) {
List<Widget> list = new List<Widget>();
for (var i = 0; i < hasil.length; i++) {
list.add(
Padding(
padding: const EdgeInsets.all(4.0),
child: new Opacity(
opacity: hasil[i]['opacity']? 1 : 0.3), // true for 1 and false for 0.3
child: Column(
children: [
Image.asset(
'assets/biomes/biome_' + hasil[i]['name'].toLowerCase() + '.png',
height: 35,
width: 35),
],
),
),
),
);
}
return new Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: list
);
}

Searchable SliverGrid Rendering Wrong Items

I have a SliverGrid. I have a search field. In my search field onChange event I have a function that searches my local sqlite db based on the keyword entered by the user returns the results and reassigns to a variable and calls notifyListeners(). Now my problem is for some weird reason whenever I search for an item the wrong item is rendered.
I checked the results from my functions by iterating over the list and logging the title and the overall count as well and the results were correct however my view always rendered the wrong items. Not sure how this is possible.
I also noticed something strange, whenever it rendered the wrong item and I went back to my code and hit save, triggering live reload, when I switched back to my emulator it now displayed the right item.
I have tried the release build on an actual phone and it's the same behaviour. Another weird thing is sometimes certain items will duplicate and show twice in my list while the user is typing.
This is my function that searches my sqlite db:
Future<List<Book>> searchBookshelf(String keyword) async {
try {
Database db = await _storageService.database;
final List<Map<String, dynamic>> rows = await db
.rawQuery("SELECT * FROM bookshelf WHERE title LIKE '%$keyword%'; ");
return rows.map((i) => Book.fromJson(i)).toList();
} catch (e) {
print(e);
return null;
}
}
This is my function that calls the above function from my viewmodel:
Future<void> getBooksByKeyword(String keyword) async {
books = await _bookService.searchBookshelf(keyword);
notifyListeners();
}
This is my actual view where i have the SliverGrid:
class BooksView extends ViewModelBuilderWidget<BooksViewModel> {
#override
bool get reactive => true;
#override
bool get createNewModelOnInsert => true;
#override
bool get disposeViewModel => true;
#override
void onViewModelReady(BooksViewModel vm) {
vm.initialise();
super.onViewModelReady(vm);
}
#override
Widget builder(BuildContext context, vm, Widget child) {
var size = MediaQuery.of(context).size;
final double itemHeight = (size.height) / 4.3;
final double itemWidth = size.width / 3;
var heading = Container(
margin: EdgeInsets.only(top: 35),
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Align(
alignment: Alignment.centerLeft,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Books',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w900),
),
Text(
'Lorem ipsum dolor sit amet.',
textAlign: TextAlign.left,
style: TextStyle(fontSize: 14),
),
],
),
),
);
var searchField = Container(
margin: EdgeInsets.only(top: 5, left: 15, bottom: 15, right: 15),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(15)),
boxShadow: [
BoxShadow(
color: Colors.black12,
blurRadius: 1.0,
spreadRadius: 0.0,
offset: Offset(2.0, 1.0), // shadow direction: bottom right
),
],
),
child: TextFormField(
decoration: InputDecoration(
border: InputBorder.none,
prefixIcon: Icon(
FlutterIcons.search_faw,
size: 18,
),
suffixIcon: Icon(
FlutterIcons.filter_fou,
size: 18,
),
hintText: 'Search...',
),
onChanged: (keyword) async {
await vm.getBooksByKeyword(keyword);
},
onFieldSubmitted: (keyword) async {},
),
);
return Scaffold(
body: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 1, right: 1),
child: LiquidPullToRefresh(
color: Colors.amber,
key: vm.refreshIndicatorKey, // key if you want to add
onRefresh: vm.refresh,
showChildOpacityTransition: true,
child: CustomScrollView(
slivers: [
SliverToBoxAdapter(
child: Column(
children: [
heading,
searchField,
],
),
),
SliverToBoxAdapter(
child: SpaceY(15),
),
SliverToBoxAdapter(
child: vm.books.length == 0
? Column(
children: [
Image.asset(
Images.manReading,
width: 250,
height: 250,
fit: BoxFit.contain,
),
Text('No books in your bookshelf,'),
Text('Grab a book from our bookstore.')
],
)
: SizedBox(),
),
SliverPadding(
padding: EdgeInsets.only(bottom: 35),
sliver: SliverGrid.count(
childAspectRatio: (itemWidth / itemHeight),
mainAxisSpacing: 20.0,
crossAxisCount: 3,
children: vm.books
.map((book) => BookTile(book: book))
.toList(),
),
)
],
),
))));
}
#override
BooksViewModel viewModelBuilder(BuildContext context) =>
BooksViewModel();
}
Now the reason I am even using SliverGrid in the first place is because I have a search field and a title above the grid and I want all items to scroll along with the page, I didn't want just the list to be scrollable.
I believe this odd behavior can be attributed to you calling vm.getBooksByKeyword() in onChanged. As this is an async method, there is no guarantee that the last result returned will be the result for the final text in the TextFormField. The reason you see the correct results after a live reload is because the method is being called again with the full text currently in the TextFormField.
The quickest way to verify this is to move the function call to onFieldSubmitted or onEditingComplete and see if it behaves correctly.
If you require calling the function with every change to the text, you will need to add a listener to the controller and be sure to only make the call after input has stopped for a specified amount of time, using a Timer, like so:
final _controller = TextEditingController();
Timer _timer;
...
_controller.addListener(() {
_timer?.cancel();
if(_controller.text.isNotEmpty) {
// only call the search method if keyword text does not change for 300 ms
_timer = Timer(Duration(milliseconds: 300),
() => vm.getBooksByKeyword(_controller.text));
}
});
...
#override
void dispose() {
// DON'T FORGET TO DISPOSE OF THE TextEditingController
_controller.dispose();
super.dispose();
}
...
TextFormField(
controller: controller,
...
);
So I found the problem and the solution:
The widget tree is remembering the list items place and providing the
same viewmodel as it had originally. Not only that it also takes every
item that goes into index 0 and provides it with the same data that
was enclosed on the Construction of the object.
Taken from here.
So basically the solution was to add and set a key property for each list item generated:
SliverPadding(
padding: EdgeInsets.only(bottom: 35),
sliver: SliverGrid(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
childAspectRatio: (itemWidth / itemHeight),
mainAxisSpacing: 20.0,
),
delegate: SliverChildListDelegate(vm.books
.map((book) => BookTile(
key: Key(book.id.toString()), book: book))
.toList()),
),
)
And also here:
const BookTile({Key key, this.book}) : super(key: key, reactive: false);
My search works perfectly now. :)

Flutter passing list of strings in CupertinoPicker widget using loops

I was trying to pass a list inside CupertinoPicker using loops but I couldn't figure it
this image contains the function I was trying to build
const List<String> currenciesList = [
'AUD',
'BRL',
'CAD',
'CNY',
'EUR',
'GBP',
'HKD',
'IDR',
'ILS',
'INR',
'JPY',
'MXN',
'NOK',
];
Container(
height: 150.0,
alignment: Alignment.center,
padding: EdgeInsets.only(bottom: 30.0),
color: Colors.lightBlue,
child:CupertinoPicker(
backgroundColor: Colors.lightBlue,
itemExtent: 32.0,
onSelectedItemChanged: (selectedIndex){
print(selectedIndex);
}, children:[
Text('USD',style: whiteColor ),
Text('EUR' , style: whiteColor),
Text('GDP', style:whiteColor),
]
),
),
As of Dart 2.3 you can use Collection For:
CupertinoPicker(
children:[
for (String name in currenciesList) Text( name ,style: whiteColor ),
]
)
You should create a Func to get all value in your list.
List<Widget> getPickerItems() {
List<Text> itemsCurrency = [];
for (var currency in currenciesList) {
itemsCurrency.add(Text(currency));
}
return itemsCurrency;
}
and add it in to children of CupertinoPicker:
CupertinoPicker(
children: getPickerItems(),
)

Fix top Row in Data table in flutter

I have a data table that scrolls horizontally and vertically due to the amount of data available. I want the top row which specifies the column names to always be visible when scrolling down.
code: No issues in the code I am getting the results, but when I scroll down The header wont be shown. Can any one help me in how to fix the header(1st row).
My header(1st row) elements keeps changing for each req,
the way its implemented is there will be cards in my dashboard onclicking each card it hits the API and I am displaying its result i.e contents of field named header list as my first row.
Content of the header keeps changing for each card. so once any card is clicked new page containing its data will popup, there I need to fix the header.
Widget getTable(BuildContext context, var data) {
Widget _widget;
_widget = SingleChildScrollView(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: DataTable(
rows: createDataRows(context, data),
columns: createDataColumns(context, data),
),
),
);
return _widget;
}
List<DataRow> createDataRows(BuildContext context, var jsonData) {
List<DataRow> _dataRows = [];
List<DataCell> _cells = [];
String _dataCellTitle;
Map<String, Object> headers = jsonData['headers'];
Map<int, String> headerMapping;
headerMapping = headers.keys
.toList()
.asMap()
.map((index, value) => MapEntry(index + 1, value));
if (jsonData["data"] == null) {
_dataRows = [];
} else {
for (var j = 0; j < jsonData["data"].length; j++) {
_cells.add(DataCell(Text('${j + 1}')));
for (int i = 1; i <= headerMapping.length; i++) {
_dataCellTitle = "${jsonData["data"][j][headerMapping[i]]}" ?? '-';
_cells.add(DataCell(Text('$_dataCellTitle')));
}
_dataRows.add(DataRow(cells: _cells));
_cells = [];
}
}
return _dataRows;
}
List<DataColumn> createDataColumns(BuildContext context, var jsonData) {
String columnTitle;
List<DataColumn> _dataColumns = [];
Map<String, Object> headers = jsonData['headers'];
Map<int, String> headerMapping;
headerMapping = headers.keys
.toList()
.asMap()
.map((index, value) => MapEntry(index + 1, value));
_dataColumns.add(DataColumn(label: Text('S. No.')));
for (int i = 1; i <= headerMapping.length; i++) {
columnTitle = headers[headerMapping[i]];
_dataColumns.add(
DataColumn(label: Text('$columnTitle')),
);
}
return _dataColumns;
}
like this here
where the first row is constant and first column also.
You can do the smart thing here, You can create two datatables one with only column, and one with rows.
Suppose your list_rows is a List<DataRow> and list_column is List<DataColumn> of your actual columns, then:
Column(
children: [
DataTable(
rows: [], // DONT PROVIDE ROWS
columns: list_column, // PROVIDE YOUR COLUMNS HERE
),
Expanded(
child: SingleChildScrollView(
child: DataTable(
rows: list_lows, // PROVIDE ROWS HERE
columns: List.generate(
list_column.length, (index) => DataColumn(label: Text(''))), // GENERATE EMPTY COLUMNS
headingRowHeight: 0, // SHRINK THE HEADER
),
),
),
],
)
This way your columns will always be on top!
Okay so you have one method getTable to get data content,
In it you are calling two methods
1) To fetch rows
2) To fetch columns.
But you didn't show the code of DataTable Widget.
I assume it is ListView or Column.
So in your DataTable Widget you should create one static Row for that heading.
ListView(
children: <Widget>[
Row(
children: <Widget>[
Text('$heading1'),
Text('$heading2'),
Text('$heading3'),
],
),
/// Your row and column
],
);
With information you provided i assume it will help you out.
I have Used Like that , and its working fine :
SingleChildScrollView dataBody() {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
columnSpacing: 10,
//sortAscending: sort,
//sortColumnIndex: 0,
columns: [
DataColumn(label: Text("Date"), numeric: false),
DataColumn(label: Text("Particulars"), numeric: false),
DataColumn(label: Text("Amount"), numeric: true)
],
rows: transactions
.map((txn) => DataRow(cells: [
DataCell(
Text(
DateFormat('dd-MMM-yyyy').format(txn.date),
style: TextStyle(fontSize: 12.0),
),
/*onTap: () {
print('Selected ${txn.date}');
}*/
),
DataCell(FittedBox(
child: Text(
txn.particulars,
),
)),
DataCell(Text(txn.amount.toString()))
]))
.toList(),
),
));
}
This is the solution to make Datatable Header sticky :
SizedBox(
height: 500,
width: 1000,
child: Scrollbar(
controller: _vertical,
thumbVisibility: true,
trackVisibility: true,
child: Scrollbar(
controller: _horizontal,
thumbVisibility: true,
trackVisibility: true,
child: SingleChildScrollView(
controller: _horizontal,
scrollDirection: Axis.horizontal,
child: Stack(
children: [
SingleChildScrollView(
controller: _vertical,
scrollDirection: Axis.vertical,
child: DataTable(
rows: list_rows, // PROVIDE ROWS HERE
columns: List.generate(
list_column.length,
(index) => DataColumn(
label: Text(''),
),
), //GENERATE EMPTY COLUMNS
headingRowHeight: 90, // HEADER HEIGHT
),
),
DataTable(
rows: [], // DONT PROVIDE ROWS
columns:
list_column, // PROVIDE YOUR COLUMNS HERE
headingRowHeight: 90, // HEADER HEIGHT
headingRowColor: MaterialStateColor.resolveWith(
(states) => Color.fromRGBO(86, 153, 255, 1),
),
border: TableBorder.all(
width: 1.0, color: Colors.white),
),
],
),
),
),
),
),