Flutter) 접고 펼치는 위젯

Sang·2023년 11월 18일
0

Flutter

목록 보기
2/3
post-custom-banner

작업에 필요한 접고 펼치는 타일을 찾다보니 Flutter에서 제공하는 ExpansionTile을 찾게 되었는데 아쉽게도 우측의 화살표가 너무 거슬렸다.

그래서 ExpansionTile을 그대로 Custom 해보기로 했다.

AnimationController을 생성할때 value에 1 또는 0을 넣도록하였는데 위젯이 생성될때에 펼쳐져있는 상태로 생성할지 닫혀져있는 상태로 생성할지를 뜻한다.

아래의 이미지는 AnimationController에 lowerBound, upperBound가 있는데 따로 값을 설정하지 않으면 0과 1로 설정이 되어있다.
더 아래에 보면 _internalSetValue 가 있는데 초기값 설정을 value로 설정을하거나 value가 null인 경우 lowerBound로 설정하는것으로 되어있다.

import 'package:flutter/material.dart';
import 'package:flutter_app/presentations/components/default_button.dart';

const Duration _kExpand = Duration(milliseconds: 200);

class ExpandableTile extends StatefulWidget {
  final Widget titleWidget;
  final Widget childWidget;
  final EdgeInsets? tilePadding;
  final ValueChanged<bool>? onExpansionChanged;
  final Widget? trailing;
  final bool isExpanded;

  const ExpandableTile({
    super.key,
    required this.titleWidget,
    required this.childWidget,
    this.onExpansionChanged,
    this.trailing,
    this.tilePadding,
    this.isExpanded = false,
  });

  @override
  State<ExpandableTile> createState() => _ExpandableTileState();
}

class _ExpandableTileState extends State<ExpandableTile> with SingleTickerProviderStateMixin {
  late bool _isExpanded;

  static final Animatable<double> _easeInTween = CurveTween(curve: Curves.easeIn);
  late AnimationController _animationController;
  late Animation<double> _heightFactor;

  @override
  void initState() {
    super.initState();
    _isExpanded = widget.isExpanded;
    _animationController = AnimationController(duration: _kExpand, vsync: this, value: _isExpanded ? 1 : 0);
    _heightFactor = _animationController.drive(_easeInTween);
  }

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

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

  Widget _buildChildren(BuildContext context, Widget? child) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: <Widget>[
        DefaultButton(
          padding: widget.tilePadding,
          childWidget: widget.titleWidget,
          onPressed: _handleTap,
        ),
        ClipRect(
          child: Align(
            alignment: Alignment.center,
            heightFactor: _heightFactor.value,
            child: child,
          ),
        ),
      ],
    );
  }

  @override
  Widget build(BuildContext context) {
    final bool closed = !_isExpanded && _animationController.isDismissed;
    final bool shouldRemoveChildren = closed;

    final Widget result = Offstage(
      offstage: closed,
      child: TickerMode(
        enabled: !closed,
        child: widget.childWidget,
      ),
    );

    return AnimatedBuilder(
      animation: _animationController.view,
      builder: _buildChildren,
      child: shouldRemoveChildren ? null : result,
    );
  }
}
profile
Mobile App Develop
post-custom-banner

0개의 댓글