HarmonyOS鸿蒙Next中Flutter框架多设备开发指导-模态弹窗场景

HarmonyOS鸿蒙Next中Flutter框架多设备开发指导-模态弹窗场景

1.1 场景概述

在移动应用开发中,不同设备的屏幕形态各异,如横竖屏切换、PC、折叠屏等,同时系统状态栏、导航栏、软键盘等元素也会占据屏幕空间。为了确保应用内容在各种设备和场景下都能正常显示,不被遮挡。本文将详细介绍RN模态组件的实现原理、适配指导以及具体的场景案例。

1.1.1 使用场景

在应用交互过程中,模态页面弹窗是很常见的交互方式,希望在不同设备上模态页面弹窗都能正常显示和交互。下面是不同设备上弹出框差异,包括:直板机、PAD、PC、折叠屏(阔折叠、双折叠、三折叠)。

说明

根据断点布局效果:

图1 横向断点sm,宽度为100%,高度为60%,效果如下:

图2 横向断点md,非悬停态时宽度为100%,高度为60%。悬停态时宽度为100%,高度为40%,且距离底部高为55%避免被折痕和软键盘遮挡,效果如下:

图3 横向断点lg,宽度为100%,高度为60%,效果如下:

图4 横向断点xl,宽度为100%,高度为60%,效果如下:

1.1.2 常见问题

开发过程中弹出框界面在不同设备上会存在差异,可能会出现截断、内容重叠或者显示不完整等问题

1.1.3 多设备适配

1.1.3.1 模态页面弹框多设备尺寸自适应适配

模态页面弹窗尺寸在不同屏幕尺寸上自适应放大或缩小

1.1.3.2 模态页面弹框多设备窗口变化适配

弹出位置跟随窗口变化,不能超出窗口范围

模态页面弹窗适配多设备形态变化,应用窗口适配悬停态,模态页面避让折痕区,在下半屏显示

适配分屏场景,模态页面无遮挡,不跨越窗口区域。

1.1.3.3 模态页面软键盘适配

模态页面弹出框多为底部弹出(也可设置其他方向),与软件盘弹出方向相同。默认模态页面弹窗无需避让,保证软键盘在最上层即可。

1.2 开发指导

1.2.1 Flutter开发

1.2.1.1 关键能力

1)flutter模态弹窗适配,需要自定义组件,当前已有的弹窗组件无法自由调整高度。

2)flutter模态弹窗适配,重点需要关注输入框场景。在输入框聚焦时,需要处理避让区,防止输入框内容截断或者被遮挡。

1.2.1.2 指导案例

flutter模态弹窗开发主要关注以下四点

1)模态页面弹窗多设备布局规则适配:在各个机型上,模态弹窗拉起时,无论横竖屏都需要横向填满,让弹窗标题和内容正常显示,点击透明区域能关闭弹窗。

适配不同的产品屏幕布局及视觉规则,保持整体体验的一致性。

2)模态页面弹窗尺寸多设备自适应适配:模态页面弹窗尺寸在不同屏幕尺寸上自适应放大或缩小,且弹窗高度有三档,可以自由调节。

3)模态页面弹窗多设备窗口变化适配:弹出位置跟随窗口变化,不能超出窗口范围,即在不同设备上,弹出位置不同。窗口横竖屏切换时,需要保留弹窗档位。

4)模态页面软键盘适配:模态页面弹出框多为底部弹出(也可设置其他方向),与软件盘弹出方向相同。默认模态页面弹窗无需避让,保证软键盘在最上层即可,即不遮挡软键盘输入内容。

1.2.1.3 示例代码

1)模态页面弹窗多设备布局规则适配:弹窗布局逻辑如下。主要关注避让区处理逻辑,以及点击透明区域退出弹窗逻辑。

@override
Widget build(BuildContext context) {
  final mediaQuery = MediaQuery.of(context);
  final orientation = mediaQuery.orientation;
  final snapFractions = _currentSnapFractions;
  if (_lastOrientation != orientation) {
    final nearest = _findNearestSnap(_fraction, snapFractions);
    _currentSnapIndex = nearest.key;
    _fraction = nearest.value;
    _lastOrientation = orientation;
  }
  if (_currentSnapIndex >= snapFractions.length) {
    final nearest = _findNearestSnap(_fraction, snapFractions);
    _currentSnapIndex = nearest.key;
    _fraction = nearest.value;
  }
  final screenHeight = mediaQuery.size.height;
  final statusBarHeight = kToolbarHeight;
  final keyboardHeight = mediaQuery.viewInsets.bottom;
  final modalHeight = screenHeight * _fraction;
  double needLiftHeight;
  final availableHeight = (screenHeight - statusBarHeight - modalHeight)
    .clamp(0.0, double.infinity);
  if (keyboardHeight > availableHeight) {
    needLiftHeight = availableHeight;
  } else {
    needLiftHeight = keyboardHeight;
  }
  final bool isPortrait = orientation == Orientation.portrait;
  final bool isLandscape = orientation == Orientation.landscape;
  if (isLandscape && (screenHeight <= 440)) {
    needLiftHeight = screenHeight - modalHeight - 10;
  }
  final bool needLift =
    keyboardHeight > 0 &&
      ((isPortrait && _currentSnapIndex == 0) || isLandscape);
  return AnimatedPadding(
    padding: EdgeInsets.only(
      bottom: needLift ? needLiftHeight : 0.0,
    ),
    duration: const Duration(milliseconds: 200),
    curve: Curves.decelerate,
    child: Stack(
      children: [
        Positioned.fill(
          child: GestureDetector(
            behavior: HitTestBehavior.opaque,
            onTap: () => Navigator.of(context).pop(),
          ),
        ),
        Align(
          alignment: Alignment.bottomCenter,
          child: SizedBox(
            width: double.infinity,
            height: modalHeight,
            child: _buildSheetContent(
              onDragStart:_onDragStart,
              onDragUpdate:_onDragUpdate,
              onDragEnd:_onDragEnd,
            ),
          ),
        ),
      ],
    ),
  );
}

2)模态页面弹窗尺寸多设备自适应适配:档位设计逻辑如下。

需注意三个点:1.在最低档位时,做一个下拉退出逻辑。2.档位调节需要做一个“档位吸附”效果,即根据手动拉起弹窗的位置,松手后吸附到距离最近的档位。3.在中间档位时,需要软键盘拉起时的避让区处理

其它大部分是尺寸处理的逻辑。

// 档位吸附
MapEntry<int, double> _findNearestSnap(double value, List<double> snaps) {
  double closest = snaps.first;
  int closestIndex = 0;
  double minDiff = (value - closest).abs();
  for (int i = 1; i < snaps.length; i++) {
    final f = snaps[i];
    final diff = (value - f).abs();
    if (diff < minDiff) {
      minDiff = diff;
      closest = f;
      closestIndex = i;
    }
  }
  return MapEntry(closestIndex, closest);
}

// 初始档位
void _onDragStart(DragStartDetails details) {
  final minFraction = _currentSnapFractions.first;
  _isAtMinOnDragStart = (_fraction == minFraction);
  _extraDragDownPixels = 0.0;
}

// 更新档位,需要处理避让区
void _onDragUpdate(DragUpdateDetails details) {
  final screenHeight = MediaQuery.of(context).size.height;
  final dy = details.delta.dy;
  final snapFractions = _currentSnapFractions;
  final minFraction = snapFractions.first;
  final maxFraction = snapFractions.last;
  double newFraction = _fraction - dy / screenHeight;
  if (_isAtMinOnDragStart && newFraction <= minFraction && dy > 0) {
    _extraDragDownPixels += dy;
    newFraction = minFraction;
  } else {
    _extraDragDownPixels = 0.0;
  }
  newFraction = newFraction.clamp(minFraction, maxFraction);
  setState(() {
    _fraction = newFraction;
  });
}
  
  //最低档位
void _onDragEnd(DragEndDetails details) {
  final snapFractions = _currentSnapFractions;
  final minFraction = snapFractions.first;
  if (_isAtMinOnDragStart &&
  _fraction == minFraction &&
  _extraDragDownPixels > _closeThresholdPixels) {
    Navigator.of(context).pop();
    return;
  }
  final nearest = _findNearestSnap(_fraction, snapFractions);
  setState(() {
    _fraction = nearest.value;
    _currentSnapIndex = nearest.key;
    _extraDragDownPixels = 0.0;
    _isAtMinOnDragStart = false;
  });
}

3)模态页面弹窗多设备窗口变化适配:重点逻辑是记录当前档位,使横竖屏切换和折叠状态切换,能保留切换前的档位。

if (_lastOrientation != orientation) {
  final nearest = _findNearestSnap(_fraction, snapFractions);
  _currentSnapIndex = nearest.key;
  _fraction = nearest.value;
  _lastOrientation = orientation;
}
if (_currentSnapIndex >= snapFractions.length) {
  final nearest = _findNearestSnap(_fraction, snapFractions);
  _currentSnapIndex = nearest.key;
  _fraction = nearest.value;
}

4)模态页面软键盘适配:重点关注软键盘拉起时,弹窗会被顶起。以下是弹窗顶起时,避让区的处理逻辑。

final screenHeight = mediaQuery.size.height;
final statusBarHeight = kToolbarHeight;
final keyboardHeight = mediaQuery.viewInsets.bottom;
final modalHeight = screenHeight * _fraction;
double needLiftHeight;
final availableHeight = (screenHeight - statusBarHeight - modalHeight)
  .clamp(0.0, double.infinity);
if (keyboardHeight > availableHeight) {
  needLiftHeight = availableHeight;
} else {
  needLiftHeight = keyboardHeight;
}
final bool isPortrait = orientation == Orientation.portrait;
final bool isLandscape = orientation == Orientation.landscape;
if (isLandscape && (screenHeight <= 440)) {
  needLiftHeight = screenHeight - modalHeight - 10;
}
final bool needLift =
  keyboardHeight > 0 &&
    ((isPortrait && _currentSnapIndex == 0) || isLandscape);

模态弹窗的Sample示例代码地址:flutter 模态弹窗,开发者可以通过该地址查看完整的示例代码,并根据自己的需求进行修改和扩展。


更多关于HarmonyOS鸿蒙Next中Flutter框架多设备开发指导-模态弹窗场景的实战教程也可以访问 https://www.itying.com/category-92-b0.html

2 回复

在HarmonyOS Next中,Flutter框架的模态弹窗实现使用showDialogshowModalBottomSheet。多设备适配需通过MediaQueryLayoutBuilder控制弹窗尺寸与位置。若需调用鸿蒙原生弹窗样式,采用MethodChannelohos.plugin层通信,仅使用Dart/ArkTS接口,不涉及Java或C语言。确保弹窗响应系统折叠屏与平板布局变化。

更多关于HarmonyOS鸿蒙Next中Flutter框架多设备开发指导-模态弹窗场景的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


这篇指导完整覆盖了HarmonyOS Next下Flutter模态弹窗的多设备适配要点,从直板机到折叠屏、分屏和软键盘弹出等场景均给出了具体方案。

核心思路是自定义底部弹出组件,通过断点布局让弹窗横向撑满,高度采用可拖拽的三段式档位。软键盘避让利用MediaQuery.viewInsets计算键盘高度,与弹窗档位、屏幕方向协同,在横屏和小屏设备上额外处理顶起量。档位吸附算法确保拖拽松手后平滑贴合最近档位,同时支持下拉关闭。窗口形态变化(横竖屏、折叠悬停)通过记录当前档位索引,在方向切换后重新吸附相同档位,避免突兀跳变。

代码示例清晰展示了上述逻辑,开发者可直接基于提供的示例地址进行二次开发。整体方案有效解决了弹出框截断、重叠和位置错位等常见问题,保障了多设备交互一致性。

回到顶部