• Greg Spencer's avatar
    Adding proper accommodation for textScaleFactor in chips, and StadiumBorder border. (#12533) · 05e10633
    Greg Spencer authored
    In order to allow chips to be properly drawn when they expand in size (without
    using IntrinsicHeight), I needed a BoxDecoration shape that would be dependent
    upon the rendered height of the widget. This seemed to be pretty generally
    useful, so I added a new ShapeDecoration called StadiumBorder. It uses the
    minimum dimension to adjust the BorderRadius of a rounded rect in the shape
    decoration.
    
    I also converted some uses of BoxShape to be case statements, updated the
    chips to use the StadiumBorder decoration, and updated some of the metrics to match
    the Material spec, as well as implementing lerping to and from StadiumBorder.
    05e10633
chip.dart 5.14 KB
// 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/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/painting.dart';

import 'colors.dart';
import 'debug.dart';
import 'feedback.dart';
import 'icons.dart';
import 'tooltip.dart';

/// A material design chip.
///
/// Chips represent complex entities in small blocks, such as a contact.
///
/// Supplying a non-null [onDeleted] callback will cause the chip to include a
/// button for deleting the chip.
///
/// Requires one of its ancestors to be a [Material] widget. The [label]
/// and [border] arguments must not be null.
///
/// ## Sample code
///
/// ```dart
/// new Chip(
///   avatar: new CircleAvatar(
///     backgroundColor: Colors.grey.shade800,
///     child: new Text('AB'),
///   ),
///   label: new Text('Aaron Burr'),
/// )
/// ```
///
/// See also:
///
///  * [CircleAvatar], which shows images or initials of people.
///  * <https://material.google.com/components/chips.html>
class Chip extends StatelessWidget {
  /// Creates a material design chip.
  ///
  ///  * [onDeleted] determines whether the chip has a delete button. This
  ///    callback runs when the delete button is pressed.
  const Chip({
    Key key,
    this.avatar,
    @required this.label,
    this.onDeleted,
    TextStyle labelStyle,
    this.deleteButtonTooltipMessage,
    this.backgroundColor,
    this.deleteIconColor,
    this.border: const StadiumBorder(),
  }) : assert(label != null),
       assert(border != null),
       labelStyle = labelStyle ?? _defaultLabelStyle,
       super(key: key);

  static const TextStyle _defaultLabelStyle = const TextStyle(
    inherit: false,
    fontSize: 13.0,
    fontWeight: FontWeight.w400,
    color: Colors.black87,
    textBaseline: TextBaseline.alphabetic,
  );

  static const double _chipHeight = 32.0;

  /// A widget to display prior to the chip's label.
  ///
  /// Typically a [CircleAvatar] widget.
  final Widget avatar;

  /// The primary content of the chip.
  ///
  /// Typically a [Text] widget.
  final Widget label;

  /// Called when the user deletes the chip, e.g., by tapping the delete button.
  ///
  /// The delete button is included in the chip only if this callback is non-null.
  final VoidCallback onDeleted;

  /// The style to be applied to the chip's label.
  ///
  /// This only has effect on widgets that respect the [DefaultTextStyle],
  /// such as [Text].
  final TextStyle labelStyle;

  /// Color to be used for the chip's background, the default being grey.
  ///
  /// This color is used as the background of the container that will hold the
  /// widget's label.
  final Color backgroundColor;

  /// The border to draw around the chip.
  ///
  /// Defaults to a [StadiumBorder].
  final ShapeBorder border;

  /// Color for delete icon, the default being black.
  ///
  /// This has no effect when [onDelete] is null since no delete icon will be
  /// shown.
  final Color deleteIconColor;

  /// Message to be used for the chip delete button's tooltip.
  ///
  /// This has no effect when [onDelete] is null since no delete icon will be
  /// shown.
  final String deleteButtonTooltipMessage;

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasMaterial(context));
    final bool deletable = onDeleted != null;
    double startPadding = 12.0;
    double endPadding = 12.0;

    final List<Widget> children = <Widget>[];

    if (avatar != null) {
      startPadding = 0.0;
      children.add(new ExcludeSemantics(
        child: new Container(
          margin: const EdgeInsetsDirectional.only(end: 8.0),
          width: _chipHeight,
          height: _chipHeight,
          child: avatar,
        ),
      ));
    }

    children.add(new Flexible(
      child: new DefaultTextStyle(
        overflow: TextOverflow.ellipsis,
        style: labelStyle,
        child: label,
      ),
    ));

    if (deletable) {
      endPadding = 0.0;
      children.add(new GestureDetector(
        onTap: Feedback.wrapForTap(onDeleted, context),
        child: new Tooltip(
          // TODO(gspencer): Internationalize this text.
          // https://github.com/flutter/flutter/issues/12378
          message: deleteButtonTooltipMessage ?? 'Delete "$label"',
          child: new Container(
            padding: const EdgeInsets.symmetric(horizontal: 4.0),
            child: new Icon(
              Icons.cancel,
              size: 24.0,
              color: deleteIconColor ?? Colors.black54,
            ),
          ),
        ),
      ));
    }

    return new Semantics(
      container: true,
      child: new Container(
        constraints: const BoxConstraints(minHeight: _chipHeight),
        padding: new EdgeInsetsDirectional.only(start: startPadding, end: endPadding),
        decoration: new ShapeDecoration(
          color: backgroundColor ?? Colors.grey.shade300,
          shape: border,
        ),
        child: new Center(
          widthFactor: 1.0,
          heightFactor: 1.0,
          child: new Row(
            children: children,
            mainAxisSize: MainAxisSize.min,
          ),
        ),
      ),
    );
  }
}