mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
289 lines
7.8 KiB
Dart
289 lines
7.8 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
/// Flutter code sample for [SafeArea].
|
|
///
|
|
/// The app is wrapped with [Insets] (defined below)
|
|
/// to simulate a mobile device with a notched screen.
|
|
|
|
void main() => runApp(const Insets());
|
|
|
|
class SafeAreaExampleApp extends StatelessWidget {
|
|
const SafeAreaExampleApp({super.key});
|
|
|
|
static const Color spring = Color(0xFF00FF80);
|
|
static final ColorScheme colors = ColorScheme.fromSeed(seedColor: spring);
|
|
static final ThemeData theme = ThemeData(
|
|
colorScheme: colors,
|
|
sliderTheme: SliderThemeData(
|
|
trackHeight: 8,
|
|
activeTrackColor: colors.primary.withValues(alpha: 0.5),
|
|
thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 12),
|
|
),
|
|
scaffoldBackgroundColor: const Color(0xFFD0FFE8),
|
|
listTileTheme: const ListTileThemeData(
|
|
tileColor: Colors.white70,
|
|
contentPadding: EdgeInsets.all(6.0),
|
|
),
|
|
appBarTheme: AppBarTheme(
|
|
backgroundColor: colors.secondary,
|
|
foregroundColor: colors.onSecondary,
|
|
),
|
|
);
|
|
|
|
static final AppBar appBar = AppBar(title: const Text('SafeArea Demo'));
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return MaterialApp(
|
|
theme: theme,
|
|
debugShowCheckedModeBanner: false,
|
|
home: Builder(
|
|
builder: (BuildContext context) => Scaffold(
|
|
appBar: Toggle.appBar.of(context) ? appBar : null,
|
|
body: const DefaultTextStyle(
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black,
|
|
),
|
|
child: Center(
|
|
child: SafeAreaExample(),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class SafeAreaExample extends StatelessWidget {
|
|
const SafeAreaExample({super.key});
|
|
|
|
static final Widget controls = Column(
|
|
children: <Widget>[
|
|
const SizedBox(height: 14),
|
|
Builder(
|
|
builder: (BuildContext context) => Text(
|
|
Toggle.safeArea.of(context) ? 'safe area!' : 'no safe area',
|
|
style: const TextStyle(fontSize: 24),
|
|
),
|
|
),
|
|
const Spacer(flex: 2),
|
|
for (final Value data in Value.allValues) ...data.controls,
|
|
],
|
|
);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final bool hasSafeArea = Toggle.safeArea.of(context);
|
|
|
|
return SafeArea(
|
|
top: hasSafeArea,
|
|
bottom: hasSafeArea,
|
|
left: hasSafeArea,
|
|
right: hasSafeArea,
|
|
child: controls,
|
|
);
|
|
}
|
|
}
|
|
|
|
sealed class Value implements Enum {
|
|
Object _getValue(covariant Model<Value> model);
|
|
|
|
List<Widget> get controls;
|
|
|
|
static const List<Value> allValues = <Value>[...Inset.values, ...Toggle.values];
|
|
}
|
|
|
|
enum Inset implements Value {
|
|
top('top notch'),
|
|
sides('side padding'),
|
|
bottom('bottom indicator');
|
|
|
|
const Inset(this.label);
|
|
|
|
final String label;
|
|
|
|
@override
|
|
double _getValue(_InsetModel model) => switch (this) {
|
|
top => model.insets.top,
|
|
sides => model.insets.left,
|
|
bottom => model.insets.bottom,
|
|
};
|
|
|
|
double of(BuildContext context) => _getValue(Model.of<_InsetModel>(context, this));
|
|
|
|
@override
|
|
List<Widget> get controls => <Widget>[
|
|
Text(label),
|
|
Builder(
|
|
builder: (BuildContext context) => Slider(
|
|
max: 50,
|
|
value: of(context),
|
|
onChanged: (double newValue) {
|
|
InsetsState.instance.changeInset(this, newValue);
|
|
},
|
|
),
|
|
),
|
|
const Spacer(),
|
|
];
|
|
}
|
|
|
|
enum Toggle implements Value {
|
|
appBar('Build an AppBar?'),
|
|
safeArea("Wrap Scaffold's body with SafeArea?");
|
|
|
|
const Toggle(this.label);
|
|
|
|
final String label;
|
|
|
|
@override
|
|
bool _getValue(_ToggleModel model) => switch (this) {
|
|
appBar => model.buildAppBar,
|
|
safeArea => model.buildSafeArea,
|
|
};
|
|
|
|
bool of(BuildContext context) => _getValue(Model.of<_ToggleModel>(context, this));
|
|
|
|
@override
|
|
List<Widget> get controls => <Widget>[
|
|
Builder(
|
|
builder: (BuildContext context) => SwitchListTile(
|
|
title: Text(label),
|
|
value: of(context),
|
|
onChanged: (bool value) {
|
|
InsetsState.instance.toggle(this, value);
|
|
},
|
|
),
|
|
),
|
|
];
|
|
}
|
|
|
|
abstract class Model<E extends Value> extends InheritedModel<E> {
|
|
const Model({super.key, required super.child});
|
|
|
|
static M of<M extends Model<Value>>(BuildContext context, Value value) {
|
|
return context.dependOnInheritedWidgetOfExactType<M>(aspect: value)!;
|
|
}
|
|
|
|
@override
|
|
bool updateShouldNotify(Model<E> oldWidget) => true;
|
|
|
|
@override
|
|
bool updateShouldNotifyDependent(Model<E> oldWidget, Set<E> dependencies) {
|
|
return dependencies.any((E data) => data._getValue(this) != data._getValue(oldWidget));
|
|
}
|
|
}
|
|
|
|
class _InsetModel extends Model<Inset> {
|
|
const _InsetModel({required this.insets, required super.child});
|
|
|
|
final EdgeInsets insets;
|
|
}
|
|
|
|
class _ToggleModel extends Model<Toggle> {
|
|
_ToggleModel({required Set<Toggle> togglers, required super.child})
|
|
: buildAppBar = togglers.contains(Toggle.appBar),
|
|
buildSafeArea = togglers.contains(Toggle.safeArea);
|
|
|
|
final bool buildAppBar;
|
|
final bool buildSafeArea;
|
|
}
|
|
|
|
class Insets extends UniqueWidget<InsetsState> {
|
|
const Insets() : super(key: const GlobalObjectKey<InsetsState>('insets'));
|
|
|
|
@override
|
|
InsetsState createState() => InsetsState();
|
|
}
|
|
|
|
class InsetsState extends State<Insets> {
|
|
static InsetsState get instance => const Insets().currentState!;
|
|
|
|
EdgeInsets insets = const EdgeInsets.fromLTRB(8, 25, 8, 12);
|
|
void changeInset(Inset inset, double value) {
|
|
setState(() {
|
|
insets = switch (inset) {
|
|
Inset.top => insets.copyWith(top: value),
|
|
Inset.sides => insets.copyWith(left: value, right: value),
|
|
Inset.bottom => insets.copyWith(bottom: value),
|
|
};
|
|
});
|
|
}
|
|
|
|
final Set<Toggle> _togglers = <Toggle>{};
|
|
void toggle(Toggle toggler, bool value) {
|
|
setState(() {
|
|
value ? _togglers.add(toggler) : _togglers.remove(toggler);
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final Widget topNotch = ClipRRect(
|
|
borderRadius: BorderRadius.vertical(
|
|
bottom: Radius.circular(insets.top),
|
|
),
|
|
child: SizedBox(
|
|
height: insets.top,
|
|
child: const FractionallySizedBox(
|
|
widthFactor: 1 / 2,
|
|
child: ColoredBox(color: Colors.black),
|
|
),
|
|
),
|
|
);
|
|
final Widget bottomIndicator = SizedBox(
|
|
width: double.infinity,
|
|
height: insets.bottom,
|
|
child: const FractionallySizedBox(
|
|
heightFactor: 0.5,
|
|
widthFactor: 0.5,
|
|
child: PhysicalShape(
|
|
clipper: ShapeBorderClipper(shape: StadiumBorder()),
|
|
color: Color(0xC0000000),
|
|
child: SizedBox.expand(),
|
|
),
|
|
),
|
|
);
|
|
final Widget sideBar = SizedBox(
|
|
width: insets.left,
|
|
height: double.infinity,
|
|
child: const IgnorePointer(child: ColoredBox(color: Colors.black12)),
|
|
);
|
|
|
|
final Widget app = _ToggleModel(
|
|
togglers: _togglers,
|
|
child: Builder(
|
|
builder: (BuildContext context) => MediaQuery(
|
|
data: MediaQuery.of(context).copyWith(
|
|
viewInsets: EdgeInsets.only(top: insets.top),
|
|
viewPadding: insets,
|
|
padding: insets,
|
|
),
|
|
child: const SafeAreaExampleApp(),
|
|
),
|
|
),
|
|
);
|
|
|
|
return _InsetModel(
|
|
insets: insets,
|
|
child: Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: Stack(
|
|
children: <Widget>[
|
|
app,
|
|
Align(alignment: Alignment.topCenter, child: topNotch),
|
|
Align(alignment: Alignment.bottomCenter, child: bottomIndicator),
|
|
Align(alignment: Alignment.centerLeft, child: sideBar),
|
|
Align(alignment: Alignment.centerRight, child: sideBar),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|