Flutter ExpansionPanelList


简介

所有chip widget类型的基础,集合所有功能,我们通常不是直接使用它,而是根据自己需要选择chi, 比如:

  • chip ,一个简单的芯片,只能显示信息,并被删除
  • inputChip,以紧凑的形式表现复杂的信息,例如:实体(人,地点,或者事物)或者会话文本
  • choiceChip,允许从一组选项中进行单一的选择
  • FilterChip,使用标签或描述作为过滤内容的方式

基本用法

  • inputChip可以通过设置进行选择onSelected,通过设置onDeleted可以删除,并且可以通过OnPressed表现按压效果
  • inputChip 有一个前导图标和尾随图标,填充颜色可以订制
  • inputChip 可以和其他UI元素搭配使用,比如:wrap,ListView(scrollDirection为Axis.horizo​​ntal)

实例演示

import 'package:flutter/material.dart';


@visibleForTesting
enum Location {
  Barbados,
  Bahamas,
  Bermuda
}

typedef DemoItemBodyBuilder<T> = Widget Function(DemoItem<T> item);
typedef ValueToString<T> = String Function(T value);

/*
 * google flutter 详情多个例子地址:
 * https://github.com/flutter/flutter/blob/master/examples/flutter_gallery/lib/demo/material/expansion_panels_demo.dart
 */
class DualHeaderWithHint extends StatelessWidget {
  const DualHeaderWithHint({
    this.name,
    this.value,
    this.hint,
    this.showHint
  });

  final String name;
  final String value;
  final String hint;
  final bool showHint;

  Widget _crossFade(Widget first, Widget second, bool isExpanded) {
    return AnimatedCrossFade(
      firstChild: first,
      secondChild: second,
      firstCurve: const Interval(0.0, 0.6, curve: Curves.fastOutSlowIn),
      secondCurve: const Interval(0.4, 1.0, curve: Curves.fastOutSlowIn),
      sizeCurve: Curves.fastOutSlowIn,
      crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
      duration: const Duration(milliseconds: 200),
    );
  }

  @override
  Widget build(BuildContext context) {
    final ThemeData theme = Theme.of(context);
    final TextTheme textTheme = theme.textTheme;

    return Row(
      children: <Widget>[
        Expanded(
          flex: 2,
          child: Container(
            margin: const EdgeInsets.only(left: 24.0),
            child: FittedBox(
              fit: BoxFit.scaleDown,
              alignment: Alignment.centerLeft,
              child: Text(
                name,
                style: textTheme.body1.copyWith(fontSize: 15.0),
              ),
            ),
          ),
        ),
        Expanded(
          flex: 3,
          child: Container(
            margin: const EdgeInsets.only(left: 24.0),
            child: _crossFade(
              Text(value, style: textTheme.caption.copyWith(fontSize: 15.0)),
              Text(hint, style: textTheme.caption.copyWith(fontSize: 15.0)),
              showHint
            )
          )
        )
      ]
    );
  }
}


class DemoItem<T> {
  DemoItem({
    this.name,
    this.value,
    this.hint,
    this.builder,
    this.valueToString
  }) : textController = TextEditingController(text: valueToString(value));

  final String name;
  final String hint;
  final TextEditingController textController;
  final DemoItemBodyBuilder<T> builder;
  final ValueToString<T> valueToString;
  T value;
  bool isExpanded = true;

  ExpansionPanelHeaderBuilder get headerBuilder {
    return (BuildContext context, bool isExpanded) {
      return DualHeaderWithHint(
        name: name,
        value: valueToString(value),
        hint: hint,
        showHint: isExpanded
      );
    };
  }

  Widget build() => builder(this);
}

class ExpansionPanelsDemo extends StatefulWidget {
  // static const String routeName = '/material/expansion_panels';

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

class _ExpansionPanelsDemoState extends State<ExpansionPanelsDemo> {
  List<DemoItem<dynamic>> _demoItems;

  @override
  void initState() {
    super.initState();

    _demoItems = <DemoItem<dynamic>>[

      DemoItem<Location>(
        name: 'Location',
        value: Location.Bahamas,
        hint: 'Select location',
        valueToString: (Location location) => location.toString().split('.')[1],
        builder: (DemoItem<Location> item) {
          return Form(
            child: Builder(
              builder: (BuildContext context) {
                return FormField<Location>(
                    initialValue: item.value,
                    onSaved: (Location result) { item.value = result; },
                    builder: (FormFieldState<Location> field) {
                      return Column(
                        mainAxisSize: MainAxisSize.min,
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: <Widget>[
                          RadioListTile<Location>(
                            value: Location.Bahamas,
                            title: const Text('Bahamas'),
                            groupValue: field.value,
                            onChanged: field.didChange,
                          ),
                          RadioListTile<Location>(
                            value: Location.Barbados,
                            title: const Text('Barbados'),
                            groupValue: field.value,
                            onChanged: field.didChange,
                          ),
                          RadioListTile<Location>(
                            value: Location.Bermuda,
                            title: const Text('Bermuda'),
                            groupValue: field.value,
                            onChanged: field.didChange,
                          ),
                        ]
                      );
                    }
                  );
              }
            )
          );
        }
      ),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return  SingleChildScrollView(
        child: SafeArea(
          top: false,
          bottom: false,
          child: Container(
            margin: const EdgeInsets.all(24.0),
            child: ExpansionPanelList(
              expansionCallback: (int index, bool isExpanded) {
                setState(() {
                  _demoItems[index].isExpanded = !isExpanded;
                });
              },
              children: _demoItems.map<ExpansionPanel>((DemoItem<dynamic> item) {
                return ExpansionPanel(
                  isExpanded: true,
                  headerBuilder: item.headerBuilder,
                  body: item.build()
                );
              }).toList()
            ),
          ),
        ),
      );
  }
}