Flutter 自定义ExpansionTitle

      最近写一个可伸缩的二级展开列表,网上搜了下基本是用ExpansionTitle 来实现,但是有一个问题是标题栏部分可自定义的样式有点少,而且高度还不可以调,怎么办,只能自定义了。

    偶然间发现有一位大佬的博客:地址 Flutter初探--半自定义ExpansionTile - 简书,是通过半自定义方式,增加可自定义属性,在他的基础上,修改了下,让标题栏部分完全自定义而不使用其自带的属性,具体代码如下:

import 'package:flutter/material.dart';

///自定义的listTitle,支持自定义标题栏布局
const Duration _kExpand = Duration(milliseconds: 200);

// 分割线显示时机
enum DividerDisplayTime {
  always, //总是显示
  opened, //展开时显示
  closed, //关闭时显示
  never //不显示
}

class UserExpansionTile extends StatefulWidget {
  const UserExpansionTile({
    Key key,
    this.leading,
    @required this.title,
    this.backgroundColor,
    this.dividerColor,
    this.iconColor,
    this.dividerDisplayTime,
    this.onExpansionChanged,
    this.children = const <Widget>[],
    this.trailing,
    this.initiallyExpanded = false,
  })  : assert(initiallyExpanded != null),
        super(key: key);

  final Widget leading;

  final Widget title;

  /// Called when the tile expands or collapses.
  ///
  /// When the tile starts expanding, this function is called with the value
  /// true. When the tile starts collapsing, this function is called with
  /// the value false.
  final ValueChanged<bool> onExpansionChanged;

  /// The widgets that are displayed when the tile expands.
  ///
  /// Typically [ListTile] widgets.
  final List<Widget> children;

  /// The color to display behind the sublist when expanded.
  final Color backgroundColor;

  /// A widget to display instead of a rotating arrow icon.
  final Widget trailing;

  /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default).
  final bool initiallyExpanded;

  final Color dividerColor;

  final DividerDisplayTime dividerDisplayTime;

  final Color iconColor;

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

class _UserExpansionTileState extends State<UserExpansionTile>
    with SingleTickerProviderStateMixin {
  static final Animatable<double> _easeOutTween =
      CurveTween(curve: Curves.easeOut);
  static final Animatable<double> _easeInTween =
      CurveTween(curve: Curves.easeIn);
  static final Animatable<double> _halfTween =
      Tween<double>(begin: 0.0, end: 0.5);

  final ColorTween _borderColorTween = ColorTween();
  final ColorTween _headerColorTween = ColorTween();
  final ColorTween _iconColorTween = ColorTween();
  final ColorTween _backgroundColorTween = ColorTween();

  AnimationController _controller;
  Animation<double> _iconTurns;
  Animation<double> _heightFactor;
  Animation<Color> _borderColor;
  Animation<Color> _headerColor;
  Animation<Color> _iconColor;
  Animation<Color> _backgroundColor;

  bool _isExpanded = false;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: _kExpand, vsync: this);
    _heightFactor = _controller.drive(_easeInTween);
    _iconTurns = _controller.drive(_halfTween.chain(_easeInTween));
    _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween));
    _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween));
    _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween));
    _backgroundColor =
        _controller.drive(_backgroundColorTween.chain(_easeOutTween));

    _isExpanded =
        PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded;
    if (_isExpanded) _controller.value = 1.0;
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void _handleTap() {
    setState(() {
      _isExpanded = !_isExpanded;
      if (_isExpanded) {
        _controller.forward();
      } else {
        _controller.reverse().then<void>((void value) {
          if (!mounted) return;
          setState(() {
            // Rebuild without widget.children.
          });
        });
      }
      PageStorage.of(context)?.writeState(context, _isExpanded);
    });
    if (widget.onExpansionChanged != null)
      widget.onExpansionChanged(_isExpanded);
  }

  Widget _buildChildren(BuildContext context, Widget child) {
    final Color borderSideColor = _borderColor.value ?? Colors.transparent;
    return Container(
      alignment: Alignment.centerLeft,
      decoration: BoxDecoration(
        color: Colors.white,
        border: Border(
          bottom: BorderSide(color: borderSideColor),
        ),
      ),
      child: Column(
        children:[
          GestureDetector(
            child:   widget.title,
            onTap: _handleTap,
          ),

          ClipRect(
            child: Align(
              heightFactor: _heightFactor.value,
              child: child,
            ),
          ),
        ],
      ),
    );
  }

  @override
  void didChangeDependencies() {
    setupDidvierColorTween();

    setupIconColorTween();

    setupBackgroundColor();

    super.didChangeDependencies();
  }

  void setupDidvierColorTween() {
    final ThemeData theme = Theme.of(context);

    Color beginColor = this.widget.dividerColor ?? theme.dividerColor;
    Color endColor = beginColor;

    switch (widget.dividerDisplayTime) {
      case DividerDisplayTime.always:
        break;
      case DividerDisplayTime.opened:
        endColor = Colors.transparent;
        break;
      case DividerDisplayTime.closed:
        beginColor = Colors.transparent;
        break;
      case DividerDisplayTime.never:
        beginColor = Colors.transparent;
        endColor = Colors.transparent;
        break;
      default:
    }
    _borderColorTween
      ..begin = beginColor
      ..end = endColor;
  }

  void setupIconColorTween() {
    final ThemeData theme = Theme.of(context);

    Color beginColor = this.widget.iconColor ?? theme.unselectedWidgetColor;
    Color endColor = beginColor;

    _iconColorTween
      ..begin = beginColor
      ..end = endColor;
  }

  void setupBackgroundColor() {
    _backgroundColorTween
      ..begin = widget.backgroundColor
      ..end = widget.backgroundColor;
  }

  @override
  Widget build(BuildContext context) {
    final bool closed = !_isExpanded && _controller.isDismissed;
    return AnimatedBuilder(
      animation: _controller.view,
      builder: _buildChildren,
      child: closed ? null : Column(children: widget.children),
    );
  }
}

使用方式:

  ///子项view
  Widget _item(CourseEntity courseEntity, CourseModel model) {
    return UserExpansionTile(
      backgroundColor: Colors.white,//背景色
      initiallyExpanded: true,//是否默认展开
      title: Container(),//标题栏部分布局,完全自定义你自己的头部布局
      dividerColor: Colors.white,//未展开时分割线颜色
      dividerDisplayTime: DividerDisplayTime.always,//分割线展示时机
      children: [],//展开的子列表布局
    );
  }

记录一下,也给其他小伙伴提供下方便。运行样例效果:

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值