You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter横向展开组件关闭时溢出问题求助及实现方案咨询

Flutter横向展开组件布局溢出问题

我正在开发横向展开组件,需求如下:

  • 初始状态下两个Widget平分一行空间
  • 点击任意组件时,该组件展开,另一个组件隐藏
  • 当前关闭展开的组件时,出现布局溢出问题

尝试过的方案:

  • 考虑使用Hero Widget,但不清楚如何在不跳转路由的场景下实现
  • 可以通过Builder实现动画,但该方式过于繁琐,不够便捷

相关代码:

import 'package:flutter/material.dart';

void main() {
  runApp(const AccountSecurityPage());
}

class AccountSecurityPage extends StatefulWidget {
  const AccountSecurityPage({super.key});

  @override
  State<AccountSecurityPage> createState() => _AccountSecurityPageState();
}
enum SelectedField { email, psw }
class _AccountSecurityPageState extends State<AccountSecurityPage> {
  SelectedField? field;
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        backgroundColor: Colors.white,
        appBar: AppBar(title: const Text("Account Security")),
        body: Column(
          children: [
            const Column(
              children: [
                Row(
                  children: [
                    Text(
                      "Two-factor authentication",
                      style: TextStyle(fontWeight: FontWeight.w700),
                    ),
                  ],
                ),
                Wrap(
                  children: [
                    Text(
                        "To protect your account, we recommend enabling at least one type of two-factor authentication")
                  ],
                )
              ],
            ),
            Row(
              mainAxisSize: MainAxisSize.max,
              children: [
                //SliderAppScreen(),
                HorizontalExpandedField<SelectedField>(
                  value: SelectedField.email,
                  groupValue: field,
                  onChanged: (SelectedField? val) {
                    if (field != SelectedField.email) {
                      setState(() {
                        field = SelectedField.email;
                      });
                    } else {
                      setState(() {
                        field = null;
                      });
                    }
                  },
                ),

                HorizontalExpandedField<SelectedField>(
                  value: SelectedField.psw,
                  groupValue: field,
                  onChanged: (SelectedField? val) {
                    if (field != SelectedField.psw) {
                      setState(() {
                        field = SelectedField.psw;
                      });
                    } else {
                      setState(() {
                        field = null;
                      });
                    }
                  },
                ),
              ],
            )
          ],
        ),
      ),
    );
  }
}

class HorizontalExpandedField<T> extends StatefulWidget {
  final T value;
  final T? groupValue;
  final ValueChanged<T?> onChanged;
  const HorizontalExpandedField({
    super.key,
    required this.value,
    required this.groupValue,
    required this.onChanged,
  });

  @override
  State<HorizontalExpandedField<T>> createState() =>
      _HorizontalExpandedFieldState();
}

class _HorizontalExpandedFieldState<T> extends State<HorizontalExpandedField<T>>
    with TickerProviderStateMixin {
  late final AnimationController _expandedController;
  late final AnimationController _scaleContainerController;
  late Animation _expandedAnimation;
  late final Animation<double> _scaleContainerAnimation;

  @override
  initState() {
    _scaleContainerController = AnimationController(
        duration: const Duration(milliseconds: 200), vsync: this);

    _scaleContainerAnimation = CurvedAnimation(
      parent:
          Tween<double>(begin: 0.0, end: 1).animate(_scaleContainerController),
      curve: Curves.easeOut,
    );
    super.initState();

    _expandedController = AnimationController(
        duration: const Duration(milliseconds: 300), vsync: this);
    _expandedAnimation =
        IntTween(begin: 1, end: 20).animate(_expandedController);

    _expandedAnimation.addListener(() => setState(() {}));
    _scaleContainerController.addListener(() => setState(() {}));
  }

  @override
  dispose() {
    _scaleContainerController.dispose();
    _expandedController.dispose();
    super.dispose();
  }

  void _handleChanged(bool selected) {
    if (!selected) {
      _expandedController
          .forward()
          .then((value) => _scaleContainerController.forward());
      widget.onChanged(widget.value);
    } else {
      if (!isCollapse) {
        _scaleContainerController
            .reverse()
            .then((value) => _expandedController.reverse());
      }
      widget.onChanged(null);
    }

    return;
  }

  bool get _isSelected => widget.value == widget.groupValue;

  bool get isCollapse => !_isSelected && widget.groupValue != null;
  @override
  Widget build(BuildContext context) {
    return Expanded(
      flex: _expandedAnimation.value,
      child: Padding(
        padding: const EdgeInsets.all(8.0),
        child: MaterialButton(
          clipBehavior: Clip.hardEdge,
          onPressed: () {
            _handleChanged(_isSelected);
          },
          color: const Color(0xFFF8F8F8),
          elevation: 0,
          padding: const EdgeInsets.all(16),
          child: Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                crossAxisAlignment: _isSelected
                    ? CrossAxisAlignment.start
                    : CrossAxisAlignment.center,
                children: [
                  Expanded(
                    child: Flex(
                      clipBehavior: Clip.hardEdge,
                      direction: _isSelected ? Axis.horizontal : Axis.vertical,
                      crossAxisAlignment: CrossAxisAlignment.center,
                      children: const <Widget>[
                        Icon(Icons.email_rounded),
                        Text(softWrap: false, "email"),
                      ],
                    ),
                  ),
                  const Expanded(child: Icon(Icons.expand_circle_down)),
                ],
              ),
              //if (_isSelected && !isCollapse)
              ScaleTransition(
                scale: _scaleContainerAnimation,
                alignment: Alignment.centerLeft,
                child: Container(
                  decoration: const BoxDecoration(
                    color: Colors.red,
                  ),
                  clipBehavior: Clip.hardEdge,
                  height: _scaleContainerAnimation.value * 50,
                  width: double.infinity,
                  child: Column(
                    children: [
                      Expanded(
                        child: Row(
                          children: [
                            Text(softWrap: false, "email"),
                            Expanded(child: TextField()),
                          ],
                        ),
                      ),
                    ],
                  ),
                ),
              )
            ],
          ),
        ),
      ),
    );
  }
}

内容的提问来源于stack exchange,提问作者kannt_im

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 23:55:00