Кнопка Flutter DropDown для настроек аккаунта - PullRequest
0 голосов
/ 20 февраля 2020

Хотите создать собственную выпадающую кнопку для настроек учетной записи с возможностью выхода из системы. Как изменить цвет фона выпадающего списка? Как настроить выпадающий список?

 DropdownButton(
          hint: ButtonText(
            buttonText: "Select a option",
            textColor: ThemeColors.grey8,
            buttonFontSize: 20.0,
          ),
          value: _selectedOption,
          onChanged: (value) {
            setState(() {
              _selectedOption = value;
            });
          },
          items: _options.map((option) {
            return DropdownMenuItem(
              child: ButtonText(
                buttonText: option,
                buttonFontSize: 16.0,
                textColor: ThemeColors.grey8,
              ),
              value: option,
            );
          }).toList(),

Я хочу создать собственную выпадающую кнопку с панелью учетной записи. Want a drop down button like this

1 Ответ

0 голосов
/ 20 февраля 2020

У меня была похожая ситуация. Мне нужно было реализовать раскрывающийся диалог. Я использовал источники DropdownButton и изменил их.

Мой виджет:

import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';

const double _hintWidgetHeight = 34;
const double _hintWidgetWidth = 140;

class MyHintWidget extends StatefulWidget {
  const MyHintWidget({
    Key key,
    @required this.title,
    @required this.style,
    @required this.child,
  }) : super(key: key);

  final String title;
  final TextStyle style;
  final Widget child;

  @override
  _MyHintState createState() => _MyHintState();
}

class _MyHintState extends State<MyHintWidget> with WidgetsBindingObserver {
  static const EdgeInsets _hintItemPadding = EdgeInsets.symmetric(horizontal: 16.0);
  _HintRoute route;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _removeRoute();
    super.dispose();
  }

  @override
  void didChangeMetrics() {
    _removeRoute();
  }

  void _removeRoute() {
    route?._dismiss();
    route = null;
  }

  void _handleTap() {
    final RenderBox itemBox = context.findRenderObject() as RenderBox;
    final Rect itemRect = itemBox.localToGlobal(Offset.zero) & itemBox.size;
    final TextDirection textDirection = Directionality.of(context);
    route = _HintRoute(
      title: widget.title,
      style: widget.style,
      buttonRect: _hintItemPadding.resolve(textDirection).inflateRect(itemRect),
      padding: _hintItemPadding.resolve(textDirection),
      theme: ThemeData(
        brightness: Brightness.light,
      ),
      barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
    );
    Navigator.push(context, route).then<void>((_) {
      route = null;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Semantics(
      button: true,
      child: GestureDetector(
        onTap: _handleTap,
        behavior: HitTestBehavior.opaque,
        child: widget.child,
      ),
    );
  }
}

class _HintRoute extends PopupRoute<void> {
  _HintRoute({
    @required this.title,
    @required this.style,
    this.padding,
    this.buttonRect,
    this.theme,
    this.barrierLabel,
  });

  final String title;
  final TextStyle style;
  final EdgeInsetsGeometry padding;
  final Rect buttonRect;
  final ThemeData theme;

  @override
  Duration get transitionDuration => const Duration(milliseconds: 300);

  @override
  bool get barrierDismissible => true;

  @override
  Color get barrierColor => null;

  @override
  final String barrierLabel;

  @override
  Widget buildPage(
    BuildContext context,
    Animation<double> animation,
    Animation<double> secondaryAnimation,
  ) {
    return LayoutBuilder(builder: (BuildContext context, BoxConstraints constraints) {
      return _HintRoutePage(
        title: title,
        style: style,
        route: this,
        constraints: constraints,
        padding: padding,
        buttonRect: buttonRect,
        theme: theme,
      );
    });
  }

  void _dismiss() => navigator?.removeRoute(this);
}

class _HintRoutePage extends StatelessWidget {
  const _HintRoutePage({
    Key key,
    @required this.title,
    @required this.style,
    this.route,
    this.constraints,
    this.padding,
    this.buttonRect,
    this.theme,
  }) : super(key: key);

  final String title;
  final TextStyle style;
  final _HintRoute route;
  final BoxConstraints constraints;
  final EdgeInsetsGeometry padding;
  final Rect buttonRect;
  final ThemeData theme;

  @override
  Widget build(BuildContext context) {
    assert(debugCheckHasDirectionality(context));
    final double availableHeight = constraints.maxHeight;
    final double maxHintHeight = availableHeight - 2.0 * _hintWidgetHeight;

    final double buttonTop = buttonRect.top;
    final double buttonBottom = math.min(buttonRect.bottom, availableHeight);

    final double topLimit = math.min(_hintWidgetHeight, buttonTop);
    final double bottomLimit = math.max(availableHeight - _hintWidgetHeight, buttonBottom);

    final double selectedItemOffset = _hintWidgetHeight + kMaterialListPadding.top;

    double hintTop =
        (buttonTop - selectedItemOffset) - (_hintWidgetHeight * 2 - buttonRect.height) / 3;
    final double preferredHintHeight = _hintWidgetHeight + kMaterialListPadding.vertical;

    final double hintHeight = math.min(maxHintHeight, preferredHintHeight);

    double hintBottom = hintTop + hintHeight;

    if (hintTop < topLimit) hintTop = math.min(buttonTop, topLimit);
    if (hintBottom > bottomLimit) {
      hintBottom = math.max(buttonBottom, bottomLimit);
      hintTop = hintBottom - hintHeight;
    }

    final TextDirection textDirection = Directionality.of(context);

    Widget hint = _HintWidget(
      title: title,
      style: style,
      route: route,
      padding: padding.resolve(textDirection),
    );

    if (theme != null) hint = Theme(data: theme, child: hint);

    return MediaQuery.removePadding(
      context: context,
      removeTop: true,
      removeBottom: true,
      removeLeft: true,
      removeRight: true,
      child: Builder(
        builder: (BuildContext context) {
          return CustomSingleChildLayout(
            delegate: _HintRouteLayout(
              buttonRect: buttonRect,
              hintTop: hintTop,
              hintHeight: hintHeight,
              textDirection: textDirection,
            ),
            child: hint,
          );
        },
      ),
    );
  }
}

class _HintRouteLayout extends SingleChildLayoutDelegate {
  _HintRouteLayout({
    @required this.buttonRect,
    @required this.hintTop,
    @required this.hintHeight,
    @required this.textDirection,
  });

  final Rect buttonRect;
  final double hintTop;
  final double hintHeight;
  final TextDirection textDirection;

  @override
  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    final double maxHeight = math.max(0.0, constraints.maxHeight - 2 * _hintWidgetHeight);
    const double width = _hintWidgetWidth;
    return BoxConstraints(
      minWidth: width,
      maxWidth: width,
      minHeight: 0.0,
      maxHeight: maxHeight,
    );
  }

  @override
  Offset getPositionForChild(Size size, Size childSize) {
    assert(() {
      final Rect container = Offset.zero & size;
      if (container.intersect(buttonRect) == buttonRect) {
        assert(hintTop >= 0.0);
        assert(hintTop + hintHeight <= size.height);
      }
      return true;
    }());
    assert(textDirection != null);
    double left;
    if (buttonRect.left > size.width - childSize.width / 1.1) {
      left = size.width - childSize.width;
    } else if (buttonRect.left < childSize.width / 5) {
      left = 0;
    } else {
      left = buttonRect.left - (buttonRect.right - buttonRect.left) / 3;
    }
    return Offset(left, hintTop);
  }

  @override
  bool shouldRelayout(_HintRouteLayout oldDelegate) {
    return buttonRect != oldDelegate.buttonRect ||
        hintTop != oldDelegate.hintTop ||
        hintHeight != oldDelegate.hintHeight ||
        textDirection != oldDelegate.textDirection;
  }
}

class _HintWidget extends StatefulWidget {
  const _HintWidget({
    Key key,
    @required this.title,
    @required this.style,
    this.padding,
    this.route,
  }) : super(key: key);

  final String title;
  final TextStyle style;
  final _HintRoute route;
  final EdgeInsets padding;

  @override
  _HintState createState() => _HintState();
}

class _HintState extends State<_HintWidget> {
  CurvedAnimation _fadeOpacity;
  CurvedAnimation _resize;

  @override
  void initState() {
    super.initState();
    _fadeOpacity = CurvedAnimation(
      parent: widget.route.animation,
      curve: const Interval(0.0, 0.25),
      reverseCurve: const Interval(0.75, 1.0),
    );
    _resize = CurvedAnimation(
      parent: widget.route.animation,
      curve: const Interval(0.25, 0.5),
      reverseCurve: const Threshold(0.0),
    );
  }

  @override
  Widget build(BuildContext context) {
    final MaterialLocalizations localizations = MaterialLocalizations.of(context);
    final _HintRoute route = widget.route;
    const double unit = 0.5 / 1.5;
    CurvedAnimation opacity;
    final double start = (0.5 + 1 * unit).clamp(0.0, 1.0) as double;
    final double end = (start + 1.5 * unit).clamp(0.0, 1.0) as double;
    opacity = CurvedAnimation(parent: route.animation, curve: Interval(start, end));

    return FadeTransition(
      opacity: _fadeOpacity,
      child: CustomPaint(
        painter: _HintPainter(
          color: Theme.of(context).canvasColor,
          elevation: 8,
          resize: _resize,
        ),
        child: Semantics(
          scopesRoute: true,
          namesRoute: true,
          explicitChildNodes: true,
          label: localizations.popupMenuLabel,
          child: Material(
            type: MaterialType.transparency,
            child: Padding(
              padding: kMaterialListPadding,
              child: FadeTransition(
                opacity: opacity,
                child: InkWell(
                  onTap: () => Navigator.pop(context),
                  child: Container(
                    padding: widget.padding,
                    child: Text(
                      widget.title,
                      style: widget.style,
                      textAlign: TextAlign.center,
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _HintPainter extends CustomPainter {
  _HintPainter({
    this.color,
    this.elevation,
    this.resize,
  })  : _painter = BoxDecoration(
          color: color,
          borderRadius: BorderRadius.circular(6.0),
          boxShadow: kElevationToShadow[elevation],
        ).createBoxPainter(),
        super(repaint: resize);

  final Color color;
  final int elevation;
  final Animation<double> resize;
  final BoxPainter _painter;

  @override
  void paint(Canvas canvas, Size size) {
    final double selectedItemOffset = _hintWidgetHeight + kMaterialListPadding.top;
    final Tween<double> top = Tween<double>(
      begin: selectedItemOffset.clamp(0.0, size.height - _hintWidgetHeight) as double,
      end: 0.0,
    );
    final Tween<double> bottom = Tween<double>(
      begin: (top.begin + _hintWidgetHeight).clamp(_hintWidgetHeight, size.height) as double,
      end: size.height,
    );
    final Rect rect = Rect.fromLTRB(0.0, top.evaluate(resize), size.width, bottom.evaluate(resize));
    _painter.paint(canvas, rect.topLeft, ImageConfiguration(size: rect.size));
  }

  @override
  bool shouldRepaint(_HintPainter oldPainter) {
    return oldPainter.color != color ||
        oldPainter.elevation != elevation ||
        oldPainter.resize != resize;
  }
}

Итак, он используется:

return MyHintWidget(
  title: 'some text',
  style: //text style[![enter image description here][1]][1],
  child: //child,
);

Результат

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...