一洼绿地

Flutter 开发编辑器的控制台处理

·5 min read

前言

有朋友在 flutter 下开发富文本编辑器,遇到一些问题 下面的代码,结合前面的 KeyboardEventWidget 代码,能较好的完成控制台的处理 代码在 https://github.com/koocyton/flutter_editor_hub 有完整下载

controller

import 'package:flutter/services.dart';
import 'package:flutter_editor_hub/editor_hub_widget.dart';

class EditorHubController {

  // 键盘的高度
  double keyboradBottomMargin = 0;

  // 操作面板的高度
  double panelBottomMargin = 0;

  // 禁止 panel 跟随 键盘滑入滑出
  bool isPanelFollowKeyboard = true;

  // disableKeyboard = false 时,panel 滑入滑出动画时长 0
  // disableKeyboard = true 时,panel 滑入滑出动画时长 200
  int bottomAnimatedMilliseconds = 0;

  // panel 最大高度
  final double maxPanelBottomMargin = 300;

  // panel index
  int panelIndex = 0;

  // 记录锚定的状态 (面板 ▤ 键盘 ━ 前一个状态)
  // 前一个是 面板 ━ 键盘 ━  0,操作 switchPanelBar -> 面板 ━ 键盘 ━
  // 前一个是 面板 ✕ 键盘 ▤ ,操作 switchPanelBar -> 面板 ✕ 键盘 ▤
  // 0 -> 面板 ━ 键盘 ━ 
  // 1 -> 面板 ▤ 键盘 ━ 
  // 2 -> 面板 ✕ 键盘 ▤ 
  int anchoredState = 0;

  EditorHubController();

  late EditorHubWidgetState hubState;

  void initHubState(EditorHubWidgetState state) {
    hubState = state;
  }

  bool popScopeCanPop() {
    // 面板 ━ 键盘 ━
    if (panelBottomMargin<=0 && keyboradBottomMargin<=0) {
      return true;
    }
    return false;
  }

  void popScopePopInvoked(bool didPop) {
    statusDispatcher(
      // 面板 ━ 键盘 ━ -> 面板 ▤ 键盘 ━
      onPanelHideKbHide:(){
        return;
      },
      // 面板 ▤ 键盘 ━ -> 面板 ━ 键盘 ━
      onPanelShowKbHide:(){
        hubState.setState((){
          bottomAnimatedMilliseconds = 130;
          isPanelFollowKeyboard = false;
          panelBottomMargin = 0;
        });
      },
      // 面板 ✕ 键盘 ▤ -> 面板 ▤ 键盘 ━
      onPanelHideKbShow:(){
        hubState.setState((){
          bottomAnimatedMilliseconds = 0;
          isPanelFollowKeyboard = true;
          hideTextInput();
        });
      }
    );
  }

  void resetState() {
    hubState.setState((){
      isPanelFollowKeyboard = true;
      bottomAnimatedMilliseconds = 0;
    });
  }

  void panelSlideEnd() {
    resetState();
  }

  void keyboardSlideEnd(double bm) {
    resetState();
  }

  void keyboardShowing(double bm) {
    if (isPanelFollowKeyboard && panelBottomMargin<bm) {
      hubState.setState((){
        panelBottomMargin = bm;
      });
    }
    keyboradBottomMargin = bm;
  }

  void keyboardHiding(double bm) {
    if (isPanelFollowKeyboard && panelBottomMargin>bm) {
      hubState.setState((){
        panelBottomMargin = bm;
      });
    }
    keyboradBottomMargin = bm;
  }

  void switchPanelBar(int idx) {
    statusDispatcher(
      // 面板 ━ 键盘 ━ -> 面板 ▤ 键盘 ━
      onPanelHideKbHide:(){
        hubState.setState((){
          panelIndex = idx;
          bottomAnimatedMilliseconds = 130;
          isPanelFollowKeyboard = false;
          panelBottomMargin = maxPanelBottomMargin;
          anchoredState = 0;
        });
      },
      // 面板 ▤ 键盘 ━ -> 面板 ━ 键盘 ━
      onPanelShowKbHide:(){
        hubState.setState((){
          // panel 切换
          if (idx!=panelIndex) {
            panelIndex = idx;
            return;
          }
          // 上一次状态是 面板 ━ 键盘 ━
          if (anchoredState==0) {
            bottomAnimatedMilliseconds = 130;
            isPanelFollowKeyboard = false;
            panelBottomMargin = 0;
          }
          // 上一次状态是 面板 ✕ 键盘 ▤
          else {
            panelIndex = idx;
            isPanelFollowKeyboard = false;
            anchoredState = -1;
            showTextInput();
          }
        });
      },
      // 面板 ✕ 键盘 ▤ -> 面板 ▤ 键盘 ━
      onPanelHideKbShow:(){
        hubState.setState((){
          panelIndex = idx;
          isPanelFollowKeyboard = false;
          anchoredState = 2;
        });
        hideTextInput();
      }
    );
  }

  void statusDispatcher({Function? onPanelHideKbHide, Function? onPanelShowKbHide, Function? onPanelHideKbShow}) {
    // 面板 ━ 键盘 ━
    if (panelBottomMargin<=0) {
      if (onPanelHideKbHide!=null) {
        onPanelHideKbHide();
      }
    }
    // 面板 ▤ 键盘 ━
    else if (keyboradBottomMargin<=0) { // && panelBottomMargin>0
      if (onPanelShowKbHide!=null) {
        onPanelShowKbHide();
      }
    }
    // 面板 ✕ 键盘 ▤
    else { // if (panelBottomMargin>0 && keyboradBottomMargin>0) {
      if (onPanelHideKbShow!=null) {
        onPanelHideKbShow();
      }
    }
  }

  static void showTextInput() {
    SystemChannels.textInput.invokeMethod<void>('TextInput.show');
  }

  static void hideTextInput() {
    SystemChannels.textInput.invokeMethod<void>('TextInput.hide');
  }
}

widget

import 'package:flutter/material.dart';
import 'package:flutter_editor_hub/editor_hub_controller.dart';
import 'package:flutter_editor_hub/keyboard_event_widget.dart';

class EditorHubWidget extends StatefulWidget {

  final EditorHubController controller;

  final Widget editorChild;

  final Widget navbarChild;

  final List<Widget> panelChildren;

  const EditorHubWidget({
    required this.controller,
    required this.editorChild,
    required this.navbarChild,
    required this.panelChildren,
    super.key
  });

  @override
  State<EditorHubWidget> createState() => EditorHubWidgetState();

}

class EditorHubWidgetState extends State<EditorHubWidget> {

  @override
  Widget build(BuildContext context) {
    return PopScope(
      canPop: widget.controller.popScopeCanPop(),
      onPopInvoked: widget.controller.popScopePopInvoked,
      child: KeyboardEventWidget(
        onKbHiding: (bm){
          widget.controller.keyboardHiding(bm);
        },
        onKbShowing: (bm){
          widget.controller.keyboardShowing(bm);
        },
        onKbHideEnd: (bm) {
          widget.controller.resetState();
        },
        onKbShowEnd: (bm) {
          widget.controller.resetState();
        },
        child: Column(
          children:[
            Expanded(
              child: widget.editorChild
            ),
            widget.navbarChild,
            slidePanelBar()
          ]
        )
      )
    );
  }

  Widget slidePanelBar() {
    return AnimatedContainer(
        onEnd: (){
          Future.delayed(const Duration(milliseconds: 50), () {
            widget.controller.panelSlideEnd();
          });
        },
        duration: Duration(milliseconds: widget.controller.bottomAnimatedMilliseconds),
        height: widget.controller.panelBottomMargin,
        child: IndexedStack(
          alignment: Alignment.center,
          index: widget.controller.panelIndex,
          children: widget.panelChildren.map((e){
            return SingleChildScrollView(
              child: e
            );
          }).toList()
        )
      );
  }

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

  @override
  void initState() {
    super.initState();
    widget.controller.initHubState(this);
  }

  @override
  void setState(Function fn) {
    if (mounted) {
      super.setState((){
        fn();
      });
    }
  }
}