divider.dart 9.77 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 16
/// In the material design language, this represents a divider. Dividers can be
/// 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 --template=stateless_widget_scaffold}
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
/// See also:
38
///
39
///  * [PopupMenuDivider], which is the equivalent but for popup menus.
40
///  * [ListTile.divideTiles], another approach to dividing widgets in a list.
41
///  * [VerticalDivider], which is the vertical analog of this widget.
42
///  * <https://material.io/design/components/dividers.html>
43
class Divider extends StatelessWidget {
44 45
  /// Creates a material design divider.
  ///
46 47
  /// The [height], [thickness], [indent], and [endIndent] must be null or
  /// non-negative.
48
  const Divider({
49
    Key? key,
50 51 52 53
    this.height,
    this.thickness,
    this.indent,
    this.endIndent,
54
    this.color,
55 56 57 58
  }) : assert(height == null || height >= 0.0),
       assert(thickness == null || thickness >= 0.0),
       assert(indent == null || indent >= 0.0),
       assert(endIndent == null || endIndent >= 0.0),
59
       super(key: key);
Hans Muller's avatar
Hans Muller committed
60

jslavitz's avatar
jslavitz committed
61 62

  /// The divider's height extent.
63
  ///
64 65
  /// The divider itself is always drawn as a horizontal line that is centered
  /// within the height specified by this value.
66
  ///
67 68
  /// If this is null, then the [DividerThemeData.space] is used. If that is
  /// also null, then this defaults to 16.0.
69
  final double? height;
70

71 72 73 74 75
  /// 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.
  ///
76
  /// If this is null, then the [DividerThemeData.thickness] is used. If
77
  /// that is also null, then this defaults to 0.0.
78
  final double? thickness;
79 80 81 82 83

  /// 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.
84
  final double? indent;
85

86 87 88 89
  /// 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.
90
  final double? endIndent;
91

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

107
  /// Computes the [BorderSide] that represents a divider.
108
  ///
109 110 111 112 113 114 115 116
  /// If [color] is null, then [DividerThemeData.color] is used. If that is also
  /// null, then [ThemeData.dividerColor] is used.
  ///
  /// 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.
117
  ///
118
  /// {@tool snippet}
119 120 121 122 123 124
  ///
  /// 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
125 126 127
  /// DecoratedBox(
  ///   decoration: BoxDecoration(
  ///     border: Border(
128 129 130 131 132 133 134
  ///       top: Divider.createBorderSide(context),
  ///       bottom: Divider.createBorderSide(context),
  ///     ),
  ///   ),
  ///   // child: ...
  /// )
  /// ```
135
  /// {@end-tool}
136 137
  static BorderSide createBorderSide(BuildContext? context, { Color? color, double? width }) {
    final Color? effectiveColor = color
138
        ?? (context != null ? (DividerTheme.of(context).color ?? Theme.of(context).dividerColor) : null);
139 140 141 142 143 144 145 146 147 148 149
    final double effectiveWidth =  width
        ?? (context != null ? DividerTheme.of(context).thickness : null)
        ?? 0.0;

    // Prevent assertion since it is possible that context is null and no color
    // is specified.
    if (effectiveColor == null) {
      return BorderSide(
        width: effectiveWidth,
      );
    }
150
    return BorderSide(
151 152
      color: effectiveColor,
      width: effectiveWidth,
153 154 155
    );
  }

156
  @override
Hans Muller's avatar
Hans Muller committed
157
  Widget build(BuildContext context) {
158 159 160 161 162 163
    final DividerThemeData dividerTheme = DividerTheme.of(context);
    final double height = this.height ?? dividerTheme.space ?? 16.0;
    final double thickness = this.thickness ?? dividerTheme.thickness ?? 0.0;
    final double indent = this.indent ?? dividerTheme.indent ?? 0.0;
    final double endIndent = this.endIndent ?? dividerTheme.endIndent ?? 0.0;

164
    return SizedBox(
165
      height: height,
166 167
      child: Center(
        child: Container(
168
          height: thickness,
169
          margin: EdgeInsetsDirectional.only(start: indent, end: endIndent),
170 171
          decoration: BoxDecoration(
            border: Border(
172
              bottom: createBorderSide(context, color: color, width: thickness),
173 174 175
            ),
          ),
        ),
Hans Muller's avatar
Hans Muller committed
176 177 178 179
      ),
    );
  }
}
jslavitz's avatar
jslavitz committed
180

181
/// A thin vertical line, with padding on either side.
jslavitz's avatar
jslavitz committed
182
///
183 184 185
/// In the material design language, this represents a divider. Vertical
/// dividers can be used in horizontally scrolling lists, such as a
/// [ListView] with [ListView.scrollDirection] set to [Axis.horizontal].
jslavitz's avatar
jslavitz committed
186 187 188 189
///
/// The box's total width is controlled by [width]. The appropriate
/// padding is automatically computed from the width.
///
190
/// {@tool dartpad --template=stateless_widget_scaffold}
191
/// This sample shows how to display a [VerticalDivider] between a purple and orange box
192 193 194 195
/// 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.
///
196
/// ** See code in examples/api/lib/material/divider/vertical_divider.0.dart **
197
/// {@end-tool}
198
///
jslavitz's avatar
jslavitz committed
199 200
/// See also:
///
201
///  * [ListView.separated], which can be used to generate vertical dividers.
202
///  * [Divider], which is the horizontal analog of this widget.
203
///  * <https://material.io/design/components/dividers.html>
jslavitz's avatar
jslavitz committed
204
class VerticalDivider extends StatelessWidget {
205
  /// Creates a material design vertical divider.
jslavitz's avatar
jslavitz committed
206
  ///
207 208
  /// The [width], [thickness], [indent], and [endIndent] must be null or
  /// non-negative.
jslavitz's avatar
jslavitz committed
209
  const VerticalDivider({
210
    Key? key,
211 212 213 214
    this.width,
    this.thickness,
    this.indent,
    this.endIndent,
215
    this.color,
216 217 218 219
  }) : assert(width == null || width >= 0.0),
       assert(thickness == null || thickness >= 0.0),
       assert(indent == null || indent >= 0.0),
       assert(endIndent == null || endIndent >= 0.0),
jslavitz's avatar
jslavitz committed
220 221 222 223
       super(key: key);

  /// The divider's width.
  ///
224 225
  /// 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
226
  ///
227 228
  /// If this is null, then the [DividerThemeData.space] is used. If that is
  /// also null, then this defaults to 16.0.
229
  final double? width;
jslavitz's avatar
jslavitz committed
230

231 232 233 234 235 236 237
  /// 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.
238
  final double? thickness;
239

240
  /// The amount of empty space on top of the divider.
241 242 243
  ///
  /// If this is null, then the [DividerThemeData.indent] is used. If that is
  /// also null, then this defaults to 0.0.
244
  final double? indent;
jslavitz's avatar
jslavitz committed
245

246
  /// The amount of empty space under the divider.
247 248 249
  ///
  /// If this is null, then the [DividerThemeData.endIndent] is used. If that is
  /// also null, then this defaults to 0.0.
250
  final double? endIndent;
251

jslavitz's avatar
jslavitz committed
252 253
  /// The color to use when painting the line.
  ///
254 255
  /// 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
256
  ///
257
  /// {@tool snippet}
jslavitz's avatar
jslavitz committed
258 259
  ///
  /// ```dart
260
  /// const Divider(
jslavitz's avatar
jslavitz committed
261 262 263
  ///   color: Colors.deepOrange,
  /// )
  /// ```
264
  /// {@end-tool}
265
  final Color? color;
jslavitz's avatar
jslavitz committed
266 267 268

  @override
  Widget build(BuildContext context) {
269 270 271 272 273 274
    final DividerThemeData dividerTheme = DividerTheme.of(context);
    final double width = this.width ?? dividerTheme.space ?? 16.0;
    final double thickness = this.thickness ?? dividerTheme.thickness ?? 0.0;
    final double indent = this.indent ?? dividerTheme.indent ?? 0.0;
    final double endIndent = this.endIndent ?? dividerTheme.endIndent ?? 0.0;

jslavitz's avatar
jslavitz committed
275 276 277 278
    return SizedBox(
      width: width,
      child: Center(
        child: Container(
279
          width: thickness,
280
          margin: EdgeInsetsDirectional.only(top: indent, bottom: endIndent),
jslavitz's avatar
jslavitz committed
281 282
          decoration: BoxDecoration(
            border: Border(
283
              left: Divider.createBorderSide(context, color: color, width: thickness),
jslavitz's avatar
jslavitz committed
284 285 286 287 288 289 290
            ),
          ),
        ),
      ),
    );
  }
}