Flutter实现StackView

18 篇文章 0 订阅

1.让界面之间可以嵌套且执行动画。

2.界面的添加遵循先进后出原则。

3.需要使用AnimateView,请看我上一篇博客。

演示:

代码:

Stack:

import 'package:flutter/cupertino.dart';

///栈,先进后出
class KqWidgetStack {
  final List<Widget> _stack = [];

  ///入栈
  push(Widget obj) {
    _stack.add(obj);
  }

  ///出栈
  Widget? pop() {
    if (_stack.isEmpty) {
      return null;
    } else {
      return _stack.removeLast();
    }
  }

  ///栈长度
  length() {
    return _stack.length;
  }

  ///清除栈
  clear() {
    _stack.clear();
  }
}

StackView:

import 'package:flutter/cupertino.dart';
import 'package:kq_flutter_widgets/widgets/animate/animate_view.dart';
import 'package:kq_flutter_widgets/widgets/stackview/stack.dart';

class StackView extends StatefulWidget {
  ///初始显示的界面
  final Widget initChild;

  ///state回调。获取state后方便后续界面操作。
  final void Function(StackViewState state)? stateCallback;

  const StackView({
    super.key,
    required this.initChild,
    this.stateCallback,
  });

  @override
  State<StatefulWidget> createState() => StackViewState();
}

class StackViewState extends State<StackView> {
  final KqWidgetStack _stack = KqWidgetStack();
  bool _isOpen = true;
  Widget? _previousWidget;
  Widget? _currentWidget;

  @override
  void initState() {
    super.initState();
    _currentWidget = widget.initChild;
    widget.stateCallback?.call(this);
  }

  @override
  Widget build(BuildContext context) {
    if (_currentWidget == null) {
      return Container();
    } else if (_previousWidget == null) {
      return _isOpen
          ? AnimateView(
              animate: TranslationAnimate(
                  angle: TranslationAnimateDirection.bottomToTop.angle,
                  type: TranslationAnimateType.translateIn),
              child: _currentWidget!,
            )
          : AnimateView(
              animate: TranslationAnimate(
                  angle: TranslationAnimateDirection.topToBottom.angle,
                  type: TranslationAnimateType.translateOut),
              child: _currentWidget!,
            );
    } else {
      return _isOpen
          ? Stack(
              children: [
                AnimateView(
                  animate: TranslationAnimate(
                      angle: TranslationAnimateDirection.bottomToTop.angle,
                      type: TranslationAnimateType.translateOut),
                  isNeedFlashEveryTime: true,
                  child: _previousWidget!,
                ),
                AnimateView(
                  animate: TranslationAnimate(
                      angle: TranslationAnimateDirection.bottomToTop.angle,
                      type: TranslationAnimateType.translateIn),
                  isNeedFlashEveryTime: true,
                  child: _currentWidget!,
                ),
              ],
            )
          : Stack(
              children: [
                AnimateView(
                  animate: TranslationAnimate(
                      angle: TranslationAnimateDirection.topToBottom.angle,
                      type: TranslationAnimateType.translateOut),
                  isNeedFlashEveryTime: true,
                  child: _previousWidget!,
                ),
                AnimateView(
                  animate: TranslationAnimate(
                      angle: TranslationAnimateDirection.topToBottom.angle,
                      type: TranslationAnimateType.translateIn),
                  isNeedFlashEveryTime: true,
                  child: _currentWidget!,
                ),
              ],
            );
    }
  }

  addWidget(Widget page) {
    _isOpen = true;
    _previousWidget = _currentWidget;
    _currentWidget = page;
    if (_previousWidget != null) {
      _stack.push(_previousWidget!);
    }
    print("stack size=${_stack.length()}");
    setState(() {});
  }

  ///回退,返回上一个界面。
  ///[bool] 返回true表示成功返回上一级,
  ///返回false表示返回失败,已是最后一个界面,不可继续返回。
  bool back() {
    _isOpen = false;
    _previousWidget = _currentWidget;
    _currentWidget = _stack.pop();

    print("stack size=${_stack.length()}");
    setState(() {});
    if (_stack.length() > 1) {
      return true;
    } else {
      return false;
    }
  }

  @override
  void dispose() {
    super.dispose();
    _stack.clear();
  }
}

demo:

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:kq_flutter_widgets/widgets/button/kq_small_button.dart';
import 'package:kq_flutter_widgets/widgets/stackview/stack_view.dart';
import 'package:kq_flutter_widgets/widgets/titleBar/kq_title_bar.dart';

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

  @override
  State<StatefulWidget> createState() => StackViewDemoState();
}

class StackViewDemoState extends State<StackViewDemo> {
  StackViewState? state;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: KqHeadBar(
        headTitle: 'StackView演示',
        back: () {
          Get.back();
        },
      ),
      body: StackView(
        initChild: Column(
          children: [
            const Text("我是首页"),
            KqSmallButton(
              title: "打开新页面",
              onTap: (disabled) {
                state?.addWidget(TestPage1(state: state!));
              },
            ),
            Expanded(child: Container(color: Colors.purple,)),
          ],
        ),
        stateCallback: (StackViewState state) {
          this.state = state;
        },
      ),
    );
  }
}

class TestPage1 extends StatelessWidget {
  final StackViewState state;

  const TestPage1({super.key, required this.state});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text("我是TestPage1"),
        KqSmallButton(
          title: "返回",
          onTap: (disabled) {
            state.back();
          },
        ),
        KqSmallButton(
          title: "打开新页面",
          onTap: (disabled) {
            state.addWidget(TestPage2(state: state));
          },
        ),
        Expanded(child: Container(color: Colors.amber,)),
      ],
    );
  }
}

class TestPage2 extends StatelessWidget {
  final StackViewState state;

  const TestPage2({super.key, required this.state});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text("我是TestPage2"),
        KqSmallButton(
          title: "返回",
          onTap: (disabled) {
            state.back();
          },
        ),
        KqSmallButton(
          title: "打开新页面",
          onTap: (disabled) {
            state.addWidget(TestPage3(state: state));
          },
        ),
        Expanded(child: Container(color: Colors.cyan,)),
      ],
    );
  }
}

class TestPage3 extends StatelessWidget {
  final StackViewState state;

  const TestPage3({super.key, required this.state});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text("我是TestPage3"),
        KqSmallButton(
          title: "返回",
          onTap: (disabled) {
            state.back();
          },
        ),
        Expanded(child: Container(color: Colors.blueAccent,)),
      ],
    );
  }
}

2023/9/1号更新:

由于只做了单纯的界面处理,没有做数据处理,后续加入了数据传递功能:

最新的StackView代码:

import 'package:flutter/cupertino.dart';
import 'package:kq_flutter_widgets/widgets/animate/animate_view.dart';
import 'package:kq_flutter_widgets/widgets/stackView/stack.dart';

class StackView extends StatefulWidget {
  ///初始显示的界面
  final Widget? Function(StackViewState state) init;

  const StackView({
    super.key,
    required this.init,
  });

  @override
  State<StatefulWidget> createState() => StackViewState();
}

class StackViewState extends State<StackView> {
  ///栈
  final KqWidgetStack _stack = KqWidgetStack();

  ///是否是打开的状态,true表示打开界面,false表示是关闭界面,默认为true
  bool _isOpen = true;

  ///上一个界面
  Widget? _previousWidget;

  ///当前界面
  Widget? _currentWidget;

  ///参数
  dynamic arguments;

  ///当当前界面为空时使用的零时变量
  final Widget _tempNullWidget = Container();

  ///返回回调出缓存
  final Map<Widget, Function(dynamic result)> _callbacks = {};

  @override
  void initState() {
    super.initState();
    _currentWidget = widget.init.call(this);
  }

  @override
  Widget build(BuildContext context) {
    if (_currentWidget == null) {
      return Container();
    } else if (_previousWidget == null) {
      return _isOpen
          ? AnimateView(
              animate: TranslationAnimate(
                  angle: TranslationAnimateDirection.bottomToTop.angle,
                  type: TranslationAnimateType.translateIn),
              child: _currentWidget!,
            )
          : AnimateView(
              animate: TranslationAnimate(
                  angle: TranslationAnimateDirection.topToBottom.angle,
                  type: TranslationAnimateType.translateOut),
              child: _currentWidget!,
            );
    } else {
      return _isOpen
          ? Stack(
              children: [
                AnimateView(
                  animate: TranslationAnimate(
                      angle: TranslationAnimateDirection.bottomToTop.angle,
                      type: TranslationAnimateType.translateOut),
                  isNeedFlashEveryTime: true,
                  child: _previousWidget!,
                ),
                AnimateView(
                  animate: TranslationAnimate(
                      angle: TranslationAnimateDirection.bottomToTop.angle,
                      type: TranslationAnimateType.translateIn),
                  isNeedFlashEveryTime: true,
                  child: _currentWidget!,
                ),
              ],
            )
          : Stack(
              children: [
                AnimateView(
                  animate: TranslationAnimate(
                      angle: TranslationAnimateDirection.topToBottom.angle,
                      type: TranslationAnimateType.translateOut),
                  isNeedFlashEveryTime: true,
                  child: _previousWidget!,
                ),
                AnimateView(
                  animate: TranslationAnimate(
                      angle: TranslationAnimateDirection.topToBottom.angle,
                      type: TranslationAnimateType.translateIn),
                  isNeedFlashEveryTime: true,
                  child: _currentWidget!,
                ),
              ],
            );
    }
  }

  ///以Map的方式获取参数,如果传入的参数不是Map形式,则需使用[arguments]获取参数
  S? getArg<S>(String key) {
    if (arguments == null) {
      return null;
    }
    if (arguments is! Map) {
      return null;
    }
    return arguments[key];
  }

  ///添加一个widget,即打开一个界面。
  ///[page] 需要打开的界面。
  ///[arguments] 打开时需要给下一个界面传递的参数。
  ///[callback]打开的界面关闭时,回传的参数。
  addWidget(Widget page,
      {dynamic arguments, Function(dynamic result)? callback}) {
    _isOpen = true;
    _previousWidget = _currentWidget;
    _currentWidget = page;

    ///传参赋值,取值时,直接使用[arguments]取值,或者使用[getArg]方法取值
    this.arguments = arguments;

    ///存储回调,当初始界面为空时,使用临时key存储
    if (callback != null) {
      _callbacks.putIfAbsent(
          _previousWidget ?? _tempNullWidget, () => callback);
    }
    if (_previousWidget != null) {
      _stack.push(_previousWidget!);
    }
    setState(() {});
  }

  ///回退,返回上一个界面。
  ///[bool] 返回true表示成功返回上一级,
  ///返回false表示返回失败,已是最后一个界面,不可继续返回。
  bool back({dynamic result}) {
    _isOpen = false;
    _previousWidget = _currentWidget;
    _currentWidget = _stack.pop();

    ///移除当前界面的callback,如果存在,调用call,并传递result的值
    _callbacks.remove(_currentWidget)?.call(result);

    setState(() {});
    if (_stack.length() > 0) {
      return true;
    } else {
      return false;
    }
  }

  ///是否可返回
  bool backAble() {
    return _stack.length() > 0;
  }

  /// 关闭
  close({dynamic result}) {
    _callbacks[_tempNullWidget]?.call(result);
    _stack.clear();
    _callbacks.clear();
    _currentWidget = null;
    setState(() {});
  }

  @override
  void dispose() {
    super.dispose();
    _stack.clear();
    _callbacks.clear();
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

许天成

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值