list_item.dart 6.33 KB
Newer Older
Adam Barth's avatar
Adam Barth committed
1 2 3 4 5 6
// Copyright 2015 The Chromium 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/widgets.dart';

7
import 'constants.dart';
8
import 'debug.dart';
Adam Barth's avatar
Adam Barth committed
9
import 'ink_well.dart';
Hans Muller's avatar
Hans Muller committed
10
import 'theme.dart';
Adam Barth's avatar
Adam Barth committed
11

12 13 14 15 16 17 18 19
/// An item in a material design list.
///
/// [MaterialList] items are one to three lines of text optionally flanked by
/// icons. Icons are defined with the [leading] and [trailing] parameters. The
/// first line of text is not optional and is specified with [title]. The value
/// of [subtitle] will occupy the space allocated for an aditional line of text,
/// or two lines if [isThreeLine] is true. If [dense] is true then the overall
/// height of this list item and the size of the [DefaultTextStyle]s that wrap
20
/// the [title] and [subtitle] widget are reduced.
21 22 23 24
///
/// Requires one of its ancestors to be a [Material] widget.
///
/// See also:
25
///
26 27 28
///  * [MaterialList]
///  * [CircleAvatar]
///  * <https://www.google.com/design/spec/components/lists.html>
29
class ListItem extends StatelessWidget {
30 31 32 33 34
  /// Creates a list item.
  ///
  /// If [isThreeLine] is true, then [subtitle] must not be null.
  ///
  /// Requires one of its ancestors to be a [Material] widget.
Adam Barth's avatar
Adam Barth committed
35 36
  ListItem({
    Key key,
37 38 39 40
    this.leading,
    this.title,
    this.subtitle,
    this.trailing,
Hans Muller's avatar
Hans Muller committed
41
    this.isThreeLine: false,
42 43
    this.dense: false,
    this.enabled: true,
Adam Barth's avatar
Adam Barth committed
44 45
    this.onTap,
    this.onLongPress
46
  }) : super(key: key);
Adam Barth's avatar
Adam Barth committed
47

48 49 50
  /// A widget to display before the title.
  ///
  /// Typically a [CircleAvatar] widget.
51
  final Widget leading;
52 53 54 55

  /// The primary content of the list item.
  ///
  /// Typically a [Text] widget.
56
  final Widget title;
57 58 59 60

  /// Additional content displayed below the title.
  ///
  /// Typically a [Text] widget.
61
  final Widget subtitle;
62 63 64 65

  /// A widget to display after the title.
  ///
  /// Typically an [Icon] widget.
66
  final Widget trailing;
67 68

  /// Whether this list item is intended to display three lines of text.
69 70 71
  ///
  /// If false, the list item is treated as having one line if the subtitle is
  /// null and treated as having two lines if the subtitle is non-null.
Hans Muller's avatar
Hans Muller committed
72
  final bool isThreeLine;
73 74

  /// Whether this list item is part of a vertically dense list.
75
  final bool dense;
76

77
  /// Whether this list item is interactive.
78
  ///
79 80 81
  /// If false, this list item is styled with the disabled color from the
  /// current [Theme] and the [onTap] and [onLongPress] callbacks are
  /// inoperative.
82
  final bool enabled;
83 84

  /// Called when the user taps this list item.
85 86
  ///
  /// Inoperative if [enabled] is false.
Adam Barth's avatar
Adam Barth committed
87
  final GestureTapCallback onTap;
88 89

  /// Called when the user long-presses on this list item.
90 91
  ///
  /// Inoperative if [enabled] is false.
Adam Barth's avatar
Adam Barth committed
92 93
  final GestureLongPressCallback onLongPress;

Hans Muller's avatar
Hans Muller committed
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
  /// Add a one pixel border in between each item. If color isn't specified the
  /// dividerColor of the context's theme is used.
  static Iterable<Widget> divideItems({ BuildContext context, Iterable<Widget> items, Color color }) sync* {
    assert(items != null);
    assert(color != null || context != null);

    final Color dividerColor = color ?? Theme.of(context).dividerColor;
    final Iterator<Widget> iterator = items.iterator;
    final bool isNotEmpty = iterator.moveNext();

    Widget item = iterator.current;
    while(iterator.moveNext()) {
      yield new DecoratedBox(
        decoration: new BoxDecoration(
          border: new Border(
            bottom: new BorderSide(color: dividerColor)
          )
        ),
        child: item
      );
      item = iterator.current;
    }
    if (isNotEmpty)
      yield item;
  }

120
  TextStyle _primaryTextStyle(BuildContext context) {
121
    final ThemeData theme = Theme.of(context);
122
    final TextStyle style = theme.textTheme.subhead;
123
    if (!enabled) {
124
      final Color color = theme.disabledColor;
125
      return dense ? style.copyWith(fontSize: 13.0, color: color) : style.copyWith(color: color);
126
    }
127
    return dense ? style.copyWith(fontSize: 13.0) : style;
Hans Muller's avatar
Hans Muller committed
128 129
  }

130
  TextStyle _secondaryTextStyle(BuildContext context) {
Hans Muller's avatar
Hans Muller committed
131
    final ThemeData theme = Theme.of(context);
132 133
    final Color color = theme.textTheme.caption.color;
    final TextStyle style = theme.textTheme.body1;
134
    return dense ? style.copyWith(color: color, fontSize: 12.0) : style.copyWith(color: color);
Hans Muller's avatar
Hans Muller committed
135 136
  }

137
  @override
Adam Barth's avatar
Adam Barth committed
138
  Widget build(BuildContext context) {
139
    assert(debugCheckHasMaterial(context));
140
    final bool isTwoLine = !isThreeLine && subtitle != null;
Hans Muller's avatar
Hans Muller committed
141 142 143
    final bool isOneLine = !isThreeLine && !isTwoLine;
    double itemHeight;
    if (isOneLine)
144
      itemHeight = dense ? 48.0 : 56.0;
Hans Muller's avatar
Hans Muller committed
145
    else if (isTwoLine)
146
      itemHeight = dense ? 60.0 : 72.0;
Hans Muller's avatar
Hans Muller committed
147
    else
148
      itemHeight = dense ? 76.0 : 88.0;
Hans Muller's avatar
Hans Muller committed
149 150 151

    // Overall, the list item is a Row() with these children.
    final List<Widget> children = <Widget>[];
Adam Barth's avatar
Adam Barth committed
152

153
    if (leading != null) {
Adam Barth's avatar
Adam Barth committed
154
      children.add(new Container(
155
        margin: const EdgeInsets.only(right: 16.0),
Adam Barth's avatar
Adam Barth committed
156
        width: 40.0,
Hans Muller's avatar
Hans Muller committed
157
        child: new Align(
158
          alignment: FractionalOffset.centerLeft,
159
          child: leading
Hans Muller's avatar
Hans Muller committed
160
        )
Adam Barth's avatar
Adam Barth committed
161 162 163
      ));
    }

164
    final Widget primaryLine = new AnimatedDefaultTextStyle(
165
      style: _primaryTextStyle(context),
166
      duration: kThemeChangeDuration,
167
      child: title ?? new Container()
Hans Muller's avatar
Hans Muller committed
168 169
    );
    Widget center = primaryLine;
170
    if (subtitle != null && (isTwoLine || isThreeLine)) {
Hans Muller's avatar
Hans Muller committed
171
      center = new Column(
172 173
        mainAxisAlignment: MainAxisAlignment.collapse,
        crossAxisAlignment: CrossAxisAlignment.start,
Hans Muller's avatar
Hans Muller committed
174 175
        children: <Widget>[
          primaryLine,
176
          new AnimatedDefaultTextStyle(
177
            style: _secondaryTextStyle(context),
178
            duration: kThemeChangeDuration,
179
            child: subtitle
Hans Muller's avatar
Hans Muller committed
180 181 182 183
          )
        ]
      );
    }
Adam Barth's avatar
Adam Barth committed
184 185 186 187
    children.add(new Flexible(
      child: center
    ));

188
    if (trailing != null) {
Adam Barth's avatar
Adam Barth committed
189
      children.add(new Container(
190
        margin: const EdgeInsets.only(left: 16.0),
Hans Muller's avatar
Hans Muller committed
191
        child: new Align(
192
          alignment: FractionalOffset.centerRight,
193
          child: trailing
Hans Muller's avatar
Hans Muller committed
194
        )
Adam Barth's avatar
Adam Barth committed
195 196 197
      ));
    }

198
    return new InkWell(
199 200
      onTap: enabled ? onTap : null,
      onLongPress: enabled ? onLongPress : null,
Hans Muller's avatar
Hans Muller committed
201 202
      child: new Container(
        height: itemHeight,
203
        padding: const EdgeInsets.symmetric(horizontal: 16.0),
Hans Muller's avatar
Hans Muller committed
204
        child: new Row(
205
          crossAxisAlignment: CrossAxisAlignment.center,
Hans Muller's avatar
Hans Muller committed
206 207
          children: children
        )
Adam Barth's avatar
Adam Barth committed
208 209 210 211
      )
    );
  }
}