StackedBar widget in Flutter - 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);
}

Related

Flutter Drag and Drop

I'm facing a problem implementing drag and drop in my project. I want dynamically add and delete draggable elements. The problem is that I can't understand how to get the reference to the widget I'm currently moving so that I can understand which coordinates in the list I have to change.
Here is an example of the code where I use the static number of draggable widgets. They are assigned with coordinates from the list. But what if I have to dynamically add and delete those draggable widgets how can I understand which coordinates to change?
So, I have an array of draggble elements and array of coordinates. Widget with index 0 refers to coordinates with index 0. How can I understand which widget I'm currently moving so that I can get the index of it in array of widgets and then change proper coordinates in array of coordinates.
class DragAndDrop extends StatefulWidget {
const DragAndDrop({
Key? key,
this.width,
this.height,
}) : super(key: key);
final double? width;
final double? height;
#override
_DragAndDropState createState() => _DragAndDropState();
}
class _DragAndDropState extends State<DragAndDrop> {
List<double?> _x = [0.0, 20.0];
List<double?> _y = [0.0, 20.0];
int k = -1;
List<Widget> pel = [];
final GlobalKey stackKey = GlobalKey();
#override
Widget build(BuildContext context) {
pel.add(Container(color: Colors.blue));
k++;
Widget drag = Draggable<int>(
data: k,
child: Icon(
Icons.keyboard_arrow_down,
color: Color(0x95000000),
size: 40,
),
feedback: Icon(
Icons.keyboard_arrow_down,
color: Color.fromRGBO(212, 14, 14, 0.584),
size: 40,
),
childWhenDragging: Container(),
onDragStarted: () {},
onDragEnd: (dragDetails) {
setState(() {
final parentPos = stackKey.globalPaintBounds;
if (parentPos == null) return;
if (dragDetails.offset.dx - parentPos.left < 0)
_x[0] = 0;
else if (dragDetails.offset.dx - parentPos.left >
double.parse(widget.width.toString()) - 40)
_x[0] = double.parse(widget.width.toString()) - 40;
else
_x[0] = dragDetails.offset.dx - parentPos.left;
if (dragDetails.offset.dy - parentPos.top < 0)
_y[0] = 0;
else if (dragDetails.offset.dy - parentPos.top >
double.parse(widget.height.toString()) - 40)
_y[0] = double.parse(widget.height.toString()) - 40;
else
_y[0] = dragDetails.offset.dy - parentPos.top;
});
},
);
pel.add(Positioned(
left: _x[0],
top: _y[0],
child: drag,
));
pel.add(Positioned(
left: _x[1],
top: _y[1],
child: Draggable<int>(
data: k,
child: Icon(
Icons.keyboard_arrow_down,
color: Color(0x95000000),
size: 40,
),
feedback: Icon(
Icons.keyboard_arrow_down,
color: Color.fromRGBO(212, 14, 14, 0.584),
size: 40,
),
childWhenDragging: Container(),
onDragStarted: () {},
onDragEnd: (dragDetails) {
setState(() {
final parentPos = stackKey.globalPaintBounds;
if (parentPos == null) return;
_x[1] = dragDetails.offset.dx - parentPos.left; // 11.
_y[1] = dragDetails.offset.dy - parentPos.top;
});
},
),
));
return Stack(
key: stackKey,
fit: StackFit.expand,
children: pel,
);
}
}
extension GlobalKeyExtension on GlobalKey {
Rect? get globalPaintBounds {
final renderObject = currentContext?.findRenderObject();
var translation = renderObject?.getTransformTo(null).getTranslation();
if (translation != null && renderObject?.paintBounds != null) {
return renderObject!.paintBounds
.shift(Offset(translation.x, translation.y));
} else {
return null;
}
}
}
I tried to use a variable that I can assign to Dragble.data field but I'm not able to get it inside the widget.

Create Dynamically Sized Squares

I'm attempting to create a GitHub style heat map inside a Card and am struggling with the UI. The challenge is making the heat map dynamically expand to fit the Card it sits in based on the device's screen size.
Here is an example screenshot.
The code to create the screenshot is below.
Essentially the code,
creates a column that starts with two lines of text
then inserts a Row of Columns that consist of squares
I'm not sure if I should focus on making the individual boxes expand, the columns that the individual boxes sit in, or both. All my experiments end in unbound errors. I'm not sure where/how to add the constraints.
I also assume I'll need the boxes to be wrapped in AspectRatio() to keep the 1:1 ratio and be a square.
(I've removed some of the the more verbose business logic in my actual code for simplicity.)
class ProfileView extends StatelessWidget {
const ProfileView({Key? key}) : super(key: key);
List<Widget> _heatMapColumnList() {
final _columns = <Widget>[];
final _startDate = DateTime.now().subtract(const Duration(days: 365));
final _endDate = DateTime.now();
final _dateDifference = _endDate.difference(_startDate).inDays;
for (var index = 0 - (_startDate.weekday % 7);
index <= _endDate.difference(_startDate).inDays;
index += 7) {
//helper to change date by index
final _firstDay = DateUtility.changeDay(_startDate, index);
_columns.add(
HeatMapColumn(
startDate: _firstDay,
endDate: index <= _dateDifference - 7
? DateUtility.changeDay(_startDate, index + 6)
: _endDate,
numDays: min(_endDate.difference(_firstDay).inDays + 1, 7),
),
);
}
return _columns;
}
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Card(
elevation: 1,
child: Padding(
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Text('Some Title Text'),
const Text('More SubTitle Text'),
const SizedBox(height: 10),
Row(
children: <Widget>[
..._heatMapColumnList(),
],
...
...
class HeatMapColumn extends StatelessWidget {
HeatMapColumn({
super.key,
required this.startDate,
required this.endDate,
required this.numDays,
}) : dayContainers = List.generate(
numDays,
(i) => HeatMapBox(
date: DateUtility.changeDay(startDate, 1),
),
),
emptySpace = (numDays != 7)
? List.generate(
7 - numDays,
(i) => const HeatMapBox(
date: null,
),
)
: [];
final List<Widget> dayContainers;
final List<Widget> emptySpace;
final DateTime startDate;
final DateTime endDate;
final int numDays;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Column(
children: <Widget>[
...dayContainers,
...emptySpace,
],
...
// !!!THIS IS THE BOX I WANT TO DYNAMICALLY RESIZE!!!
class HeatMapBox extends StatelessWidget {
const HeatMapBox({
required this.date,
this.color,
super.key,
});
final DateTime? date;
final Color? color;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(1),
child: SizedBox(
child: Container(
// ???HOW DO I AVOID THIS EXPLICIT NUMERIC CONTAINER SIZE???
height: 3,
width: 3,
decoration: const BoxDecoration(
color: Colors.black12,
),
),
),
);
}
}
I would add a comment but I do not have enough reputation so sorry if this is not the answer you are looking for
You could use something like this
double width = MediaQuery.of(context).size.width; // gives width of device screen
double height = MediaQuery.of(context).size.height; // gives height of device screen
// if the card has padding
double cardLeftPadding = a double;
double cardRightPadding = a double;
width -= (cardLeftPadding + cardRightPadding);
Container(
// ???HOW DO I AVOID THIS EXPLICIT NUMERIC CONTAINER SIZE???
height: 3,
width: width,
decoration: const BoxDecoration(
color: Colors.black12,
),),
I believe something like this will allow you to fit your heat map to the full length of your card

Flutter - Rows with Icons like a Bow

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:

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();
}

How to use the sizes of other widgets before the widget tree is drawn ? [with Example]

This Shows all 3 cases discussed in this other post with a simple example.
How to use the constraints and sizes of other widgets during the build phase
NOTE:
I know that this is weird
I know that if used improperly this could create layouts that are never drawn
Just Reading Constraints is not enough because sometimes the constraints don't exist (like in this particular case)
GOAL:
Get what is drawn on screen to stabilize after 0 ReBuilds (or 1 build) instead of 2 ReBuilds
CURRENT PROCESS:
Build 1
Build 2
Build 3
when ("automaticReBuilding " == true) => the system automatically rebuilds itself depending on the quantity of dependancies (this is determined by you)
[the fact that automatic rebuilding runs the build function multiple times is what creates the stutter problem I have referred to now and in previous posts]
when ("automaticReBuilding" == false) => the system waits for you to rebuild things manually
//--------------------------------------------------CODE START
import 'package:flutter/material.dart';
import 'dart:async';
//Desired Behavior on FIRST build (It should not take 3)
//CASE 1 (parent uses child size) : eye.width = vane.width * 10
//CASE 2 (child uses parent size) : pupil.width = iris.width / 2
//CASE 3: (child uses sibling size) : iris.width = vane.width * 5
//Desired Sizes (can be read from Render Tree in Flutter Inspector) [in original config of 4 letters]
//vane = 30
//pupil = 75
//iris = 150
//eye = 300
//NOTE: that vane width (aka size) is not determined until we see what is inside of it
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new StateFull();
}
}
class StateFull extends StatefulWidget {
#override
_StateFullState createState() => new _StateFullState();
}
var vaneKey = new GlobalKey();
var vaneWidth;
var irisKey = new GlobalKey();
var irisWidth;
class _StateFullState extends State<StateFull> {
//NOTE: change this to either run the rebuild in one shot or slowly see the progression
bool automaticReBuilding = false;
//NOTE: this starts here because the first build method isn't technically a rebuild
int timesReBuilt = -1;
//NOTE: this is set MANUALLY given the dependencies between your widgets
//In this particular case C relies on B which relies on A
//so (first) I get the size of A, (second) I use the size of A to get B, (third) i use the size of B to get C
//which comes down to 3 rebuilds
int requiredBuildsPerChange = 3;
int timesBuilt = 0;
rebuild(){
setState(() {
});
}
rebuildAsync() async{
await Future.delayed(Duration.zero);
setState(() {
});
}
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
timesReBuilt++;
if(automaticReBuilding){
timesBuilt++;
print("build #" + timesBuilt.toString());
if(timesBuilt < requiredBuildsPerChange)
rebuildAsync();
else
timesBuilt = 0;
}
var complexWidget = complexRelationshipWidget();
return new MaterialApp(
title: '3 Cases Test',
home: new Scaffold(
backgroundColor: Colors.brown,
body: new Stack(
children: <Widget>[
new Align(
alignment: Alignment.center,
child: complexWidget,
),
new Container(
padding: EdgeInsets.all(16.0),
alignment: Alignment.bottomRight,
child: new RaisedButton(
onPressed: () => (automaticReBuilding == false) ? rebuild() : null,
child: new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(Icons.update),
new Text("Manual ReBuilds\nOR\nAutomatic Frame Stutter\n$timesReBuilt", textAlign: TextAlign.center,),
],
),
),
),
],
)
),
);
}
Container complexRelationshipWidget() {
vaneWidth = vaneKey?.currentContext?.findRenderObject()?.semanticBounds?.size?.width;
irisWidth = irisKey?.currentContext?.findRenderObject()?.semanticBounds?.size?.width;
return new Container( //-----EYE-----
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.white),
width: vaneWidth == null ? null : vaneWidth * 10,
alignment: Alignment.center,
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Container( //-----VANE-----
key: vaneKey,
color: Colors.red,
child: new Text("vane"),
),
new Container( //-----IRIS-----
key: irisKey,
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.blue),
width: vaneWidth == null ? null : vaneWidth * 5,
alignment: Alignment.center,
child: new Container( //-----PUPIL
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.black),
width: irisWidth == null ? null : irisWidth / 2,
),
),
],
)
);
}
}
//--------------------------------------------------CODE END