divider.dart 11.7 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
Hans Muller's avatar
Hans Muller committed
2 3 4 5 6
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/widgets.dart';

7
import 'divider_theme.dart';
Hans Muller's avatar
Hans Muller committed
8 9
import 'theme.dart';

10
// Examples can assume:
11
// late BuildContext context;
12

13
/// A thin horizontal line, with padding on either side.
14
///
15
/// In the Material Design language, this represents a divider. Dividers can be
16
/// used in lists, [Drawer]s, and elsewhere to separate content.
17
///
18
/// To create a divider between [ListTile] items, consider using
jslavitz's avatar
jslavitz committed
19
/// [ListTile.divideTiles], which is optimized for this case.
20
///
21 22
/// {@youtube 560 315 https://www.youtube.com/watch?v=_liUC641Nmk}
///
jslavitz's avatar
jslavitz committed
23
/// The box's total height is controlled by [height]. The appropriate
24
/// padding is automatically computed from the height.
25
///
26
/// {@tool dartpad}
27 28 29 30 31 32 33
/// This sample shows how to display a Divider between an orange and blue box
/// inside a column. The Divider is 20 logical pixels in height and contains a
/// vertically centered black line that is 5 logical pixels thick. The black
/// line is indented by 20 logical pixels.
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/material/divider.png)
///
34
/// ** See code in examples/api/lib/material/divider/divider.0.dart **
35
/// {@end-tool}
36
///
37 38 39 40 41 42 43
/// {@tool dartpad}
/// This sample shows the creation of [Divider] widget, as described in:
/// https://m3.material.io/components/divider/overview
///
/// ** See code in examples/api/lib/material/divider/divider.1.dart **
/// {@end-tool}
///
44
/// See also:
45
///
46
///  * [PopupMenuDivider], which is the equivalent but for popup menus.
47
///  * [ListTile.divideTiles], another approach to dividing widgets in a list.
48
///  * [VerticalDivider], which is the vertical analog of this widget.
49
///  * <https://material.io/design/components/dividers.html>
50
class Divider extends StatelessWidget {
51
  /// Creates a Material Design divider.
52
  ///
53 54
  /// The [height], [thickness], [indent], and [endIndent] must be null or
  /// non-negative.
55
  const Divider({
56
    super.key,
57 58 59 60
    this.height,
    this.thickness,
    this.indent,
    this.endIndent,
61
    this.color,
62 63 64
  }) : assert(height == null || height >= 0.0),
       assert(thickness == null || thickness >= 0.0),
       assert(indent == null || indent >= 0.0),
65
       assert(endIndent == null || endIndent >= 0.0);
Hans Muller's avatar
Hans Muller committed
66

jslavitz's avatar
jslavitz committed
67 68

  /// The divider's height extent.
69
  ///
70 71
  /// The divider itself is always drawn as a horizontal line that is centered
  /// within the height specified by this value.
72
  ///
73 74
  /// If this is null, then the [DividerThemeData.space] is used. If that is
  /// also null, then this defaults to 16.0.
75
  final double? height;
76

77 78 79 80 81
  /// The thickness of the line drawn within the divider.
  ///
  /// A divider with a [thickness] of 0.0 is always drawn as a line with a
  /// height of exactly one device pixel.
  ///
82
  /// If this is null, then the [DividerThemeData.thickness] is used. If
83
  /// that is also null, then this defaults to 0.0.
84
  final double? thickness;
85 86 87 88 89

  /// The amount of empty space to the leading edge of the divider.
  ///
  /// If this is null, then the [DividerThemeData.indent] is used. If that is
  /// also null, then this defaults to 0.0.
90
  final double? indent;
91

92 93 94 95
  /// The amount of empty space to the trailing edge of the divider.
  ///
  /// If this is null, then the [DividerThemeData.endIndent] is used. If that is
  /// also null, then this defaults to 0.0.
96
  final double? endIndent;
97

98 99
  /// The color to use when painting the line.
  ///
100 101
  /// If this is null, then the [DividerThemeData.color] is used. If that is
  /// also null, then [ThemeData.dividerColor] is used.
102
  ///
103
  /// {@tool snippet}
104
  ///
105
  /// ```dart
106
  /// const Divider(
107 108
  ///   color: Colors.deepOrange,
  /// )
109
  /// ```
110
  /// {@end-tool}
111
  final Color? color;
Hans Muller's avatar
Hans Muller committed
112

113
  /// Computes the [BorderSide] that represents a divider.
114
  ///
115
  /// If [color] is null, then [DividerThemeData.color] is used. If that is also
116 117 118
  /// null, then if [ThemeData.useMaterial3] is true then it defaults to
  /// [ThemeData.colorScheme]'s [ColorScheme.outlineVariant]. Otherwise
  /// [ThemeData.dividerColor] is used.
119 120 121 122 123 124
  ///
  /// If [width] is null, then [DividerThemeData.thickness] is used. If that is
  /// also null, then this defaults to 0.0 (a hairline border).
  ///
  /// If [context] is null, the default color of [BorderSide] is used and the
  /// default width of 0.0 is used.
125
  ///
126
  /// {@tool snippet}
127 128 129 130 131 132
  ///
  /// This example uses this method to create a box that has a divider above and
  /// below it. This is sometimes useful with lists, for instance, to separate a
  /// scrollable section from the rest of the interface.
  ///
  /// ```dart
133 134 135
  /// DecoratedBox(
  ///   decoration: BoxDecoration(
  ///     border: Border(
136 137 138 139 140 141 142
  ///       top: Divider.createBorderSide(context),
  ///       bottom: Divider.createBorderSide(context),
  ///     ),
  ///   ),
  ///   // child: ...
  /// )
  /// ```
143
  /// {@end-tool}
144
  static BorderSide createBorderSide(BuildContext? context, { Color? color, double? width }) {
145 146 147 148 149 150
    final DividerThemeData? dividerTheme = context != null ? DividerTheme.of(context) : null;
    final DividerThemeData? defaults = context != null
      ? Theme.of(context).useMaterial3 ? _DividerDefaultsM3(context) : _DividerDefaultsM2(context)
      : null;
    final Color? effectiveColor = color ?? dividerTheme?.color ?? defaults?.color;
    final double effectiveWidth =  width ?? dividerTheme?.thickness ?? defaults?.thickness ?? 0.0;
151 152 153 154 155 156 157 158

    // Prevent assertion since it is possible that context is null and no color
    // is specified.
    if (effectiveColor == null) {
      return BorderSide(
        width: effectiveWidth,
      );
    }
159
    return BorderSide(
160 161
      color: effectiveColor,
      width: effectiveWidth,
162 163 164
    );
  }

165
  @override
Hans Muller's avatar
Hans Muller committed
166
  Widget build(BuildContext context) {
167
    final ThemeData theme = Theme.of(context);
168
    final DividerThemeData dividerTheme = DividerTheme.of(context);
169 170 171 172 173
    final DividerThemeData defaults = theme.useMaterial3 ? _DividerDefaultsM3(context) : _DividerDefaultsM2(context);
    final double height = this.height ?? dividerTheme.space ?? defaults.space!;
    final double thickness = this.thickness ?? dividerTheme.thickness ?? defaults.thickness!;
    final double indent = this.indent ?? dividerTheme.indent ?? defaults.indent!;
    final double endIndent = this.endIndent ?? dividerTheme.endIndent ?? defaults.endIndent!;
174

175
    return SizedBox(
176
      height: height,
177 178
      child: Center(
        child: Container(
179
          height: thickness,
180
          margin: EdgeInsetsDirectional.only(start: indent, end: endIndent),
181 182
          decoration: BoxDecoration(
            border: Border(
183
              bottom: createBorderSide(context, color: color, width: thickness),
184 185 186
            ),
          ),
        ),
Hans Muller's avatar
Hans Muller committed
187 188 189 190
      ),
    );
  }
}
jslavitz's avatar
jslavitz committed
191

192
/// A thin vertical line, with padding on either side.
jslavitz's avatar
jslavitz committed
193
///
194
/// In the Material Design language, this represents a divider. Vertical
195 196
/// dividers can be used in horizontally scrolling lists, such as a
/// [ListView] with [ListView.scrollDirection] set to [Axis.horizontal].
jslavitz's avatar
jslavitz committed
197 198 199 200
///
/// The box's total width is controlled by [width]. The appropriate
/// padding is automatically computed from the width.
///
201
/// {@tool dartpad}
202
/// This sample shows how to display a [VerticalDivider] between a purple and orange box
203 204 205 206
/// inside a [Row]. The [VerticalDivider] is 20 logical pixels in width and contains a
/// horizontally centered black line that is 1 logical pixels thick. The grey
/// line is indented by 20 logical pixels.
///
207
/// ** See code in examples/api/lib/material/divider/vertical_divider.0.dart **
208
/// {@end-tool}
209
///
210 211 212 213 214 215 216
/// {@tool dartpad}
/// This sample shows the creation of [VerticalDivider] widget, as described in:
/// https://m3.material.io/components/divider/overview
///
/// ** See code in examples/api/lib/material/divider/vertical_divider.1.dart **
/// {@end-tool}
///
jslavitz's avatar
jslavitz committed
217 218
/// See also:
///
219
///  * [ListView.separated], which can be used to generate vertical dividers.
220
///  * [Divider], which is the horizontal analog of this widget.
221
///  * <https://material.io/design/components/dividers.html>
jslavitz's avatar
jslavitz committed
222
class VerticalDivider extends StatelessWidget {
223
  /// Creates a Material Design vertical divider.
jslavitz's avatar
jslavitz committed
224
  ///
225 226
  /// The [width], [thickness], [indent], and [endIndent] must be null or
  /// non-negative.
jslavitz's avatar
jslavitz committed
227
  const VerticalDivider({
228
    super.key,
229 230 231 232
    this.width,
    this.thickness,
    this.indent,
    this.endIndent,
233
    this.color,
234 235 236
  }) : assert(width == null || width >= 0.0),
       assert(thickness == null || thickness >= 0.0),
       assert(indent == null || indent >= 0.0),
237
       assert(endIndent == null || endIndent >= 0.0);
jslavitz's avatar
jslavitz committed
238 239 240

  /// The divider's width.
  ///
241 242
  /// The divider itself is always drawn as a vertical line that is centered
  /// within the width specified by this value.
jslavitz's avatar
jslavitz committed
243
  ///
244 245
  /// If this is null, then the [DividerThemeData.space] is used. If that is
  /// also null, then this defaults to 16.0.
246
  final double? width;
jslavitz's avatar
jslavitz committed
247

248 249 250 251 252 253 254
  /// The thickness of the line drawn within the divider.
  ///
  /// A divider with a [thickness] of 0.0 is always drawn as a line with a
  /// width of exactly one device pixel.
  ///
  /// If this is null, then the [DividerThemeData.thickness] is used which
  /// defaults to 0.0.
255
  final double? thickness;
256

257
  /// The amount of empty space on top of the divider.
258 259 260
  ///
  /// If this is null, then the [DividerThemeData.indent] is used. If that is
  /// also null, then this defaults to 0.0.
261
  final double? indent;
jslavitz's avatar
jslavitz committed
262

263
  /// The amount of empty space under the divider.
264 265 266
  ///
  /// If this is null, then the [DividerThemeData.endIndent] is used. If that is
  /// also null, then this defaults to 0.0.
267
  final double? endIndent;
268

jslavitz's avatar
jslavitz committed
269 270
  /// The color to use when painting the line.
  ///
271 272
  /// If this is null, then the [DividerThemeData.color] is used. If that is
  /// also null, then [ThemeData.dividerColor] is used.
jslavitz's avatar
jslavitz committed
273
  ///
274
  /// {@tool snippet}
jslavitz's avatar
jslavitz committed
275 276
  ///
  /// ```dart
277
  /// const Divider(
jslavitz's avatar
jslavitz committed
278 279 280
  ///   color: Colors.deepOrange,
  /// )
  /// ```
281
  /// {@end-tool}
282
  final Color? color;
jslavitz's avatar
jslavitz committed
283 284 285

  @override
  Widget build(BuildContext context) {
286
    final ThemeData theme = Theme.of(context);
287
    final DividerThemeData dividerTheme = DividerTheme.of(context);
288 289 290 291 292
    final DividerThemeData defaults = theme.useMaterial3 ? _DividerDefaultsM3(context) : _DividerDefaultsM2(context);
    final double width = this.width ?? dividerTheme.space ?? defaults.space!;
    final double thickness = this.thickness ?? dividerTheme.thickness ?? defaults.thickness!;
    final double indent = this.indent ?? dividerTheme.indent ?? defaults.indent!;
    final double endIndent = this.endIndent ?? dividerTheme.endIndent ?? defaults.endIndent!;
293

jslavitz's avatar
jslavitz committed
294 295 296 297
    return SizedBox(
      width: width,
      child: Center(
        child: Container(
298
          width: thickness,
299
          margin: EdgeInsetsDirectional.only(top: indent, bottom: endIndent),
jslavitz's avatar
jslavitz committed
300 301
          decoration: BoxDecoration(
            border: Border(
302
              left: Divider.createBorderSide(context, color: color, width: thickness),
jslavitz's avatar
jslavitz committed
303 304 305 306 307 308 309
            ),
          ),
        ),
      ),
    );
  }
}
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344

class _DividerDefaultsM2 extends DividerThemeData {
  const _DividerDefaultsM2(this.context) : super(
    space: 16,
    thickness: 0,
    indent: 0,
    endIndent: 0,
  );

  final BuildContext context;

  @override Color? get color => Theme.of(context).dividerColor;
}

// BEGIN GENERATED TOKEN PROPERTIES - Divider

// Do not edit by hand. The code between the "BEGIN GENERATED" and
// "END GENERATED" comments are generated from data in the Material
// Design token database by the script:
//   dev/tools/gen_defaults/bin/gen_defaults.dart.

class _DividerDefaultsM3 extends DividerThemeData {
  const _DividerDefaultsM3(this.context) : super(
    space: 16,
    thickness: 1.0,
    indent: 0,
    endIndent: 0,
  );

  final BuildContext context;

  @override Color? get color => Theme.of(context).colorScheme.outlineVariant;
}

// END GENERATED TOKEN PROPERTIES - Divider