Home > Net >  Create Dynamically Sized Squares
Create Dynamically Sized Squares

Time:06-17

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.

enter image description here

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

CodePudding user response:

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

  • Related