multi selection like iOS picture in Flutter
How can I implement an UI with Flutter like the following video?
There are tiles / Rectangles arranged.
They are available for multiple selections.
In this video, I just drug from [B] to [R] (like Picture-1), and [B] to [R] were selected (like Picture-2), even though I did not touch neither [F],[K],[P] nor [E],[J],[O].
what to do with GestureDetector?
And I also wonder whether this is possible with rectangle painted on Canvas?
you can try this, drag_select_grid_view:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final controller = DragSelectGridViewController();
#override
void initState() {
super.initState();
controller.addListener(scheduleRebuild);
}
#override
void dispose() {
controller.removeListener(scheduleRebuild);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: SelectionAppBar(
selection: controller.value,
),
body: DragSelectGridView(
gridController: controller,
itemCount: 20,
itemBuilder: (context, index, selected) {
return SelectableItem(
index: index,
selected: selected,
);
},
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 80,
),
),
);
}
void scheduleRebuild() => setState(() {});
}
Related
I'm using Package Confetti 0.6.0 ConfettiWidget() within Card()s generated in a GridView.builder
Upon scroll the animation stops working properly & even vanishes if I scroll too much.
I thought the issue had to do with lazy loading, so I tried two other particle animation widgets:
Particles 0.1.4 and
Animated Background 2.0.0
Both of which worked fine, displaying correct animation - even through scrolling.
-> It could be just an issue with Package Confetti 0.6.0, but I think the problem might come from the way I'm initializing the ConfettiController and calling .play() method & disposing of it.
Here is my complete simplified code:
class HomePage extends StatefulWidget {
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late ConfettiController _controllerCenter;
#override
void initState() {
super.initState();
_controllerCenter =
ConfettiController(duration: const Duration(seconds: 1000));
_controllerCenter.play();
}
#override
void dispose() {
_controllerCenter.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: SafeArea(
child: new GridView.builder(
itemCount: 30,
itemBuilder: (BuildContext context, int index) {
return Card(
child: Center(
//
child: ConfettiWidget(
confettiController: _controllerCenter,
shouldLoop: true,
),
//
),
);
},
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
),
),
);
}
}
-> I'd like to keep the animation running despite scrolling through the GridView.
I need to use a GridView.builder or GridView.count as data within the cards will be populated with Firebase Data.
On the screen I expect to see an ever growing list:
0
1
2
...
but it's just black. If I hit CTRL+S in Android Studio all of a sudden the whole list is finally shown.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Widget> list = [];
#override
Widget build(BuildContext context) {
print('${list.length}');
return MaterialApp(
home: ListView(
children: list,
),
);
}
#override
void initState() {
super.initState();
new Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
list.add(Text('${list.length}', key: Key(list.length.toString())));
});
});
}
}
At first I thought it was because I wasn't setting key property, but I added that and still nothing.
I know the build() property is being triggered every second because I see the print() statement showing the numbers in the debug Run tab output.
What am I missing here? It's got to be something simple!
Interesting discovery.
What is actually causing your UI to not update is that the reference to the list variable is staying the same.
Adding an element, doesn't change the reference and ListView seems to work based on references.
Two ways you can confirm this are.
Change your ListView to a much simpler Column widget and voila, it's updating correctly.
Change your updation method. Destroy the reference and make a new List like this,
list = [...list, Text('${list.length}')];
and yet again, surprise.
You need to 'key' parameter to 'ListView' widget.
With below code, I confirmed working what you want.
I think that because you add a 'key' to Text widget, 'ListView' widget didn't rebuild.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Widget> list = [];
#override
Widget build(BuildContext context) {
print('${list.length}');
return MaterialApp(
home: ListView(
key: UniqueKey(),
children: list,
),
);
}
#override
void initState() {
super.initState();
new Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
list.add(Text('${list.length}', key: Key(list.length.toString())));
});
});
}
}
Or you can do it also by changing 'ListView' to 'ListView.builder'.
ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return list[index];
},
),
);
Part of my Flutter app has a filter function which filters through a list of items. This being part of a UX should indicate the current operation to the user.
The issue I am facing is displaying the current item being filtered (evaluated) - the images simply won't be displayed or will "get stuck" on 1 - reason most likely being the run loop set the state too fast for the redraw to take place.
Here is a rough example of what I am trying to accomplish
import 'dart:async';
import 'package:flutter/material.dart';
void main() async {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int i = 0;
List<String> _images = [
"assets/sample/sample1.jpg",
"assets/sample/sample2.jpg",
"assets/sample/sample3.jpg",
"assets/sample/sample4.jpg",
];
#override
void initState() {
super.initState();
Timer(Duration(), () {
while (true) {
i++;
setState(() {
i = i % 4;
});
}
});
}
#override
Widget build(BuildContext context) {
Widget content = Container(
child: ClipRRect(
clipBehavior: Clip.antiAlias,
child: CircleAvatar(
radius: 24,
backgroundColor: Colors.transparent,
child: Image.asset(_images[i], gaplessPlayback: true,),
),
));
return MaterialApp(
home: Scaffold(
body: content,
),
);
}
Question:
To simplify things, how can I display a few images rapidly (and repeatedly) i.e. 1, 2, 3, 4, 1, 2, 3... with a few milliseconds delay between (at most)?
Don't use while loop because it will block the thread and does not let other tasks be executed
you could use something like Timer.periodic and it gives you the option to control the frame rate that images should change
and also don't use setState because it causes the entire widget to rebuilt you can use ValueNotifer to notify the specific widget direct with changes
and also remember to cancel the timer when your widget gets disposed
here is example
class _PageAState extends State<PageA> {
ValueNotifier<String> currentImage = ValueNotifier("0.png");
int counter=0;
Timer imageTimer;
#override
void initState() {
super.initState();
this.imageTimer=Timer.periodic(Duration(milliseconds: 100), (timer) {
counter++;
currentImage.value="${counter%3}.png";
});
}
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<String>(
valueListenable: currentImage,
builder: (context, value, child) {
return Image.asset(
value,
width: 30,
height: 30,
color: Colors.red,
);
},
);
}
#override
void dispose() {
super.dispose();
if(imageTimer!=null)imageTimer.cancel();
}
}
What is the best way to do the bottom navigation bar in flutter?
According to Getting to the Bottom of Navigation in Flutter the flutter team used IndexedStack with Offstage to show the widget when the user changes the tab, but I see there's another way of doing this by TabBarView to change between widgets with simple slide animation and also keeping each widget's scroll state
So what's the difference between the IndexedStack + Offstage and TabBarView? and what is the best way to change the current tab should I use something like flutter_bloc or just use setState()?
OverView
Well, there are many ways to implement BottomNavigationBar in Flutter.
But Using the IndexedStack method would create all the screens of theBottomNavigationBar at the starting. This can be fixed using TabBarView.
Here's how I Implemented BottomNavigationBar in my app using CupertinoTabBar and PageView as it would make only one screen at the starting. And also using AutomaticKeepAliveMixin as it wouldn't let the screens to be recreated again.
KeyPoints
PageView with PageController by which you can easily shift between screens.
AutomaticKeepAliveClientMixin doesn't let the screens to be recreated and thus there is no need to use IndexedStack.
Use Provider and Consumer to recreate only the CupertinoTabBar when changing the currentIndex. Instead of using setState(), as it would recreate the whole screen letting all the widgets to get rebuild. But here were are using Provider to recreate only TabBar.
Code Example
HomePage (BottomNavigtionBar)
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
PageController _pageController;
#override
void initState() {
_pageController = PageController();
super.initState();
}
#override
void dispose() {
_pageController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
// Wrapping the CupertinoTabBar in Consumer so that It only get
// recreated.
bottomNavigationBar: Consumer<HomeVM>(
builder: (context, model, child) {
return CupertinoTabBar(
backgroundColor: Colors.white10,
currentIndex: model.currentPage,
onTap: (index) {
index == model.currentPage
? print('same screen')
: _pageController.jumpToPage(
index,
);
model.changePage(index);
},
items: bottomNavItems);
},
),
body:ChangeNotifierProvider(
create: (_) => locator<HelpVM>(),
child: SafeArea(
top: false,
child: PageView(
controller: _pageController,
physics: NeverScrollableScrollPhysics(),
children: <Widget>[
FrontScreen(),
WorkRootScreen(),
HelpScreen(),
AccountScreen(),
],
),
),
),
);
}
const List<BottomNavigationBarItem> bottomNavItems =
<BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: const Icon(
FontAwesomeIcons.home,
),
),
//...... bottomNavigationBarItems
];
}
HomeVM (Using Provider to change the index and recreate only TabBar using Consumer)
class HomeVM extends ChangeNotifier {
int _currentPage = 0;
int get currentPage => _currentPage;
void changePage(int index) {
this._currentPage = index;
notifyListeners();
}
}
FrontScreen (Here we are using AutomaticKeepAliveClientMixin to retain the state by not recreating the Widget)
class FrontScreen extends StatefulWidget {
#override
_FrontScreenState createState() => _FrontScreenState();
}
class _FrontScreenState extends State<FrontScreen>
with AutomaticKeepAliveClientMixin {
#override
Widget build(BuildContext context) {
// VIMP to Add this Line.
super.build(context);
return SafeArea(
// Your Screen Code
);
}
#override
bool get wantKeepAlive => true;
}
I'm codeing an app with flutter an i'm haveing problems with the development. I'm trying to have a listview with a custom widget that it has a favourite icon that represents that you have liked it product. I pass a boolean on the constructor to set a variables that controls if the icons is full or empty. When i click on it i change it state. It works awesome but when i scroll down and up again it loses the lastest state and returns to the initial state.
Do you know how to keep it states after scrolling?
Ty a lot <3
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index){
return new LikeClass(liked: false);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final bool liked;//i want this variable controls how heart looks like
LikeClass({this.liked});
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
bool liked;
#override
void initState() {
liked=widget.liked;
}
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap:((){
setState(() {
liked=!liked;
//widget.liked=!widget.liked;
});
}),
child: new Icon(Icons.favorite, size: 24.0,
color: liked?Colors.red:Colors.grey,
//color: widget.liked?Colors.red:Colors.grey,//final method to control the appearance
),
),
],
),
);
}
}
You have to store the state (favorite or not) in a parent widget. The ListView.builder widget creates and destroys items on demand, and the state is discarded when the item is destroyed. That means the list items should always be stateless widgets.
Here is an example with interactivity:
class Item {
Item({this.name, this.isFavorite});
String name;
bool isFavorite;
}
class MyList extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyListState();
}
class MyListState extends State<MyList> {
List<Item> items;
#override
void initState() {
super.initState();
// Generate example items
items = List<Item>();
for (int i = 0; i < 100; i++) {
items.add(Item(
name: 'Item $i',
isFavorite: false,
));
}
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListItem(
items[index],
() => onFavoritePressed(index),
);
},
);
}
onFavoritePressed(int index) {
final item = items[index];
setState(() {
item.isFavorite = !item.isFavorite;
});
}
}
class ListItem extends StatelessWidget {
ListItem(this.item, this.onFavoritePressed);
final Item item;
final VoidCallback onFavoritePressed;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(item.name),
leading: IconButton(
icon: Icon(item.isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: onFavoritePressed,
),
);
}
}
If you don't have many items in the ListView you can replace it with a SingleChildScrollview and a Column so that the Widgets aren't recycled. But it sounds like you should have a list of items where each item has an isFavourite property, and control the icon based on that property. Don't forget to setState when toggling the favorite.
Other answer are better for your case but this an alternative and can be used if you want to only keep several elements alive during a scroll. In this case you can use AutomaticKeepAliveClientMixin with keepAlive.
class Foo extends StatefulWidget {
#override
FooState createState() {
return new FooState();
}
}
class FooState extends State<Foo> with AutomaticKeepAliveClientMixin {
bool shouldBeKeptAlive = false;
#override
Widget build(BuildContext context) {
super.build(context);
shouldBeKeptAlive = someCondition();
return Container(
);
}
#override
bool get wantKeepAlive => shouldBeKeptAlive;
}
ListView.builder & GridView.builder makes items on demand. That means ,they construct item widgets & destroy them when they going beyond more than cacheExtent.
So you cannot keep any ephemeral state inside that item widgets.(So most of time item widgets are Stateless, but when you need to use keepAlive you use Stateful item widgets.
In this case you have to keep your state in a parent widget.So i think the best option you can use is State management approach for this. (like provider package, or scoped model).
Below link has similar Example i see in flutter.dev
Link for Example
Hope this answer will help for you
A problem with what you are doing is that when you change the liked variable, it exists in the Widget state and nowhere else. ListView items share Widgets so that only a little more than are visible at one time are created no matter how many actual items are in the data.
For a solution, keep a list of items as part of your home page's state that you can populate and refresh with real data. Then each of your LikedClass instances holds a reference to one of the actual list items and manipulates its data. Doing it this way only redraws only the LikedClass when it is tapped instead of the whole ListView.
class MyData {
bool liked = false;
}
class _MyHomePageState extends State<MyHomePage> {
List<MyData> list;
_MyHomePageState() {
// TODO use real data.
list = List<MyData>();
for (var i = 0; i < 100; i++) list.add(MyData());
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return new LikeClass(list[index]);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final MyData data;
LikeClass(this.data);
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap: (() {
setState(() {
widget.data.liked = !widget.data.liked;
});
}),
child: new Icon(
Icons.favorite,
size: 24.0,
color: widget.data.liked ? Colors.red : Colors.grey,
),
),
],
),
);
}
}