Single Child Widgets are those that can contain only one child. These widgets are used to modify or control the behavior of a single widget
Container(
padding: EdgeInsets.all(16.0),
margin: EdgeInsets.all(8.0),
color: Colors.blue,
child: Text('Single Child Widget'),
);Padding(
padding: EdgeInsets.all(16.0),
child: Text('Padded Text'),
);Align(
alignment: Alignment.center,
child: Text('Centered Text'),
);Center(
child: Text('Centered Text'),
);SizedBox(
width: 100,
height: 100,
child: Text('Sized Box'),
);Expanded(
child: Text('Expanded Text'),
);Multiple Child Widgets can contain more than one child. These widgets are often used to create complex layouts by arranging multiple widgets in a specific order or alignment.
Column(
children: <Widget>[
Text('First'),
Text('Second'),
Text('Third'),
],
);Row(
children: <Widget>[
Icon(Icons.star),
Icon(Icons.star),
Icon(Icons.star),
],
);Stack(
children: <Widget>[
Container(
width: 100,
height: 100,
color: Colors.red,
),
Positioned(
top: 20,
left: 20,
child: Container(
width: 50,
height: 50,
color: Colors.blue,
),
),
],
);ListView(
children: <Widget>[
ListTile(title: Text('Item 1')),
ListTile(title: Text('Item 2')),
ListTile(title: Text('Item 3')),
],
);GridView.count(
crossAxisCount: 2,
children: <Widget>[
Container(color: Colors.red, height: 100),
Container(color: Colors.blue, height: 100),
Container(color: Colors.green, height: 100),
Container(color: Colors.yellow, height: 100),
],
);Wrap(
spacing: 8.0,
runSpacing: 4.0,
children: <Widget>[
Chip(label: Text('Chip 1')),
Chip(label: Text('Chip 2')),
Chip(label: Text('Chip 3')),
],
);Flex(
direction: Axis.horizontal,
children: <Widget>[
Expanded(child: Container(color: Colors.red, height:
50)),
Expanded(child: Container(color: Colors.blue, heigh
t: 50)),
],
);In this section, let us learn how to create a complex user interface of product listing with custom design using both single and multiple child layout widgets.
For this purpose, follow the sequence given below:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Product layout demo home page'),
);
}
}
import 'package:flutter/material.dart';
class ProductBox extends StatelessWidget {
const ProductBox({
Key? key,
this.name = '',
this.description = '',
this.price = 0,
this.image = '',
}) : super(key: key);
final String name;
final String description;
final int price;
final String image;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(2),
height: 120,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset(
'assets/appimages/' + image,
width: 100, // Adjust width as needed
height: 100, // Adjust height as needed
fit: BoxFit.cover, // Adjust BoxFit as needed
),
Expanded(
child: Container(
padding: const EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(
description,
maxLines: 2, // Adjust as needed
overflow: TextOverflow.ellipsis,
),
Text(
'Price: \$${price.toString()}',
style: TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
),
],
),
),
);
}
}
import 'package:flutter/material.dart';
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, this.title = ''});
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: <Widget>[
ProductBox(
name: "iPhone",
description: "iPhone is the stylist phone ever",
price: 1000,
image: "iPhone.jpg"),
ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone ever",
price: 800,
image: "pixel.png"),
ProductBox(
name: "Laptop",
description: "Laptop is most productive development tool",
price: 2000,
image: "laptop.jpg"),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device ever for meeting",
price: 1500,
image: "tablet.jpg"),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful rescue storage medium",
price: 20,
image: "floppy.jpg"),
],
),
);
}
}import 'package:flutter/material.dart';
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key, this.title = ''});
final String title;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("Product Listing")),
body: ListView(
shrinkWrap: true,
padding: const EdgeInsets.fromLTRB(2.0, 10.0, 2.0, 10.0),
children: const <Widget>[
ProductBox(
name: "iPhone",
description: "iPhone is the most stylish phone ever.",
price: 1000,
image: "iPhone.jpg",
),
ProductBox(
name: "Pixel",
description: "Pixel is the most featureful phone.",
price: 800,
image: "pixel.png",
),
ProductBox(
name: "Laptop",
description: "Laptop is the most productive development tool.",
price: 2000,
image: "laptop.jpg",
),
ProductBox(
name: "Tablet",
description: "Tablet is the most useful device for meetings.",
price: 1500,
image: "tablet.jpg",
),
ProductBox(
name: "Floppy Drive",
description: "Floppy drive is useful for rescue storage.",
price: 20,
image: "floppy.jpg",
),
],
),
);
}
}
class ProductBox extends StatelessWidget {
const ProductBox({
super.key,
this.name = '',
this.description = '',
this.price = 0,
this.image = '',
});
final String name;
final String description;
final int price;
final String image;
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(2),
height: 120,
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Image.asset(
"assets/appimages/$image",
width: 100,
height: 100,
fit: BoxFit.cover,
),
Expanded(
child: Container(
padding: const EdgeInsets.all(5),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
Text(
description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
Text(
"Price: \$${price.toString()}",
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
),
),
],
),
),
);
}
}In Flutter, gestures are a way to detect and respond to user interactions like taps, swipes, and drags. Flutter provides a robust system to handle gestures, making it easy to create interactive and responsive user interfaces. The primary widget used for handling gestures in Flutter is the GestureDetector .
GestureDetector: The GestureDetector widget is used to capture a wide range of gestures. It wraps around another widget and intercepts gestures like taps, double taps, long presses, swipes, and more.GestureDetector: The GestureDetector widget has properties like onTap , onDoubleTap , onLongPress , onPanUpdate , and others, each corresponding to a specific gesture.Let's create a simple example where a container changes its color when the user taps on it
import 'package:flutter/material.dart';
void main() {
runApp(GestureExample());
}
class GestureExample extends StatefulWidget {
@override
_GestureExampleState createState() => _GestureExampleState();
}
class _GestureExampleState extends State<GestureExample> {
Color _color = Colors.blue;
void _changeColor() {
setState(() {
_color = _color == Colors.blue ? Colors.red : Colors.blue;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Gesture Example'),
),
body: Center(
child: GestureDetector(
onTap: _changeColor,
child: Container(
width: 200,
height: 200,
color: _color,
child: Center(
child: Text(
'Tap me',
style: TextStyle(color: Colors.white, fontSize: 24),
),
),
),
),
),
),
);
}
}GestureDetector wraps a Container widget. The onTap callback is used to change the color of the container when the user taps on it._changeColor method is called when the container is tapped, toggling its color between blue and red.onPanUpdate callback. This is useful for interactive elements like sliders or custom scroll views.GestureDetector(
onPanUpdate: (details) {
// Handle drag
},
child: Container(
color: Colors.green,
width: 200,
height: 200,
),
);onHorizontalDragEnd or onVerticalDragEnd callbacks. These are triggered when the user swipes in a particular direction.dartCopy code
GestureDetector(
onHorizontalDragEnd: (details) {
// Handle horizontal swipe
},
child: Container(
color: Colors.orange,
width: 200,
height: 200,
),
);onScaleUpdate callback.dartCopy code
GestureDetector(
onScaleUpdate: (details) {
// Handle pinch-to-zoom
},
child: Container(
color: Colors.purple,
width: 200,
height: 200,
),
);PageView .State class
Example:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Ephemeral State Example'),
),
body: CounterWidget(),
),
);
}
}
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text('$_counter', style: Theme.of(context).textTheme.headline4),
ElevatedButton(
onPressed: _incrementCounter,
child: Text('Increment'),
),
],
),
);
}
}_counter variable is managed within the CounterWidget 's state class ( _CounterWidgetState ).CounterWidget and is not needed elsewhere in the app.setState is called, which tells Flutter to rebuild the widget with the updated state.InheritedWidget , Provider , Bloc , Riverpod , and more.StatefulWidget to hold the application state (in this case, a counter) and manage its changes.import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App State Management',
theme: ThemeData(primarySwatch: Colors.blue),
home: HomeScreen(
counter: _counter,
incrementCounter: _incrementCounter,
),
);
}
}_counter is our application-wide state. We have a function _incrementCounter() that updates the state. setState() is used to rebuild the widget tree when the state changes.HomeScreen and Pass the StateHomeScreen widget will display the counter value and allow navigation to a second screen where the counter can be updated.import 'package:flutter/material.dart';
class HomeScreen extends StatelessWidget {
final int counter;
final VoidCallback incrementCounter;
HomeScreen({
required this.counter,
required this.incrementCounter,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Counter value:',
style: TextStyle(fontSize: 20),
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(
counter: counter,
incrementCounter: incrementCounter,
),
),
);
},
child: const Text('Go to Second Screen'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
class SecondScreen extends StatelessWidget {
final int counter;
final VoidCallback incrementCounter;
SecondScreen({
required this.counter,
required this.incrementCounter,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Counter value on Second Screen:',
style: TextStyle(fontSize: 20),
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('Back to Home Screen'),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}HomeScreen takes the current counter value and the incrementCounter function as parameters.SecondScreen while passing the same state and callback function to allow state management across screens.SecondScreen will display the counter value and allow the user to increment the counter.import 'package:flutter/material.dart';
class SecondScreen extends StatelessWidget {
final int counter;
final VoidCallback incrementCounter;
SecondScreen({
required this.counter,
required this.incrementCounter,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Second Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Counter value:',
style: TextStyle(fontSize: 20),
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}CounterModel is a ChangeNotifier that holds the application state (in this case, the counter value).ChangeNotifierProvider makes CounterModel available to the entire widget tree. CounterWidget listens to changes in CounterModel using context.watch() and updates the UI accordingly.FloatingActionButton increments the counter, demonstrating how application state can be modified and shared across different parts of the app.Route in Flutter is an abstraction for a screen or page. When you navigate to a new screen, you are pushing a new Route onto the navigation stack.Navigator is a widget that manages a stack of Route objects. It provides methods to navigate between routes, such as push , pop , and others.Navigator.push method. This method takes a Route and adds it to the navigation stack, displaying the new screen.import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FirstScreen(),
);
}
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('First Screen')),
body: Center(
child: ElevatedButton(
child: Text('Go to Second Screen'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Second Screen')),
body: Center(
child: ElevatedButton(
child: Text('Go Back'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
}Navigator.push : This method is used to navigate to a new screen by adding a new route to the stack.MaterialPageRoute : This creates a route that uses a platform-specific animation to transition between screens.Navigator.pop : This removes the top route from the stack, returning to the previous screen.import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// Define the named routes
routes: {
'/': (context) => FirstScreen(),
'/second': (context) => SecondScreen(),
},
initialRoute: '/',
);
}
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('First Screen')),
body: Center(
child: ElevatedButton(
child: Text('Go to Second Screen'),
onPressed: () {
Navigator.pushNamed(context, '/second');
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Second Screen')),
body: Center(
child: ElevatedButton(
child: Text('Go Back'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
}MaterialApp widget using the routes property.Navigator.pushNamed : This method is used to navigate to a route using its name, which simplifies the navigation process, especially in larger apps.import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FirstScreen(),
);
}
}
class FirstScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('First Screen')),
body: Center(
child: ElevatedButton(
child: Text('Go to Second Screen with Data'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
SecondScreen(data: 'Hello from First Screen!'),
),
);
},
),
),
);
}
}
class SecondScreen extends StatelessWidget {
final String data;
SecondScreen({required this.data});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Second Screen')),
body: Center(
child: Text(data),
),
);
}
}SecondScreen through its constructor. This allows the SecondScreen to access and display the data.ScopedModel is a state management solution in Flutter that allows you to share state across your application in a way that is simple, efficient, and effective.Provider and Riverpod became mainstream, but it’s still useful for understanding how state can be managed in a structured manner.Model is a class that holds the application state. It extends the Model class from the scoped_model package. Any changes to the model can trigger a rebuild of widgets that depend on it.ScopedModel widget is used to provide the Model to the widget tree. It acts as a provider of the state and can be accessed by any widget in the subtree.ScopedModelDescendant is a widget that listens to changes in the Model . When the state in the Model changes, only those ScopedModelDescendant widgets that depend on the model will rebuild.Let’s create a simple counter application using ScopedModel
scoped_model DependencyFirst, add the scoped_model package to your pubspec.yaml file:
yamlCopy code
dependencies:
flutter:
sdk: flutter
scoped_model: ^1.0.1Then, run flutter pub get to install the package.
ModelCreate a CounterModel class that will hold the counter value and provide methods to modify it.
import 'package:scoped_model/scoped_model.dart';
class CounterModel extends Model {
int _counter = 0;
int get counter => _counter;
void increment() {
_counter++;
notifyListeners(); // Notifies all listening widgets to rebuild
}
}Model Using ScopedModelWrap your MaterialApp or Scaffold with a ScopedModel to provide the model to the widget tree.
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'counter_model.dart'; // Import the model
void main() {
runApp(
ScopedModel<CounterModel>(
model: CounterModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: CounterScreen(),
);
}
}ScopedModelDescendantUse ScopedModelDescendant to access and react to changes in the model.
import 'package:flutter/material.dart';
import 'package:scoped_model/scoped_model.dart';
import 'counter_model.dart';
class CounterScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Scoped Model Example'),
),
body: Center(
child: ScopedModelDescendant<CounterModel>(
builder: (context, child, model) {
return Text(
'Counter: ${model.counter}',
style: Theme.of(context).textTheme.headline4,
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
ScopedModel.of<CounterModel>(context).increment();
},
child: Icon(Icons.add),
),
);
}
}CounterModel class holds the counter value and a method to increment it. It extends Model and calls notifyListeners() whenever the counter value changes. This ensures that all widgets listening to this model get rebuilt with the updated state.ScopedModel widget provides the CounterModel to the entire widget tree. Any widget within this tree can access the model and react to changes.CounterModel . When the CounterModel updates, the ScopedModelDescendant will rebuild, displaying the new counter value.increment() method in the CounterModel to increase the counter value and notify listeners to update the UI.ScopedModel can become difficult to manage in larger applications with more complex state requirements.Provider has become the preferred state management solution, so ScopedModel has less community support and fewer resources.Made By SOU Student for SOU Students