Space Distribution with Expanded and Flexible

Have you ever thought, β€œI wish this box would take up all the remaining space…” when building websites? Flitter’s Expanded and Flexible widgets solve exactly that problem.

🎯 Learning Objectives

After completing this tutorial, you’ll be able to:

  • Divide remaining space equally with Expanded
  • Adjust space ratios with flex properties
  • Understand the differences between Flexible and Expanded
  • Create practical layout patterns
  • Understand how FlexFit properties work

πŸ€” Why is Space Distribution Important?

Typically, when you place widgets in a Row or Column, each widget only takes up the space it needs. However, modern UIs need to flexibly utilize space according to screen size.

Real-world use cases:

  • Navigation bar with logo on the left and menu on the right
  • Card layouts with multiple boxes distributed equally
  • Progress bars showing completed and remaining portions
  • Mobile app tab bars with equal width tabs
  • Dashboard with sidebar and main content area ratio adjustment

πŸ—οΈ Complete Mastery of Expanded Widget

What is Expanded?

Expanded is a widget that forces child widgets of Row, Column, or Flex widgets to fill all available space.

Basic Properties

Expanded({
  flex: 1,              // Space ratio (default: 1)
  child: Widget         // Child widget (required)
})

1. Basic Expanded Usage

Row({
  children: [
    Container({
      width: 100,
      height: 50,
      color: '#FF6B6B',
      child: Text("Fixed Size")
    }),
    Expanded({
      child: Container({
        height: 50,
        color: '#4ECDC4',
        child: Text("Takes All Remaining Space")
      })
    })
  ]
})

Result: The first box has a 100px width, and the second box takes up all remaining space.

2. Equal Distribution with Multiple Expanded Widgets

Row({
  children: [
    Expanded({
      child: Container({
        height: 100,
        color: '#FF6B6B',
        child: Text("1/3")
      })
    }),
    Expanded({
      child: Container({
        height: 100,
        color: '#4ECDC4',
        child: Text("1/3")
      })
    }),
    Expanded({
      child: Container({
        height: 100,
        color: '#45B7D1',
        child: Text("1/3")
      })
    })
  ]
})

Result: Three boxes equally divide the screen width, each taking 1/3.

🎨 Practice: Creating 3 Equally Divided Boxes

In the starting code above, the three boxes have fixed sizes (width: 100). Modify this to use Expanded so they divide space equally.

Step-by-step hints:

  1. Add Expanded widget to imports
  2. Wrap each Container with Expanded
  3. Remove width property from Container (Expanded handles this automatically)
  4. Keep height as is

βš–οΈ Ratio Adjustment with flex Property

You can use the flex property to determine the ratio of space each Expanded widget should occupy.

flex Ratio Calculation Principles

Row({
  children: [
    Expanded({
      flex: 1,  // 1 / (1 + 2 + 3) = 1/6
      child: Container({
        height: 100,
        color: '#FF6B6B',
        child: Text("1")
      })
    }),
    Expanded({
      flex: 2,  // 2 / (1 + 2 + 3) = 2/6 = 1/3
      child: Container({
        height: 100,
        color: '#4ECDC4',
        child: Text("2")
      })
    }),
    Expanded({
      flex: 3,  // 3 / (1 + 2 + 3) = 3/6 = 1/2
      child: Container({
        height: 100,
        color: '#45B7D1',
        child: Text("3")
      })
    })
  ]
})

Calculation process:

  • Total flex sum: 1 + 2 + 3 = 6
  • First box: 1/6 (about 16.7%)
  • Second box: 2/6 = 1/3 (about 33.3%)
  • Third box: 3/6 = 1/2 (50%)

Result: The third box is largest, and the first box is smallest.

Practical Ratio Examples

// Sidebar (1/4) and main content (3/4) layout
Row({
  children: [
    Expanded({
      flex: 1,  // 25%
      child: Container({
        color: '#F7FAFC',
        child: Text("Sidebar")
      })
    }),
    Expanded({
      flex: 3,  // 75%
      child: Container({
        color: '#EDF2F7',
        child: Text("Main Content")
      })
    })
  ]
})

πŸ”„ Advanced Understanding of Flexible Widget

What is Flexible?

Flexible provides more flexible space distribution than Expanded. It allows child widgets to only take up the space they actually need.

Flexible Properties

Flexible({
  flex: 1,                    // Space ratio (default: 1)
  fit: FlexFit.loose,         // Space usage method (default: loose)
  child: Widget               // Child widget (required)
})

FlexFit Options

  • FlexFit.loose: Use only the space needed (default)
  • FlexFit.tight: Use all allocated space (same as Expanded)

Expanded vs Flexible Comparison

FeatureExpandedFlexible
Default BehaviorTakes all allocated spaceTakes only needed space
FlexFitAlways tightloose (default) or tight
Use CaseWhen you want to completely fill spaceWhen flexible size adjustment is needed
RelationshipSpecial case of FlexibleMore general widget

Flexible Usage Example

Row({
  children: [
    Flexible({
      child: Container({
        height: 50,
        color: '#FF6B6B',
        child: Text("Short Text")  // Takes only text size space
      })
    }),
    Flexible({
      child: Container({
        height: 50,
        color: '#4ECDC4',
        child: Text("This is very long text")  // Takes text size space
      })
    }),
    Container({
      width: 100,
      height: 50,
      color: '#45B7D1',
      child: Text("Fixed Size")
    })
  ]
})

Result: The first box with short text takes small space, and the second box with long text takes more space.

πŸ† Real-world Examples Collection

1. Modern Navigation Bar

import { StatelessWidget, Container, Row, Text, Expanded, EdgeInsets, TextStyle } from "@meursyphus/flitter";

class ModernNavigationBar extends StatelessWidget {
  build(context) {
    return Container({
      height: 64,
      color: '#1A202C',
      padding: EdgeInsets.symmetric({ horizontal: 24, vertical: 12 }),
      child: Row({
        children: [
          // Logo area
          Container({
            padding: EdgeInsets.only({ right: 16 }),
            child: Text("MyApp", {
              style: new TextStyle({
                color: 'white',
                fontSize: 20,
                fontWeight: 'bold'
              })
            })
          }),
          
          // Middle empty space
          Expanded({
            child: Container()
          }),
          
          // Menu area
          Row({
            children: [
              Text("Home", { style: { color: 'white', marginRight: 24 } }),
              Text("Products", { style: { color: 'white', marginRight: 24 } }),
              Text("About", { style: { color: 'white', marginRight: 24 } }),
              Text("Contact", { style: { color: 'white' } })
            ]
          })
        ]
      })
    });
  }
}

export default function ModernNavigationBar(props) {
  return new _ModernNavigationBar(props);
}

2. Interactive Progress Indicator

import { StatefulWidget, State, Container, Column, Row, Text, Expanded, GestureDetector, EdgeInsets, BoxDecoration, BorderRadius, TextStyle, Center } from "@meursyphus/flitter";

class InteractiveProgressBar extends StatefulWidget {
  constructor({ initialProgress = 0.3, ...props } = {}) {
    super(props);
    this.initialProgress = initialProgress;
  }

  createState() {
    return new _InteractiveProgressBarState();
  }
}

class _InteractiveProgressBarState extends State {
  progress = 0.3;

  initState() {
    super.initState();
    this.progress = this.widget.initialProgress;
  }

  increaseProgress() {
    this.setState(() => {
      this.progress = Math.min(1.0, this.progress + 0.1);
    });
  }

  decreaseProgress() {
    this.setState(() => {
      this.progress = Math.max(0.0, this.progress - 0.1);
    });
  }

  build(context) {
    return Column({
      children: [
        // Progress bar
        Container({
          height: 24,
          margin: EdgeInsets.only({ bottom: 16 }),
          decoration: new BoxDecoration({
            color: '#E2E8F0',
            borderRadius: BorderRadius.circular(12)
          }),
          child: Row({
            children: [
              Expanded({
                flex: Math.round(this.progress * 100),
                child: Container({
                  decoration: new BoxDecoration({
                    color: '#4299E1',
                    borderRadius: BorderRadius.circular(12)
                  })
                })
              }),
              Expanded({
                flex: Math.round((1 - this.progress) * 100),
                child: Container()
              })
            ]
          })
        }),
        
        // Progress text
        Text(`Progress: ${Math.round(this.progress * 100)}%`, {
          style: new TextStyle({ fontSize: 16, fontWeight: 'bold' })
        }),
        
        // Control buttons
        Row({
          children: [
            Expanded({
              child: GestureDetector({
                onClick: () => this.decreaseProgress(),
                child: Container({
                  height: 40,
                  margin: EdgeInsets.only({ right: 8 }),
                  decoration: new BoxDecoration({
                    color: '#E53E3E',
                    borderRadius: BorderRadius.circular(4)
                  }),
                  child: Center({ child: Text("Decrease", { style: new TextStyle({ color: 'white' }) }) })
                })
              })
            }),
            Expanded({
              child: GestureDetector({
                onClick: () => this.increaseProgress(),
                child: Container({
                  height: 40,
                  margin: EdgeInsets.only({ left: 8 }),
                  decoration: new BoxDecoration({
                    color: '#38A169',
                    borderRadius: BorderRadius.circular(4)
                  }),
                  child: Center({ child: Text("Increase", { style: new TextStyle({ color: 'white' }) }) })
                })
              })
            })
          ]
        })
      ]
    });
  }
}

export default function InteractiveProgressBar(props) {
  return new _InteractiveProgressBar(props);
}

3. Responsive Card Grid

import { StatelessWidget, Container, Row, Text, Column, Expanded, EdgeInsets, BoxDecoration, BorderRadius, BoxShadow, TextStyle, CrossAxisAlignment } from "@meursyphus/flitter";

class ResponsiveCardGrid extends StatelessWidget {
  constructor({ cards = [], ...props } = {}) {
    super(props);
    this.cards = cards;
  }

  build(context) {
    return Row({
      children: this.cards.map((card, index) => (
        Expanded({
          child: Container({
            height: 120,
            margin: EdgeInsets.only({ 
              left: index > 0 ? 8 : 0,
              right: index < this.cards.length - 1 ? 8 : 0
            }),
            decoration: new BoxDecoration({
              color: card.color,
              borderRadius: BorderRadius.circular(8),
              boxShadow: [new BoxShadow({
                color: 'rgba(0, 0, 0, 0.1)',
                offset: { dx: 0, dy: 2 },
                blurRadius: 4
              })]
            }),
            padding: EdgeInsets.all(16),
            child: Column({
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(card.title, {
                  style: new TextStyle({
                    fontSize: 16,
                    fontWeight: 'bold',
                    color: 'white',
                    marginBottom: 8
                  }
                }),
                Text(card.description, {
                  style: new TextStyle({
                    fontSize: 14,
                    color: 'rgba(255, 255, 255, 0.8)'
                  })
                })
              ]
            })
          })
        })
      ))
    });
  }
}

export default function ResponsiveCardGrid(props) {
  return new _ResponsiveCardGrid(props);
}

// Usage example
const cardData = [
  {
    title: "Sales",
    description: "This month's sales status",
    color: '#4299E1'
  },
  {
    title: "Orders",
    description: "New order processing",
    color: '#48BB78'
  },
  {
    title: "Customers",
    description: "Customer satisfaction survey",
    color: '#ED8936'
  }
];

const widget = Container({
  padding: EdgeInsets.all(16),
  child: ResponsiveCardGrid({ cards: cardData })
});

πŸš€ Advanced Usage Patterns

1. Nested Expanded Usage

Column({
  children: [
    Container({
      height: 60,
      color: '#2D3748',
      child: Text("Header")
    }),
    Expanded({
      child: Row({
        children: [
          Container({
            width: 200,
            color: '#4A5568',
            child: Text("Sidebar")
          }),
          Expanded({
            child: Container({
              color: '#F7FAFC',
              child: Text("Main Content")
            })
          })
        ]
      })
    }),
    Container({
      height: 40,
      color: '#718096',
      child: Text("Footer")
    })
  ]
})

2. Spacer Widget Usage

Row({
  children: [
    Text("Left Content"),
    Spacer(),  // Same as Expanded({ child: Container() })
    Text("Right Content")
  ]
})

// Equal distribution with multiple Spacers
Row({
  children: [
    Text("A"),
    Spacer(),
    Text("B"),
    Spacer(),
    Text("C")
  ]
})

πŸ“ Practice Problems

Practice 1: Creating Tab Bar

Create a tab bar with 4 tabs distributed equally.

// TODO: Create a tab bar with 4 equally distributed tabs
const tabNames = ["Home", "Search", "Notifications", "Profile"];

// Hint: Use Row and Expanded
const tabBar = Row({
  children: [
    // Write your code here
  ]
});

Practice 2: Dashboard Layout

Create a 3-column layout with left sidebar (1/4), main content (1/2), right panel (1/4).

// TODO: Create a 1:2:1 ratio 3-column layout
const dashboardLayout = Row({
  children: [
    // Write your code here
  ]
});

Practice 3: Dynamic Progress Bar

Create a progress bar that increases by 20% each time a button is clicked.

// TODO: Create a dynamic progress bar using StatefulWidget
class DynamicProgressBar extends StatefulWidget {
  createState() {
    return new _DynamicProgressBarState();
  }
}

class _DynamicProgressBarState extends State {
  // Write your code here
}

πŸ› Common Mistakes and Solutions

❌ Mistake 1: Using Expanded Outside Row/Column

// Error!
Container({
  child: Expanded({
    child: Text("Hello")
  })
})

βœ… Correct Method:

// Expanded must be used inside Flex widgets (Row, Column, Flex)
Row({
  children: [
    Expanded({
      child: Text("Hello")
    })
  ]
})

❌ Mistake 2: Setting Infinite Size Inside Expanded

// Error!
Row({
  children: [
    Expanded({
      child: Container({ width: double.infinity })
    })
  ]
})

βœ… Correct Method:

// Expanded already provides maximum size, no need to specify width
Row({
  children: [
    Expanded({
      child: Container()  // width is automatically determined
    })
  ]
})

❌ Mistake 3: Trying Infinite Height in Column

// Error!
Column({
  children: [
    Container({ height: double.infinity })
  ]
})

βœ… Correct Method:

// Use Expanded to take all vertical space in Column
Column({
  children: [
    Expanded({
      child: Container()
    })
  ]
})

❌ Mistake 4: Setting flex Value to 0

// Wrong usage
Expanded({
  flex: 0,  // 0 is meaningless
  child: Container()
})

βœ… Correct Method:

// Use flex value of 1 or higher positive number
Expanded({
  flex: 1,  // Use default value or set appropriate ratio
  child: Container()
})

🎊 Completed Results

After completing this tutorial:

  1. Basic Implementation: 3 boxes equally divide screen width
  2. Responsive Behavior: Ratios maintained even when window size changes
  3. Visual Distinction: Each box distinguished by different colors
  4. Ratio Control: Adjustable to desired ratios with flex properties

πŸ”₯ Additional Challenges

1. Creating Responsive Grid System

Create a grid system where the number of columns changes based on screen size.

2. Animated Progress Bar

Create an animated progress bar where progress automatically increases over time.

3. Complex Dashboard Layout

Create a complete dashboard including header, sidebar, main content, right panel, and footer.

4. Adding Touch Interactions

Create an interactive layout where you can adjust the size of each area by dragging.

🎯 Next Steps

In the next tutorial, we’ll learn about Padding and Margin Management. Master the core techniques for creating visually clean and professional layouts!