25
loading...
This website collects cookies to deliver better user experience
Flex
widget, To make it easily reusable we will create a widget that holds and is responsible for the panes behaviour:class TwoPane extends StatelessWidget {
final double breakpoint = 800;
final int paneProportion = 70;
@override
Widget build(BuildContext context) {
if (breakpoint < MediaQuery.of(context).size.width) {
return Flex(
direction: Axis.horizontal,
children: [
Flexible(
flex: paneProportion,
child: Pane1(),
),
Flexible(
flex: 100 - paneProportion,
child: Pane2(),
),
],
);
}
return Flex(
direction: Axis.horizontal,
children: [
Flexible(
flex: 100,
child: Pane1(),
),
],
);
}
}
Pane1
and Pane2
are simple Containers with a text child and a background to make it easier to distinguish them.class Pane1 extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green[200],
child: Center(
child: Text('Pane 1'),
),
);
}
}
Flexible
widget. Pane1 has a flex of paneProportion
and pane2 the remaining space 100 - paneProportion
which, in this case are 70% and 30% respectively.class TwoPane extends StatefulWidget {
final Widget pane1;
final Widget pane2;
/// keeps track of the pane2 open state
final bool showPane2;
/// Called called when pane2Popup
final void Function() onClosePane2Popup;
/// the breakpoint for small devices
final double breakpoint;
/// pane1 has a flex of `paneProportion`. Default = 70
///
/// pane2 `100 - paneProportion`. Default = 30.
final int paneProportion;
const TwoPane({
Key? key,
this.showPane2 = false,
required this.pane1,
required this.pane2,
required this.onClosePane2Popup,
this.breakpoint = 800,
this.paneProportion = 70,
}) : super(key: key);
@override
_TwoPaneState createState() => _TwoPaneState();
}
class _TwoPaneState extends State<TwoPane> {
bool _popupNotOpen = true;
bool get canSplitPanes =>
widget.breakpoint < MediaQuery.of(context).size.width;
/// Loads and removes the popup page for pane2 on small screens
void loadPane2Page(BuildContext context) async {
if (widget.showPane2 && _popupNotOpen) {
_popupNotOpen = false;
SchedulerBinding.instance!.addPostFrameCallback((_) async {
// sets _popupNotOpen to true after popup is closed
Navigator.of(context)
.push<Null>(
new MaterialPageRoute<Null>(
builder: (BuildContext context) {
return new Scaffold(
appBar: AppBar(title: Text('hello')),
body: widget.pane2,
);
},
fullscreenDialog: true,
),
)
.then((_) {
// less code than wapping in a WillPopScope
_popupNotOpen = true;
// preserves value if screen canSplitPanes
if (!canSplitPanes) widget.onClosePane2Popup();
});
});
}
}
/// closes popup wind
void _closePopup() {
if (!_popupNotOpen) {
SchedulerBinding.instance!
.addPostFrameCallback((_) => Navigator.pop(context));
}
}
@override
Widget build(BuildContext context) {
if (canSplitPanes && widget.showPane2) {
_closePopup();
return Flex(
direction: Axis.horizontal,
children: [
Flexible(
flex: widget.paneProportion,
child: widget.pane1,
),
Flexible(
flex: 100 - widget.paneProportion,
child: widget.pane2,
),
],
);
} else {
loadPane2Page(context);
return Flex(
direction: Axis.horizontal,
children: [
Flexible(
flex: 100,
child: widget.pane1,
),
],
);
}
}
}
final Widget pane1;
final Widget pane2;
final bool showPane2;
final void Function() onClosePane2Popup;
final double breakpoint;
final int paneProportion;
onClosePane2Popup
callback. Useful to update values when we close the popup windows on small screens.canSplitPanes
: to keep track of the window size and breakpointloadPane2Page
: To load the popup page for pane2 on small screens_closePopup
: to close the popup window_popupNotOpen
to track whether the popup is open or not, this is used to prevent reopening the popup window every time we resize the screen. Since _popupNotOpen is a non final field we made the component stateful, which will be necessary when we add animations anyway:bool _popupNotOpen = true;
bool get canSplitPanes =>
widget.breakpoint < MediaQuery.of(context).size.width;
/// Loads the popup page for pane2 on small screens
void loadPane2Page(BuildContext context) async {
if (widget.showPane2 && _popupNotOpen) {
_popupNotOpen = false;
SchedulerBinding.instance!.addPostFrameCallback((_) async {
...
)
.then((_) {
// less code than wapping in a WillPopScope
_popupNotOpen = true;
// preserves value if screen canSplitPanes
if (!canSplitPanes) widget.onClosePane2Popup();
});
});
}
}
/// closes popup wind
void _closePopup() {
if (!_popupNotOpen) {
SchedulerBinding.instance!
.addPostFrameCallback((_) => Navigator.pop(context));
}
}
loadPane2Page
after we pop the window, we set _popupNotOpen
to true
and if (!canSplitPanes) widget.onClosePane2Popup();
ensures that the callback is only run when the user closes the popup window and not when resizing.loadPane2Page
and _closePopup
are wrapped in a SchedulerBinding.instance!.addPostFrameCallback()
, this is to ensure that the widgets have been properly rendered before calling another render job.StatefullWidget
and ValueNotifier
s to update the state and rebuild the components as needed, however, feel free to use your state management of choice.Pane1
and Pane2
to the Home page file since they aren't really part of our TwoPane
component, they are only passed as children. // home_page.dart component
import 'package:flutter/material.dart';
import 'package:two_columns/steps/two_columns1.dart';
class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
ValueNotifier<int?> _selected = ValueNotifier(null);
void _selectValue(int? val) => _selected.value = val;
void _clearSelected() => _selected.value = null;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home page')),
body: ValueListenableBuilder(
builder: (context, _, child) {
return TwoColumns(
showPane2: (_selected.value != null) ? true : false,
onClosePane2Popup: _clearSelected,
pane1: Pane1(selectValue: _selectValue),
pane2: Pane2(value: _selected.value),
);
},
valueListenable: _selected,
),
);
}
}
class Pane1 extends StatelessWidget {
final void Function(int?) selectValue;
const Pane1({required this.selectValue});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.green[200],
child: Center(
child: ElevatedButton(
child: Text('set value'),
onPressed: () => selectValue(3),
),
),
);
}
}
class Pane2 extends StatelessWidget {
final int? value;
const Pane2({Key? key, this.value}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
color: Colors.blue[200],
child: Center(
child: Text('Pane2 value is $value'),
),
);
}
}
pane1
and a detail card to pane2
25