Flutter - Rows with Icons like a Bow - flutter

in my form I have a Container with some Rows and nested Icons. Every Icon has got a Tap event with a function.
How can I curve all Rows with Icons like this.
to obtain this result as a bow (like rainbow)?

I think there is no shortcut to do this if you want a smooth curve. You must provide some constrains that what the ratio of width and height and calculate the curve formulation. You can try to combine Stack & Align to fullfill your layout.
Here is my assumption from your image:
X axis is separated equally.
Y axis is like a circular sector which radian from 1.25 pi to 1.75 pi (a circle is 2 pi). So the total radian is pi
import 'dart:math';
class CustomRainbow extends StatelessWidget {
#override
Widget build(BuildContext context) {
List<Widget> icons = List<Icon>.filled(14, Icon(Icons.tag_faces));
return Scaffold(
backgroundColor: Colors.black87,
body: Center(
child: Container(
width: 300,
height: 130,
color: Colors.white,
child: Stack(
children: [
..._bridge(icons),
..._bridge(icons, row: 1),
..._bridge(icons, row: 2),
..._bridge(icons, row: 3),
],
),
),
),
);
}
List<Widget> _bridge(List<Widget> icons, {int row = 0}) {
double totalRad = pi;
double rowHeightFactor = 0.4;
int count = icons.length;
double width = 2.0 / (count - 1);
double singleRad = totalRad / (count + 1);
double startRad = pi * 1.5 - totalRad * 0.5;
List<Widget> result = [];
for (int i = 0; i < count; i++) {
result.add(
Align(
alignment: Alignment(
-1 + i * width, sin((startRad + singleRad * (i + 1)))) +
Alignment(0, row * rowHeightFactor),
child: icons[i],
),
);
}
return result;
}
}
You can still tune the Width, Height, totalRad and rowHeightFactor,
Make sure the Alignment value is in range of (-1.0, 1.0) and all the icons will be in the Container.

A hack could be to display row(column) and to add a transparent (default) container with the respective height for each column as the first element, to move the subsequent icons downwards.

One simple way of doing that is to build Columns inside the Row with placeholders that will ocuppy some bottom space to make taller columns:
static const double size = 40.0;
static const placeholderSize = size / 2;
static const int columnsNumber = 8;
Widget repeatedWidget() {
return IconButton(
icon: Icon( //Your icon button
Icons.tag_faces,
size: size,
),
onPressed: () {},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate( //generating a fixed number of columns with icons for the example
columnsNumber,
(index) {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: List.generate(
6,
(index) => repeatedWidget(),
)..add( //adding a widget at the final of each Column
Column(
children: List.generate(
/*if the current index is greater or equal to half of the number
of columns return it minus index - 1, else return the index*/
index >= columnsNumber / 2
? columnsNumber - index - 1
: index,
(index) => Container( //containers that will ocuppy the bottom space of each column
height: placeholderSize,
),
),
),
),
);
},
),
),
);
}
The result:

Related

Generate widgets in-between texts in flutter

I am trying to generate a list of pages numbers after every 300 words but can't. please does anybody know how I can implement this?.
class Try extends StatelessWidget {
const Try({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final String text4 = lorem(paragraphs: 8, words: 2000);
return Scaffold(
body: SingleChildScrollView(
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(9),
child: Column(
children: [
Text(text4),
],
),
),
),
),
);
}
}
I tried adding the below code to it but...
for (int i = 0; i < text4.length;300 * i++)
//if (text4.length == 300)
Padding(
padding: const EdgeInsets.all(14),
child: Column(
children: [
const Text('page ${a += 1}'),
Divider(
color: Theme.of(context).primaryColor,
)
],
),
),
Text(text4),
You can use substring to extract text and page number logic will be i / 300 + 1. It will provide 300 letters. For word case you need to convert text to list by splitting on space.
final wordList = text4.split(" ").toList();
String text4 =
"I am trying to generate a list of pages numbers after every 300 words but can't. please does anybody know how I can implement this?.I am trying to generate a list of pages numbers after every 300 words but can't. please does anybody know how I can implement this?.I am trying to generate a list of pages numbers after every 300 words but can't. please does anybody know how I can implement this?.I am trying to generate a list of pages numbers after every 300 words but can't. please does anybody know how I can implement this?.I am trying to generate a list of pages numbers after every 300 words but can't. please does anybody know how I can implement this?.I am trying to generate a list of pages numbers after every 300 words but can't. please does anybody know how I can implement this?.";
late final wordList = text4.split(" ").toList();
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
// for (int i = 0; i < text4.length; i += 300)
for (int i = 0; i < wordList.length; i += 300)
Padding(
padding: const EdgeInsets.all(14),
child: Column(
children: [
Text('page ${i / 300 + 1}'),
Divider(
color: Theme.of(context).primaryColor,
),
/// for letters
// Text(text4.substring(
// i,
// i + 300 > text4.length ? text4.length : i + 300,
// )),
() {
final textX = wordList
.sublist(
i,
i + 300 > wordList.length
? wordList.length
: i + 300)
.toString();
return Text(textX.substring(1, textX.length - 1));
}()
],
),
),
],
),
),
);
}
[] depends on original text, for substring it is handled Text(textX.substring(1, textX.length - 1));.

RangeError (index): Invalid value: Valid value range is empty: 1

after whole day of trying to solve this myself I had to come and ask for help.
I'm trying to build this ListView.builder, it has fixed amount of itemCount. And its building Widgets using data retrieved from locally stored JSON file.
I'm using Provider to pass that data around. The problem is, on app start or hot restart that ListView.builder turns red and shows error, and then after like quarter of a second it shows my data.
I understand why this happens, my list of data that I get from json is initially empty. So I put ternary operator like: provider.data == null ? CircularProgressIndicator() : ListView.builder... but this doesnt stop it from crashing.
I dont know why and its driving me crazy. Here is full code:
We are talking here about widget called RecommendedCardList, its showing widgets from above mentioned list by having random number (in range of list length) as index.
I have similar ListView on HomeScreen called CategoryCardList and its working similarly to RecommendedCardList but I'm not having this issue with it. Also the rest of the home screen shows good, only the portion where RecommendedCardList is turns red for a short period of time.
Home Screen class:
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Get user's screen properties
// We are using this properties to adjust size accordingly
// In order to achieve maximum responsivnes for different screen sizes
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
var repoProvider = Provider.of<Repository>(context);
var recipeDataList = repoProvider.recipeDataList;
return Container(
color: backgroundColor,
child: repoProvider.recipeDataList == null
? Center(child: CircularProgressIndicator())
: Padding(
padding: contentPadding,
child: ListView(
children: <Widget>[
AppTitle(),
SizedBox(
height: height * 0.03,
),
Column(
children: <Widget>[
CategoryAndSeeAll(),
CategoryCardsList(height: height, provider: repoProvider),
],
),
SizedBox(
height: height * 0.05,
),
Container(
width: double.infinity,
height: height * 0.1,
decoration: BoxDecoration(
border: Border.all(color: accentColor),
),
child: Text(
'Reserved for AD',
textAlign: TextAlign.center,
),
),
SizedBox(
height: height * 0.05,
),
RecommendedCardsList(height: height, width: width, recipeDataList: recipeDataList),
],
),
),
);
}
}
RecommendedCardsList class:
class RecommendedCardsList extends StatelessWidget {
const RecommendedCardsList({
Key key,
#required this.height,
#required this.width,
#required this.recipeDataList,
}) : super(key: key);
final double height;
final double width;
final recipeDataList;
#override
Widget build(BuildContext context) {
return Container(
height: height * 0.30,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: numberOfRecommendedRecipes,
itemBuilder: (context, counter) {
int randomNumber = Random().nextInt(recipeDataList.length);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
RecommendedCard(
width: width,
height: height,
imagePath: recipeDataList.elementAt(randomNumber).image,
text: recipeDataList.elementAt(randomNumber).title,
),
],
);
}),
);
}
}
Repository class:
class Repository extends ChangeNotifier {
Repository() {
loadJson();
}
var _recipeData;
List<RecipeModel> _recipeDataList = [];
List<RecipeModel> get recipeDataList => _recipeDataList;
void loadJson() async {
var json = await rootBundle.loadString('assets/recipes.json');
var parsedJson = jsonDecode(json);
for (var item in parsedJson) {
_recipeData = RecipeModel.fromJson(item);
_recipeDataList.add(_recipeData);
}
//print('Title:${_recipeDataList[0].title}\nImage:${_recipeDataList[0].image}'); // For debugging
notifyListeners();
}
}
This error is related to the fact that the code searched for an index in your list and this index is more than you list length.
I think the error is in that part:
int randomNumber = Random().nextInt(recipeDataList.length);
Supposing the length is 10 the random function will retrieve a num between 0 and 10, but the last index is 9.
With that in mind, I have two suggestions:
1)
// changing ternary logic
(repoProvider.recipeDataList == null && repoProvider.recipeDataList.length > 0)
2)
// inside ListView.Builder change to get the list length
itemCount: recipeDataList.length
Put the following condition in build() of RecommendedCardsList widget as the first line.
if(recipeDataList == null || recipeDataList.length == 0){
return Container();
}

Flutter : Change size of stack when children overflow

I have a draggable widget (from https://medium.com/flutter-community/create-a-draggable-widget-in-flutter-50b61f12635d ) with a stack in a container (red color) consists of moveable children. Here is the widget tree:
I wanted to add a Gesture Transformations as FormBuilder ( https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/transformations/transformations_demo.dart ) to transform the matrix, as you can see in the GIF, mainly zoom in/out and transform x/y.
class _HomeViewState extends State<HomeView> {
final _stackKey;
_HomeViewState(this._stackKey);
List<Widget> movableItems = [];
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text('SynApp'),
actions: <Widget>[
IconButton(
onPressed: () {
x = 200.0;
y = 200.0;
setState(() {
movableItems.add(
MoveableStackItem(_stackKey),
);
});
},
icon: Icon(Icons.add),
),
],
),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// Draw the scene as big as is available, but allow the user to
// translate beyond that to a visibleSize that's a bit bigger.
final Size size = Size(constraints.maxWidth, constraints.maxHeight);
final Size visibleSize = Size(size.width * 3, size.height * 2);
return GestureTransformable(
reset: _reset,
onResetEnd: () {
setState(() {
_reset = false;
});
},
child: new Container(
color: Colors.red,
child: Stack(
key: _stackKey,
overflow: Overflow.visible,
fit: StackFit.expand,
children: movableItems),
),
boundaryRect: Rect.fromLTWH(
-visibleSize.width / 2,
-visibleSize.height / 2,
visibleSize.width,
visibleSize.height,
),
initialTranslation: Offset(size.width, size.height),
size: size,
);
}),
);
}
}
The problem is:
a) the size of the stack is equal to the initial screen.
b)when I move items out of the screen, gestureDetection stops, the items are no longer moveable.
What I want:
I want to dynamically resize the size of the stack (the red box) depending on where I move the items.
I was able to find the position and size of the stack widget.
Size stackSize;
Offset stackPosition;
_MoveableStackItemState(this._stackKey);
getSizeAndPosition() {
RenderStack _stackStack = _stackKey.currentContext.findRenderObject();
stackSize = _stackStack.size;
stackPosition = _stackStack.localToGlobal(Offset.zero);
print('stackSize $stackSize');
print('stackPosition $stackPosition');
}
But I'm starting to get lost in advanced UI object orientated stateful widget manipulation.
You can wrap the red stack with an AnimatedContainer.
LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
// Draw the scene as big as is available, but allow the user to
// translate beyond that to a visibleSize that's a bit bigger.
final Size size = Size(constraints.maxWidth, constraints.maxHeight);
final Size visibleSize = Size(size.width * 3, size.height * 2);
return GestureTransformable(
reset: _reset,
onResetEnd: () {
setState(() {
_reset = false;
});
},
child: new AnimatedContainer(
color: Colors.red,
duration: Duration(milliseconds: 200),
width: _stackWidth,
height: _stackHeight,
child: Stack(
key: _stackKey,
overflow: Overflow.visible,
fit: StackFit.expand,
children: movableItems),
),
boundaryRect: Rect.fromLTWH(
-visibleSize.width / 2,
-visibleSize.height / 2,
visibleSize.width,
visibleSize.height,
),
initialTranslation: Offset(size.width, size.height),
size: size,
);
}),
Try to listen the following event of the GestureTransformable
onPanUpdate: (DragUpdateDetails details){
var deltaX = details.delta.dx;
var deltaY = details.delta.dy;
}
DragUpdateDetails object let you to know the delta
the amount the pointer has moved in the coordinate space of the event
receiver since the previous update
on the x and y axis.
Inside the "onPanUpdate" you can update the width and the height of the animated container related to the delta of the gesture.
setState((){
_stackHeight = /* ... */
_stackWidth = /* ... */
});

Stack Alignment for an indeterminate number of children

I am building an app in flutter and part of what I am doing is fetching a list of actions from a server then building buttons for each action. I am trying to display the buttons in a circle and generate a list of raised button widgets. I am then trying to use a Stack to lay out this list in a circle, but cannot figure out how to use positioning to position objects in a circle when I do not know how many objects I will have (I know it will be in a range of about 3-15).
Here is the build method for my stack. Assume all methods and lists and variables are defined correctly and work. I am just concerned about the alignment
#override
Widget build(BuildContext context) {
List<Widget> actions = [];
for(var action in widget.actionsList){
actions.add(RaisedButton(
onPressed: () => _handleAction(action[0]),
shape: CircleBorder(),
child: Text(action[1] + ': ' + action[2]),
));
}
return Scaffold(
body: Center(
child: Stack(
children: actions,
alignment: //HELP,
),
),
);
}
If you have any ideas about how to do the alignment or another way to go about this to make a circle of buttons please let me know. I really want to make a circle, but am not wedded to it impossible (which I doubt) or super convoluted.
Thanks!
Here's how I would do it.
TL;DR;
final actionsList = [
"one",
"two",
"three",
"four",
"five",
];
#override
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => Stack(
children: layedOutButtons(
centerOfCircle:
Offset(constraints.maxWidth, constraints.maxHeight) / 2,
circleRadius: 100,
),
),
),
);
}
Offset getCoordinateFromAngle({double radius, double angle}) => Offset(
radius * sin(angle),
radius * cos(angle),
);
List<Widget> layedOutButtons({
Offset centerOfCircle,
double circleRadius,
}) {
var buttonRadius = 25.0;
var dispatchAngle = pi * 2 / actionsList.length;
List<Widget> widgets = [];
var i = 0;
for (var action in actionsList) {
var position = getCoordinateFromAngle(
radius: circleRadius,
angle: dispatchAngle * i++,
);
widgets.add(
Positioned(
top: centerOfCircle.dy - position.dy - buttonRadius,
left: centerOfCircle.dx + position.dx - buttonRadius,
width: buttonRadius * 2,
height: buttonRadius * 2,
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),
);
}
return widgets;
}
Explanation
If you want to dispatch a number of widgets on a circle, you have to:
define centerOfCircle, the position of the center of that circle. To do so, I use the LayoutBuilder widget to get the constraints of the layout and determine the center of the Stack => (width / 2, height / 2).
define circleRadius, the radius of that circle (in my example: 100)
Give those data to layedOutButtons that will dispatch widgets on the circle with the Positioned widget.
Widget build(BuildContext context) {
return Scaffold(
body: LayoutBuilder(
builder: (context, constraints) => Stack(
children: layedOutButtons(
centerOfCircle: Offset(constraints.maxWidth, constraints.maxHeight) / 2,
circleRadius: 100,
),
),
),
);
}
List<Widget> layedOutButtons({
Offset centerOfCircle,
double circleRadius,
}) {
List<Widget> widgets = [];
for (var action in actionsList) {
widgets.add(
Positioned(
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),
);
}
return widgets;
}
Now, you need to define how you will dispatch the widgets on the circle, based on their number. e.g, it there's 2 widgets, dispatch them at 180° from each other (meaning one on the top of the circle, one on the bottom. If there's 4, dispatch at 90° from each other, etc. Note that it is expressed in radians (not in degrees).
var dispatchAngle = pi * 2 / actionsList.length;
Then you have to define the coordinates (x, y) of a point on a circle based on an angle (look at this).
Offset getCoordinateFromAngle({double radius, double angle}) => Offset(
radius * sin(angle),
radius * cos(angle),
);
Use that to fill the top and left attributes of the Positioned widget.
List<Widget> layedOutButtons({
Offset centerOfCircle,
double circleRadius,
}) {
var dispatchAngle = pi * 2 / actionsList.length;
List<Widget> widgets = [];
var i = 0;
for (var action in actionsList) {
var position = getCoordinateFromAngle(
radius: circleRadius,
angle: dispatchAngle * i++, //increment angle for each widget
);
widgets.add(
Positioned(
top: centerOfCircle.dy - position.dy, //something's wrong here
left: centerOfCircle.dx + position.dx, //something's wrong here
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),
);
}
return widgets;
}
Now everything is almost ready except that there's a misalignement. It's due to the fact that the widgets are positioned based on their top left corners. We want to refine the positioning so it match the center of our widgets.
var buttonRadius = 25.0;
Positioned(
top: centerOfCircle.dy - position.dy - buttonRadius,
left: centerOfCircle.dx + position.dx - buttonRadius,
width: buttonRadius * 2,
height: buttonRadius * 2,
child: FloatingActionButton(
child: Text(action),
onPressed: () {},
),
),

StackedBar widget in Flutter

I'm trying to create my own horizontal stacked bar widget in Flutter and I have some difficulties. This is repro of the code I have:
class StackedBar extends StatefulWidget {
HashMap<int, double> factors = HashMap();
StackedBar(){
factors[0] = 0.1;
factors[1] = 0.3;
factors[2] = 0.5;
factors[3] = 0.1;
}
#override
_StackedBarState createState() => new _StackedBarState();
}
class _StackedBarState extends State<StackedBar> {
#override
Widget build(BuildContext context) {
List<Widget> widgets = [];
for (int i =0; i < widget.factors.length; i++) {
var w = Flexible(
child: FractionallySizedBox(
widthFactor: widget.factors[i],
child: Container(
height: 50,
color: ColorHelper.getColor(i))));
widgets.add(w);
}
return Container(
color: Colors.white,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: widgets));
}
}
The result looks like this:
So as you can see the bar is not covering entire row. I think the problem is that FractionallySizedBox is not calculating it's fraction from rows width but rather from the space that widget will have available for him inside the row (in my case probably 1/4 of the row's width). Am I correct? If so what would be the correct solution?
Use Expanded instead of Flexible. Change your code like this:
final HashMap<int, double> factors = HashMap();
StackedBar(){
factors[0] = 1;
factors[1] = 3;
factors[2] = 5;
factors[3] = 1;
}
and inside your build method :
for (int i =0; i < widget.factors.length; i++) {
var w = Expanded(
flex: widget.factors[i],
child: Container(
height: 50,
color: Colors.accents[i]));
widgets.add(w);
}