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
);
}
Related
I'm trying to find how could i can load network image in my Pdf. I have a list of details including an image urls. and i want to create the same list in a pdf with multiple pages.
So, i used pdf plugin to create the pages. All the details where loaded except the images. since the images should be downloaded from the server. The pdf pages are created with special widget elements (as like the flutter elements, but call with a prefix) and there is no FutureBuilder to manage the await functions.
Could anybody please help. I really stuck on this.
generatePdf(List<BhaiModel> bhaiList) async {
itemCount = 0;
List<BhaiModel> pageItems = [];
for (int i = 0; i < bhaiList.length; i++) {
if ((i + 1) % 5 == 0) {
pageItems.add(bhaiList[i]);
createPage(pageItems);
pageItems = [];
} else {
pageItems.add(bhaiList[i]);
if (i + 1 == bhaiList.length) {
createPage(pageItems);
}
}
}
final output = await getApplicationDocumentsDirectory();
final file = File("${output.path}/sample.pdf");
await file
.writeAsBytes(await pdf.save())
.then((value) => print("pdf saved"));
OpenFile.open("${output.path}/sample.pdf");
print(output.path);
}
createPage(List<BhaiModel> pageItems) async {
pdf.addPage(pw.Page(
pageFormat: PdfPageFormat.a4,
margin: const pw.EdgeInsets.all(30),
build: (pw.Context context) => pw.Column(
mainAxisAlignment: pageItems.length == 5
? pw.MainAxisAlignment.spaceEvenly
: pw.MainAxisAlignment.start,
children: pageItems.map((e) => customBhaiPdfItem(e)).toList())));
}
pw.Row customBhaiPdfItem(BhaiModel bhai) {
itemCount++;
final netImage = await networkImage(bhai.photo_url);
//THIS MAKE THE FUNCTION ASYNC
return pw.Row(children: [
pw.Container(
width: 530,
padding: const pw.EdgeInsets.all(8),
decoration: pw.BoxDecoration(border: pw.Border.all()),
child: pw.Row(mainAxisSize: pw.MainAxisSize.min, children: [
pw.Container(
color: const PdfColor(0.5, 0.5, 0.5),
width: 120,
height: 120,
child: pw.Image(netImage)),
pw.SizedBox(width: 20),
pw.Flexible(
child: pw.Container(
child: pw.Column(
mainAxisSize: pw.MainAxisSize.min,
crossAxisAlignment: pw.CrossAxisAlignment.stretch,
children: [
CustomRowText("No.", "$itemCount"),
CustomRowText("Name", "${bhai.first_name} ${bhai.last_name}"),
CustomRowText("Adhaar No.", bhai.adhaar_no),
CustomRowText("Mobile No.", bhai.mobile_no),
CustomRowText("Checkout At.",
bhai.last_checkout.replaceFirst('T', ' ').split('.')[0]),
CustomRowText("Address", bhai.address),
CustomRowText("City", bhai.city),
CustomRowText("State", bhai.state),
],
),
),
),
]),
),
]);
}
pw.Row CustomRowText(String label, String data) {
return pw.Row(children: [
pw.Expanded(
flex: 2,
child: pw.Text(label,
style: pw.TextStyle(fontWeight: pw.FontWeight.bold))),
pw.Expanded(flex: 7, child: pw.Text(data))
]);
}
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.
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;
}
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. :)
I am trying to create a dynamic multilevel CupertinoPicker. When you select a location type from the first list, it displays the list of locations that match that type. This part is working fine, the problem is that if I swap to a different list of locations, the first Text widget of the second list of locations is indented according to the first Text widget of the first list of locations.
I've tried specifying that the Text widget should be aligned to the center using 'alignment: TextAlignment.center'. I also tried setting the location to null when swapping between location lists. Neither of these solved the problem or had any apparent effect.
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
padding: EdgeInsets.only(bottom: 5.0),
height: pickerHeight,
width: logicalSize.width,
child: CupertinoPicker(
backgroundColor: Colors.white,
itemExtent: 32.0,
onSelectedItemChanged: (selectedIndex) {
setState(() {
location = null;
locationType = locationTypeList[selectedIndex];
});
},
children: pickerLocationType,
),
),
Container(
height: pickerHeight,
width: logicalSize.width,
child: CupertinoPicker(
backgroundColor: Colors.white,
itemExtent: 30.0,
onSelectedItemChanged: (selectedIndex) {
setState(() {
location = null;
if (locationType == 'Campus') {
location = campusList[selectedIndex];
}
if (locationType == 'City') {
location = cityList[selectedIndex];
}
});
},
children: pickerMap[locationType],
),
),
The result should be that the first line is (imagine this set into a CupertinoPicker):
----------------------------------City 1--------------------------------------
----------------------------------City 2--------------------------------------
But it looks more like:
-------------------------------City 1-----------------------------------------
----------------------------------City 2--------------------------------------
If images are needed, I will edit this post with the link to them.
I have discovered the solution. See below:
Container(
key: ValueKey(this._locationType),
height: pickerHeight,
width: logicalSize.width,
child: CupertinoPicker(
backgroundColor: Colors.white,
itemExtent: 30.0,
onSelectedItemChanged: (selectedIndex) {
setState(() {
location = null;
if (locationType == 'Campus') {
location = campusList[selectedIndex];
}
if (locationType == 'City') {
location = cityList[selectedIndex];
}
});
},
children: pickerMap[locationType],
),
),