How to implement drag and drop with flutter - flutter

How can I move my container or any other widgets on flutter around the screen and drop at some locations?
I found flutter widgets Draggable and DragTarget. How to use them to implement the drag and drop?

Draggable and DragTarget allow us to drag a widget across the screen.
A Draggable widgets gives the ability to move to any other widget while the DragTarget acts as the sink or drop location for a Draggable widget.
Find the below code sample using which I implemented a simple odd-or-even game
Hell yeah, I'm a Game Developer ◕‿↼
import 'package:flutter/material.dart';
import 'dart:math';
class OddOrEven extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _OddOrEvenState();
}
}
class _OddOrEvenState extends State<OddOrEven> {
bool accepted = false;
Color dotColor = Colors.blue;
GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey();
int val = 0;
int score = 0;
#override
Widget build(BuildContext context) {
// assign a random number to value which will be used as the box value
val = Random().nextInt(100);
return Scaffold(
key: scaffoldKey,
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
// just a score and mock player name indicator
Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: Center(
child: Chip(
avatar: CircleAvatar(
backgroundColor: Colors.teal,
child: Text(
score.toString(),
style: TextStyle(color: Colors.white),
),
),
label: Text(
'Player Alpha',
style: TextStyle(
fontSize: 20.0,
color: Colors.black,
fontStyle: FontStyle.italic),
),
),
),
),
),
// here comes our draggable.
// it holds data which is our random number
// the child of the draggable is a container reactangural in shape and
//
Draggable(
data: val,
child: Container(
width: 100.0,
height: 100.0,
child: Center(
child: Text(
val.toString(),
style: TextStyle(color: Colors.white, fontSize: 22.0),
),
),
color: Colors.pink,
),
// This will be displayed when the widget is being dragged
feedback: Container(
width: 100.0,
height: 100.0,
child: Center(
child: Text(
val.toString(),
style: TextStyle(color: Colors.white, fontSize: 22.0),
),
),
color: Colors.pink,
),
// You can also specify 'childWhenDragging' option to draw
// the original widget changes at the time of drag.
),
// and here this row holds our two DragTargets.
// One for odd numbers and the other for even numbers.
//
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Container(
width: 100.0,
height: 100.0,
color: Colors.green,
// Even holder DragTarget
//
child: DragTarget(
builder: (context, List<int> candidateData, rejectedData) {
print(candidateData);
return Center(
child: Text(
"Even",
style: TextStyle(color: Colors.white, fontSize: 22.0),
));
},
// On will accept gets called just before it accepts the drag source.
// if needed, we can reject the data here. But we are not doing that as this is a GAME !!! :)
onWillAccept: (data) {
print("Will accpt");
return true; //return false to reject it
},
// On accepting the data by the DragTarget we simply check whether the data is odd or even and accept based on that and increment the counter and rebuild the widget tree for a new random number at the source.
onAccept: (data) {
print("On accpt");
if (data % 2 == 0) {
setState(() {
score++;
});
// How did you manage to score 3 points😮
// Congrats. You won the game.
if (score >= 3) {
showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: Text("Congrats!!"),
content: Text("No-brainer...😮"),
actions: <Widget>[
FlatButton(
child: Text("Ok."),
onPressed: () {
Navigator.of(context).pop();
setState(() {
score = 0;
});
},
)
],
);
});
}
} else {
setState(() {});
}
},
),
),
// And here is the Odd-holder
Container(
width: 100.0,
height: 100.0,
color: Colors.deepPurple,
child: DragTarget(
builder: (context, List<int> candidateData, rejectedData) {
return Center(
child: Text(
"Odd",
style: TextStyle(color: Colors.white, fontSize: 22.0),
));
},
onWillAccept: (data) {
return true;
},
onAccept: (data) {
if (data % 2 != 0) {
setState(() {
score++;
});
if (score >= 10) {
showDialog(
context: context,
builder: (BuildContext context) {
return new AlertDialog(
title: Text("Congrats!!"),
content: Text("No-brainer...😮"),
actions: <Widget>[
FlatButton(
child: Text("Thanks"),
onPressed: () {
Navigator.of(context).pop();
setState(() {
score = 0;
});
},
)
],
);
});
}
} else {
setState(() {});
}
},
),
)
],
)
],
),
),
);
}
}

If you need to drop at a non-fixed location (Draggable without a DragTarget), this can also be implemented with Stack()/Positioned() using renderbox sizing, as per How to move element anywhere inside parent container with drag and drop in Flutter?

Related

How to refetch the data inside a FutureBuilder in Flutter?

Here is my code:
#override
void initState() {
super.initState();
weatherFuture = _getWeather();
}
#override
Widget build(BuildContext context) {
double viewBoxWidth = MediaQuery.of(context).size.width;
return Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FutureBuilder(
future: weatherFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
width: viewBoxWidth * 0.35,
color: Colors.orange, // Todo: Remove this color later
child: WeatherTileColumn(
topTileDisplayText: snapshot.data.areaName,
bottomTileDisplayText: snapshot.data.weatherDescription,
topTileIcon: '🗺',
bottomTileIcon:
_getWeatherIcon(snapshot.data.weatherConditionCode),
),
);
} else {
return Text(
'Loading Data...',
style: TextStyle(fontFamily: 'Merriweather', fontSize: 15),
textAlign: TextAlign.center,
);
}
}),
Container(
width: viewBoxWidth * 0.2,
margin: EdgeInsets.only(top: 95),
child: TextButton(
child: Icon(
Icons.refresh,
color: Colors.black87,
size: 55,
),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(Colors.transparent)),
onPressed: () {
setState(() {
weatherFuture = _getWeather();
});
},
),
),
FutureBuilder(
future: weatherFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Container(
width: viewBoxWidth * 0.35,
color: Colors.purple,
child: WeatherTileColumn(
topTileDisplayText: snapshot.data.temperature.toString(),
bottomTileDisplayText:
snapshot.data.tempFeelsLike.toString(),
topTileIcon: '🌡',
bottomTileIcon: '😶‍🌫️',
),
);
} else {
return Text(
'Loading Data...',
style: TextStyle(fontFamily: 'Merriweather', fontSize: 15),
textAlign: TextAlign.center,
);
}
},
)
],
),
);
In this code, if you look at the main widgets inside the children of the Row widget, you will see that there are two FutureBuilder widgets and a Container widget.
What has to happen is upon pressing that TextButton inside the Container, I want the two future builder widgets to be re updated with newly fetched data.
My current attempt was to do:
setState() {
weatherFuture = _getWeather();
}
inside the callback function for the onPressed method. And it does not work. I looked at many solutions and articles and could not get this to work.
(Also I checked Reload data when using FutureBuilder stack overflow post and this is not a duplicate. In that post, the button that rebuilds the FutureBuilder widget is inside the FutureBuilder widget but in this case it isn't. So that solution did not work for me.)

How to not load the children of a ExpansionTile

I have the follow structure: A list of Expansion tiles > clicking on it, opens another list of ExpansionTiles > Clicking in one of them, it should open some widgets according to a SQL query.
The problem is, when I tap in the first Expansion Tile it loads all the widgets from all the Expansion Tiles inside the first option making the query very slow. I want to only load the widgets when I tap in the second one (loading only the necessary ones)
Here is the code:
1st list:
class ListItemsScreen extends StatefulWidget {
#override
_ListItemsScreenState createState() => _ListItemsScreenState();
}
class _ListItemsScreenState extends State<ListItemsScreen> {
final Widget appBar = AppBar(
title: Text('ITEMS'),
actions: [
Builder(
builder: (context) => IconButton(
icon: Icon(Icons.shopping_bag_outlined),
onPressed: () {
Navigator.of(context)
.pushNamed(ROUTE_CHART);
},
),
),
],
);
#override
Widget build(BuildContext context) {
final List items = ModalRoute.of(context).settings.arguments;
return Scaffold(
appBar: appBar,
body: items == null || items.isEmpty ?
Center(child: Text("0 items here"),)
:
ListView(
children: [
...items.map<Widget>(
(item) {
return ExpansionTile(
leading: Image.asset(ASSET_IMAGE,
fit: BoxFit.cover
),
title: Text('${item.code} | ${item.description}'),
subtitle:
Text('${item.color}'),
children: [
Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: ProductWidget(item),
),
],
),
);
},
)
],
)
);
2nd list (ProductWidget):
class ProductWidget extends StatefulWidget {
final Product product;
ProductWidget(this.produto);
#override
_ProductWidgetState createState() => _ProductWidgetState();
}
class _ProdutoGradeWidgetState extends State<ProdutoGradeWidget> {
#override
Widget build(BuildContext context) {
CustomScrollView(
slivers: [
StreamBuilder(
stream: product.stream,
builder: (ctx, snapshot) {
return SliverList(
delegate: SliverChildBuilderDelegate((ctx, i) {
if (i == 0) {
return Column(
children: [
Align(
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.only(top: 5),
child: Text(
'I HAVE THIS PRODUCT IN THESE COLORS',
style: TextStyle(
fontSize: 20,
color:
Theme.of(context).textTheme.caption.color,
),
)
),
),
const SizedBox(height: 20.0),
ProductColorsWidget(color: snapshot.data[i]),
],
);
} else if (i == snapshot.data.length - 1) {
return Column(
children: [
ProductColorsWidget(color: snapshot.data[i]),
const SizedBox(height: 20.0),
Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'Qtd',
style: TextStyle(
fontSize: 16,
color:
Theme.of(context).textTheme.caption.color,
),
),
),
),
const SizedBox(height: 20.0),
],
);
}
return ProductColorsWidget(color: snapshot.data[i]);
}, childCount: snapshot.data.length),
);
}
},
),
],
);
}
}
}
}
3rd part (Product Colors Widget where I list the second Expansion Tiles):
class ProductColorsWidget extends StatelessWidget {
final ColorProduct color;
ProdutoCorGradeWidget({this.color});
#override
Widget build(BuildContext context) {
return ExpansionTile(
maintainState: true,
tilePadding: EdgeInsets.all(15.0),
title: Text(
'${color.id} - ${color.description}',
style: Theme.of(context)
.textTheme
.subtitle1
.copyWith(fontWeight: FontWeight.w600),
),
childrenPadding: EdgeInsets.all(10.0),
children: [
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...color.sizes.map<Widget>(
(item) {
return Column(
children: [
Expanded(
child: Text(
item.description, textAlign: TextAlign.center,
style: TextStyle(fontWeight: FontWeight.bold),
)
),
...item.prices.map<Widget>((size) {
return PricesWidget( //here it should show the widgets according to the second ExpansionTiles
color: color,
size: size
);
})
]
);
}
)
],
)
)
],
);
}
}
So, to be clear, what I want is: First It lists the products (with expansionTiles), expanding one it should show the second Tiles (with sizes) and after selecting one it should show the widgets.
..But what is happening now is: List the products and when I select one the app loads all the widget from all the second 'expansionTiles' making it slow to show the second list. What should I do?
I think the problem is with
ExpansionTile(
maintainState: true,
.....
)
I had a similar issue in which I had an ExpansionTile that was its own child and it caused a stack overflow because it was building them all. After setting maintainState to false the problem was solved.
You might have to adapt your state management according to that the children state may not be saved

how to make flutter searchDelagate a separate screen that can be navigated to independently?

i have a page called searchUsersSCreen which is this:
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:myApp/models/otherUser.dart';
import 'package:myApp/ui/widgets/user_profile.dart';
import 'database.dart';
class SearchUsersScreen extends StatefulWidget {
#override
_SearchUsersScreenState createState() => _SearchUsersScreenState();
}
class _SearchUsersScreenState extends State<SearchUsersScreen> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => showSearch(
context: context,
delegate: SearchUsers(
DatabaseService().fetchUsersInSearch(),
),
));
}
#override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).primaryColor,
);
}
}
and inside the same dart file is have this searchDelegate :
//Search delegate
class SearchUsers extends SearchDelegate<OtherUser> {
final Stream<QuerySnapshot> otherUser;
final String hashtagSymbol = 'assets/svgs/flaticon/hashtag_symbol.svg';
SearchUsers(this.otherUser);
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = '';
},
),
];
}
#override
Widget buildLeading(BuildContext context) {
return Container(
width: 0,
height: 0,
);
}
#override
Widget buildResults(BuildContext context) {
return Container(
width: 0,
height: 0,
color: Theme.of(context).primaryColor,
);
}
#override
Widget buildSuggestions(BuildContext context) {
showUserProfile(String userId) {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserProfileView(
userUid: userId,
)));
}
return StreamBuilder<QuerySnapshot>(
stream: DatabaseService().fetchUsersInSearch(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
final handlesResults = snapshot.data.documents
.where((u) => u['username'].contains(query));
if (!snapshot.hasData) {
return Container(
color: Theme.of(context).primaryColor,
child: Center(
child: Text(
'',
style: TextStyle(
fontSize: 16, color: Theme.of(context).primaryColor),
),
),
);
}
if (handlesResults.length > 0) {
return Container(
color: Theme.of(context).primaryColor,
child: ListView(
children: handlesResults
.map<Widget>((u) => GestureDetector(
child: Padding(
padding: const EdgeInsets.all(0.1),
child: Container(
padding: EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration(
color: Theme.of(context).primaryColor,
border: Border(
bottom: BorderSide(
width: 0.3, color: Colors.grey[50]))),
child: ListTile(
leading: CircleAvatar(
backgroundColor:
Theme.of(context).primaryColor,
backgroundImage:
NetworkImage(u['userAvatarUrl']),
radius: 20,
),
title: Container(
padding: EdgeInsets.only(left: 10),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(u['username'],
style: TextStyle(
fontSize: 16,
color: Theme.of(context)
.accentColor),
overflow: TextOverflow.ellipsis),
SizedBox(
height: 5,
),
Text(u['name'],
style: TextStyle(
fontSize: 16,
color: Colors.grey[500],
),
overflow: TextOverflow.ellipsis),
],
),
),
trailing: Container(
padding: EdgeInsets.only(left: 10),
height: 43.0,
width: MediaQuery.of(context).size.width / 2,
),
),
),
),
onTap: () {
showUserProfile(u['id']);
},
))
.toList(),
),
);
} else {
return Container(
color: Theme.of(context).primaryColor,
child: Center(
child: Text(
'No results found',
style: TextStyle(
fontSize: 16,
color: Theme.of(context).accentColor,
),
),
),
);
}
});
}
}
i wanted to user the class SearchUsers as a separate screen that i can navigate to independently...but couldn't achieve that as SearchUsers doesn't evaluate to a widget.
so i built SearchUsersScreen statefulWidget and inside it's initState() i called this:
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => showSearch(
context: context,
delegate: SearchUsers(
DatabaseService().fetchUsersInSearch(),
),
));
}
as to make the search feature starts automatically when the user navigates to SearchUsersScreen.
and i ended up into two problems:
SearchUsers is being displayed in full screen ontop of SearchUsersSCreen (which i don't want this behavior), i want it to be displayed inside of it.
actually its covering the BottomNavigationBar i built for navigation between screens.
after SearchUsers is being displayed (and its doing its job well), when i tap the device back button...i leave SearchUsers and get back to SearchUsersScreen....which is indeed a blank screen.
so to wrap it up...all i want is to use SearchUsers class as a widget that i can navigate to and navigate from independently...thats it.
any help would be much appreciated.
thanks for your time reading.
Instead of trying to create a separate widget SearchUsers, try to create a dialog and show it when anyone wants to search users. You can also use the navigator and the back button in this case and get arguments passed from the next screen to the previous screen.

How to add Progress Indicator on Cards while tap in Flutter?

I am using Cards in Flutter and want Progress Indicator at the left bottom position for 2 seconds while Tap on the card so that another page load successfully.
Does anyone know how to add?
Container(
height: 130,
child: Card(
child: Row(
children: <Widget>[
Expanded(
child: ListTile(
title: Text(
'My card Location',
style: TextStyle(
fontSize: 15, fontWeight: FontWeight.w700),
),
leading: Icon(Icons.setting),
// color: Colors.blueAccent, size: mediumIconSize),
trailing: Icon(Icons.keyboard_arrow_right),
selected: true,
onTap: () async {
// I try this one but not working
// Flushbar(
//
// showProgressIndicator: true,
// duration: Duration(seconds: 2),
// );
getDetails().then((myCardlocations) {
Navigator
.of(context)
.pushNamed('/myCardlocations',
arguments: ObjectLocations(locations, 'myCardlocations'));
}
);
}
),
),
],
),
),
),
You can do something like this using Stack and CircularProgressIndicator..
class _MyWidgetState extends State<MyWidget> {
bool isLoading = false;
#override
Widget build(BuildContext context) {
return Container(
height: 130,
child: Stack(
children: [
Container(
height: 130,
child: Card(
child: Row(
children: <Widget>[
Expanded(
child: ListTile(
title: Text(
'My card Location',
style: TextStyle(
fontSize: 15, fontWeight: FontWeight.w700),
),
leading: Icon(Icons.settings),
// color: Colors.blueAccent, size: mediumIconSize),
trailing: Icon(Icons.keyboard_arrow_right),
selected: true,
onTap: () async {
setState(() {
isLoading = true;
});
getDetails().then((myCardLocations) {
setState(() {
isLoading = false;
});
// navigation code here
});
},
),
),
],
),
),
),
Align(
alignment: Alignment.bottomLeft,
child: isLoading
? Padding(
padding: EdgeInsets.fromLTRB(15,0,0,15),
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(),
),
)
: SizedBox(),
),
],
),
);
}
}
Edit:
Looks like I misunderstood the question a bit. Specifically, the place where to show the progress indicator. Anyways, if you get the idea, you can put the indicator at a different place as per your requirement.
There are certain things, which I would like to mention before I give the actual answer.
Read about Flutter.delayed constructor, very useful thing to make some thing wait for a while and do the operation by providing Duration. Whatever you want to do after that duration, it will implement in the callback function
Future.delayed(Duration(seconds: your_time, (){
//it will perform this operation after that much of seconds
}));
You can always show/hide a Widget using bool value, and make changes accordingly
Use a column and Add the LinearProgressIndicator at the end of the Widget. Show/hide it based up on the data
Also, use MediaQuery to give out the height. It is more efficient way of giving the dimensions according to all phone size. Like match-parent in Android Studio. Do the math accordingly, I have shown in the code also
Column(
children: [
Row(),
bool val ? LinearProgressIndicator() : Container() // Container() is nothing but an empty widget which shows nothing
]
)
Some heads up: I have not used getData, since it is not defined properly but you can call it the in function which I will show you in the code, that is pageTransit(). Follow the comments and you are good to go
class _MyHomePageState extends State<MyHomePage> {
// this takes care of the show/hide of your progress indicator
bool _showProgress = false;
// this takes care of the operation
void pageTransit(){
// first show when the ListTile is clicked
setState(() => _showProgress = true);
Future.delayed(Duration(seconds: 2), (){
// hide it after 2 seconds
setState(() => _showProgress = false);
// do the page trnasition here
//getDetails().then((myCardlocations) {
//Navigator.of(context).pushNamed('/myCardlocations',
//arguments: ObjectLocations(locations, 'myCardlocations'));
//}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
height: MediaQuery.of(context).size.height * 0.1,
child: Card(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// use your items here, based upon the bool value show hide your
// progress indicator
Row(
children: <Widget>[
Expanded(
child: ListTile(
title: Text(
'My card Location',
style: TextStyle(
fontSize: 15, fontWeight: FontWeight.w700),
),
leading: Icon(Icons.settings),
// color: Colors.blueAccent, size: mediumIconSize),
trailing: Icon(Icons.keyboard_arrow_right),
selected: true,
onTap: () => pageTransit()
)
)
]
),
// show/hide in the card
_showProgress ? LinearProgressIndicator() : Container()
]
)
)
)
);
}
}
Result
Look at the ProgressIndicator, it remains there for 2 seconds, and then goes away
1. You need to define a GlobalKey for the Scaffold so that you can use a SnackBar (you can define the GloablKey in your page's State).
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
2. You need to set the key for the Scaffold.
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
...
3. You need to wrap the Card with a GestureDetector and set the onTap function to call showLoading which shows a SnackBar on the bottom of the screen. Call your getDetails function in the showLoading. Full code (except the define key step):
void _showLoading() {
_scaffoldKey.currentState.showSnackBar(new SnackBar(
duration: new Duration(seconds: 2),
content: new Row(
children: <Widget>[
new CircularProgressIndicator(),
new Text("Loading...")
],
),
));
// call to your getDetails and its steps should be here
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: Text("My app"),
),
body: Center(
child: GestureDetector(
child: Card(
child: Row(children: <Widget>[
Expanded(
child: ListTile(
title: Text(
'My card Location',
style: TextStyle(fontSize: 15, fontWeight: FontWeight.w700),
),
leading: Icon(Icons.settings),
// color: Colors.blueAccent, size: mediumIconSize),
trailing: Icon(Icons.keyboard_arrow_right),
selected: true,
)),
])),
onTap: () => _showLoading(),
)),
);
}
}
Note: you can also style the SnackBar.
Result:

Flutter :- How to display dynamic widgets on screen?

I want to show entered text in scrambled form. ie, each letter of the word need to display in individual Container in a row. For this, I am taking text input, storing it in List<String> and then scrambling it using shuffle() and then using List.generate to return Container with Text, as below:
List<Widget> _generateJumble(String input) {
inputList = input.split('');
var shuffleList = inputList.toList()..shuffle();
print(shuffleList);
return List<Widget>.generate(shuffleList.length, (int index) {
return Container(
width: 50,
color: Colors.blue,
child: Text(shuffleList[index].toString(),
style: TextStyle(color: Colors.white),
)
);
});
}
I am calling above method onTap of a button upon which the scrambled form of the input should be displayed. But I am not sure how to display the result of above method in UI. How should I use this method so that the returning Container based on shuffleList.length will be displayed in UI as below ?
RaisedButton(
onPressed: () {},
child: Text('Clear'),
)
],
),
),
Row(
children: <Widget>[
// ? _displayJumble()
]
)
This is my solution:
1) Press a button, scrable the string and set it to the a list
2) setState and show the list to the user
This is the widget code:
class _MyHomePageState extends State<MyHomePage> {
List<String> inputList = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Wrap(
children: inputList.map((s) {
return Container(
width: 50,
color: Colors.blue,
child: Text(
s,
style: TextStyle(color: Colors.white),
),
);
}).toList(),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
setState(() {
_generateJumble('Random string');
});
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
List<Widget> _generateJumble(String input) {
inputList = input.split('');
inputList = inputList.toList()..shuffle();
print(inputList);
}
}
I used the widget Wrap because automatically wrap the widget when there is no space available for it. You can use whatever you like to use.
This is the screen result:
Before press the button:
After press the button:
Please check the below solution of it, I have used the Wrap widget for it
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutterlearningapp/colors.dart';
class HomeScreen extends StatefulWidget {
var inputVales;
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return _HomeScreen();
}
}
class _HomeScreen extends State<HomeScreen> {
List<String> charcaterArray = new List<String>();
#override
Widget build(BuildContext context) {
// TODO: implement build
return Scaffold(
appBar: AppBar(
title: Text("Home"),
),
body: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.all(10.0),
child: TextField(
decoration: InputDecoration(labelText: 'Enter Words'),
onChanged: (text) {
setState(() {
widget.inputVales = text;
charcaterArray.clear();
for (var i = 0; i < widget.inputVales.length; i++) {
var character = widget.inputVales[i];
if (character != " ") {
charcaterArray.add(character);
}
}
});
},
),
),
Wrap(
spacing: 6.0,
runSpacing: 6.0,
children:
List<Widget>.generate(charcaterArray.length, (int index) {
return Container(
height: MediaQuery.of(context).size.height * 0.1,
width: MediaQuery.of(context).size.width * 0.1,
decoration: BoxDecoration(
color: Colors.lightGreen,
borderRadius: BorderRadius.all(Radius.elliptical(4.0, 4.0)),
),
child: Center(
child: Text(
charcaterArray[index],
style:
TextStyle(color: Colors.deepOrange, fontSize: 20.0),
),
),
);
/*Chip(
label: Text(charcaterArray[index]),
onDeleted: () {
setState(() {
charcaterArray.removeAt(index);
});
},
);*/
}),
)
],
));
}
}
And here is the output of it