React Native实战:高性能Overlay遮罩层组件封装与OpenHarmony适配

React Native实战:高性能Overlay遮罩层组件封装与OpenHarmony适配

本文基于OpenHarmony 6.0.0(API 20) + React Native 0.72.5 + TypeScript 4.8.4 实现通用化Overlay遮罩层组件,优化了原组件的扩展性、交互性和平台适配性,支持Modal、Loading、ActionSheet、Toast四种核心场景,解决了触摸穿透、动画卡顿、层级冲突等OpenHarmony平台常见问题,同时提供可复用的封装思路和完整实战代码。

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net


概述

遮罩层(Overlay)是移动端交互的核心组件,通过覆盖在应用内容之上的半透明层,实现模态隔离注意力引导 ,同时暂时禁用底层内容交互。在OpenHarmony平台开发时,需重点解决渲染层级管理跨端触摸事件兼容原生动画性能三大核心问题。

本文实现的Overlay组件具备高可配置强兼容性优性能三大特性:

  • 支持四种主流遮罩场景,可快速扩展自定义类型
  • 适配OpenHarmony安全区域、原生渲染引擎特性
  • 采用原生驱动动画,无卡顿、无触摸穿透
  • 完善的TypeScript类型定义,支持工程化开发

组件核心特性

支持的Overlay类型(新增扩展配置)

在原基础上补充配置项交互细节,让每种类型的行为更符合OpenHarmony平台交互规范:

类型 核心用途 点击行为 动画效果 扩展配置
Modal 模态对话框(确认/操作) 点击遮罩可关闭 淡入淡出(200ms) 可自定义遮罩透明度、内容宽高
Loading 全局加载指示器 点击遮罩无响应 旋转+淡入淡出 可自定义加载文案、加载图标颜色
ActionSheet 底部操作列表 点击遮罩可关闭 底部滑入/滑出(300ms) 可配置是否从底部弹出、列表圆角
Toast 轻量提示(成功/失败) 无点击交互 淡入淡出(150ms)+ 自动关闭 可自定义显示时长、位置(上/中/下)

视觉层级设计

严格遵循OpenHarmony渲染层级规范,避免与原生组件(如导航栏、弹窗)产生层级冲突,zIndex 配置兼顾跨端兼容性:

复制代码
┌─────────────────────────────┐
│   Overlay遮罩层 (zIndex: 9999)│ ← 提升层级,避免被原生组件覆盖
│   ┌─────────────────────┐   │
│   │   遮罩内容容器      │   │
│   └─────────────────────┘   │
├─────────────────────────────┤
│   应用业务内容 (zIndex: 0~99)│
├─────────────────────────────┤
│   OpenHarmony原生层      │
└─────────────────────────────┘

注:将原zIndex从999提升至9999,避免与应用内其他高层级组件冲突。

核心实现(优化版)

1. 完善的类型定义(TypeScript)

新增Toast类型位置配置显示时长等属性,完善类型约束,避免开发时的类型错误,相比原代码更具工程化特性:

typescript 复制代码
import { StyleProp, ViewStyle } from 'react-native';

// 遮罩层类型
export type OverlayType = 'modal' | 'loading' | 'actionSheet' | 'toast';
// Toast显示位置
export type ToastPosition = 'top' | 'center' | 'bottom';
// 核心属性定义
export interface OverlayProps {
  visible: boolean;         // 是否显示
  children?: React.ReactNode; // 自定义内容
  type?: OverlayType;       // 遮罩类型,默认modal
  closable?: boolean;       // 是否可点击遮罩关闭,默认true
  onClose?: () => void;     // 关闭回调
  maskColor?: string;       // 遮罩颜色,默认rgba(0,0,0,0.5)
  maskOpacity?: number;     // 遮罩透明度,新增配置
  duration?: number;        // 自动关闭时长(ms),仅Toast生效,默认2000
  position?: ToastPosition; // 显示位置,仅Toast/ActionSheet生效
  style?: StyleProp<ViewStyle>; // 自定义内容容器样式,新增
}

2. 动画系统优化(解决卡顿/适配OpenHarmony)

原代码仅实现淡入淡出,优化后按类型匹配专属动画 ,同时保留useNativeDriver: true 保证OpenHarmony平台原生动画性能,避免JS桥接带来的卡顿:

typescript 复制代码
import React, { useState, useRef, useEffect } from 'react';
import { Animated, Easing } from 'react-native';

const Overlay: React.FC<OverlayProps> = ({
  visible,
  type = 'modal',
  duration = 2000,
  position = 'center',
  ...rest
}) => {
  // 淡入淡出动画(基础)
  const fadeAnim = useRef(new Animated.Value(0)).current;
  // 位移动画(用于ActionSheet/Toast滑入滑出)
  const translateAnim = useRef(new Animated.Value(0)).current;

  // 根据类型初始化动画
  useEffect(() => {
    let animation: Animated.CompositeAnimation;
    if (visible) {
      // 显示动画
      fadeAnim.setValue(0);
      if (type === 'actionSheet' || (type === 'toast' && position !== 'center')) {
        // ActionSheet/非居中Toast:位移动画+淡入
        translateAnim.setValue(position === 'top' || position === 'bottom' ? 100 : 0);
        animation = Animated.parallel([
          Animated.timing(fadeAnim, {
            toValue: 1,
            duration: type === 'actionSheet' ? 300 : 150,
            useNativeDriver: true,
            easing: Easing.ease
          }),
          Animated.timing(translateAnim, {
            toValue: 0,
            duration: type === 'actionSheet' ? 300 : 150,
            useNativeDriver: true,
            easing: Easing.easeOut
          })
        ]);
      } else {
        // Modal/Loading/居中Toast:仅淡入
        animation = Animated.timing(fadeAnim, {
          toValue: 1,
          duration: type === 'toast' ? 150 : 200,
          useNativeDriver: true,
        });
      }
      animation.start();

      // Toast自动关闭
      if (type === 'toast' && visible) {
        const timer = setTimeout(() => rest.onClose?.(), duration);
        return () => clearTimeout(timer);
      }
    } else {
      // 隐藏动画
      animation = Animated.timing(fadeAnim, {
        toValue: 0,
        duration: type === 'actionSheet' ? 300 : 200,
        useNativeDriver: true,
      });
      animation.start();
    }
    return () => animation.stop(); // 清除动画,避免内存泄漏
  }, [visible, type, position, duration]);

  // 动画样式映射
  const getAnimatedStyle = () => {
    const baseStyle = { opacity: fadeAnim };
    if (type === 'actionSheet' || (type === 'toast' && position !== 'center')) {
      return {
        ...baseStyle,
        transform: [
          {
            translateY: translateAnim.interpolate({
              inputRange: [0, 100],
              outputRange: [0, position === 'bottom' ? 100 : -100]
            })
          }
        ]
      };
    }
    return baseStyle;
  };

3. 触摸事件处理(彻底解决OpenHarmony触摸穿透)

原代码仅通过activeOpacity 控制,优化后结合pointerEvents 实现双层防护,彻底解决OpenHarmony平台的触摸穿透问题,同时区分遮罩层和内容层的触摸行为:

typescript 复制代码
const handleMaskPress = () => {
  if (rest.closable && rest.onClose) {
    rest.onClose();
  }
};

// 内容层阻止事件冒泡,避免点击内容触发遮罩关闭
const handleContentPress = (e: React.MouseEvent) => {
  e.stopPropagation();
};

return (
  <Animated.View 
    style={[styles.overlayContainer, getAnimatedStyle()]}
    pointerEvents={visible ? 'auto' : 'none'} // 隐藏时禁用所有触摸事件
  >
    {/* 遮罩层 */}
    <TouchableOpacity
      style={[styles.mask, { backgroundColor: rest.maskColor, opacity: rest.maskOpacity || 1 }]}
      onPress={handleMaskPress}
      activeOpacity={rest.closable ? 1 : 0}
      disabled={!rest.closable} // 新增disabled,强化OpenHarmony兼容
    >
      {/* 内容容器:按类型匹配样式+位置 */}
      <Animated.View
        style={[
          styles.contentContainer,
          styles[`${type}Container`],
          styles[`position_${position}`],
          rest.style
        ]}
        onPress={handleContentPress}
        pointerEvents="auto" // 内容层启用触摸事件
      >
        {/* 内置默认内容 */}
        {renderDefaultContent(type)}
        {/* 自定义内容覆盖默认内容 */}
        {rest.children}
      </Animated.View>
    </TouchableOpacity>
  </Animated.View>
);

4. 内置默认内容(提升复用性)

新增renderDefaultContent 方法,为每种类型提供开箱即用的默认UI,无需重复编写加载、提示等基础内容,同时支持自定义内容覆盖:

typescript 复制代码
import { ActivityIndicator, Text, View } from 'react-native';

// 渲染默认内容
const renderDefaultContent = (type: OverlayType) => {
  switch (type) {
    case 'loading':
      return (
        <View style={styles.loadingContent}>
          <ActivityIndicator size="large" color="#FFFFFF" />
          <Text style={styles.loadingText}>加载中...</Text>
        </View>
      );
    case 'toast':
      return (
        <View style={styles.toastContent}>
          <Text style={styles.toastText}>操作成功</Text>
        </View>
      );
    default:
      return null;
  }
};

样式优化(适配OpenHarmony多设备)

  1. 提升zIndex至9999,避免与OpenHarmony原生组件层级冲突
  2. 适配安全区域paddingTop: StatusBar.currentHeight),兼容刘海屏、挖孔屏
  3. 按类型定义专属容器样式,符合OpenHarmony交互规范
  4. 新增圆角、阴影等样式,提升视觉体验
typescript 复制代码
import { StyleSheet, StatusBar } from 'react-native';

const styles = StyleSheet.create({
  // 遮罩层容器
  overlayContainer: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
    zIndex: 9999, // 提升层级,适配OpenHarmony原生组件
    paddingTop: StatusBar.currentHeight || 0, // 适配安全区域
  },
  // 遮罩层
  mask: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  // 基础内容容器
  contentContainer: {
    padding: 20,
    elevation: 8, // OpenHarmony原生阴影
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.2,
    shadowRadius: 8,
  },
  // Modal容器
  modalContainer: {
    backgroundColor: '#FFFFFF',
    borderRadius: 16,
    width: 280,
    alignItems: 'center',
  },
  // Loading容器
  loadingContainer: {
    backgroundColor: 'rgba(0,0,0,0.7)',
    borderRadius: 12,
    padding: 24,
    alignItems: 'center',
  },
  // ActionSheet容器
  actionSheetContainer: {
    backgroundColor: '#FFFFFF',
    borderRadius: 16,
    width: '90%',
    maxWidth: 350,
  },
  // Toast容器
  toastContainer: {
    backgroundColor: 'rgba(0,0,0,0.8)',
    borderRadius: 24,
    paddingHorizontal: 20,
    paddingVertical: 12,
  },
  // 位置样式
  position_center: {
    justifyContent: 'center',
    alignItems: 'center',
  },
  position_top: {
    alignSelf: 'center',
    marginTop: 60,
  },
  position_bottom: {
    alignSelf: 'center',
    marginBottom: 60,
  },
  // 内置内容样式
  loadingContent: {
    alignItems: 'center',
  },
  loadingText: {
    color: '#FFFFFF',
    fontSize: 14,
    marginTop: 12,
  },
  toastContent: {
    alignItems: 'center',
  },
  toastText: {
    color: '#FFFFFF',
    fontSize: 14,
  },
});

OpenHarmony专属适配要点(新增问题+解决方案)

在原适配基础上,补充多设备兼容内存管理原生引擎适配等要点,解决OpenHarmony 6.0.0平台的特有问题,适配更全面:

平台问题 核心原因 优化解决方案
动画卡顿 JS桥接动画耗时,OpenHarmony原生渲染优先级高 全程使用useNativeDriver: true,采用Animated.parallel合并动画,减少桥接次数
触摸穿透 OpenHarmony触摸事件冒泡机制与RN略有差异 结合pointerEvents+disabled+stopPropagation实现三层防护
层级冲突 原生组件(如通知栏、导航栏)zIndex较高 将遮罩层zIndex提升至9999,同时设置pointerEvents: auto
安全区域适配问题 刘海屏/挖孔屏遮挡内容 增加StatusBar.currentHeight适配顶部安全区域,底部留60px间距
内存泄漏 动画未清除、定时器未销毁 组件卸载时停止动画、清除Toast自动关闭定时器
多窗口适配 OpenHarmony支持多窗口,遮罩层易错位 使用absolute全屏定位,避免flex布局带来的窗口适配问题
样式失效 OpenHarmony对RN部分样式支持有限 替换为OpenHarmony原生支持的样式(如elevation替代部分shadow

完整使用示例(新增Toast+ActionSheet完整演示)

优化演示页面,补充Toast类型的使用,完善每种类型的交互示例,同时简化代码结构,更易理解和复用:

typescript 复制代码
// 演示页面
const OverlayDemoScreen: React.FC<{ onBack: () => void }> = ({ onBack }) => {
  const [modalVisible, setModalVisible] = useState(false);
  const [loadingVisible, setLoadingVisible] = useState(false);
  const [actionSheetVisible, setActionSheetVisible] = useState(false);
  const [toastVisible, setToastVisible] = useState(false);

  // 显示Loading(3秒后自动关闭)
  const showLoading = () => {
    setLoadingVisible(true);
    setTimeout(() => setLoadingVisible(false), 3000);
  };

  // 显示Toast
  const showToast = () => {
    setToastVisible(true);
  };

  return (
    <View style={styles.demoContainer}>
      {/* 导航栏 */}
      <View style={styles.navBar}>
        <TouchableOpacity onPress={onBack} style={styles.backBtn}>
          <Text style={styles.backText}>← 返回</Text>
        </TouchableOpacity>
        <Text style={styles.navTitle}>Overlay遮罩层组件演示</Text>
      </View>

      {/* 演示按钮 */}
      <View style={styles.btnGroup}>
        <TouchableOpacity style={styles.btn} onPress={() => setModalVisible(true)}>
          <Text style={styles.btnText}>Modal对话框</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.btn} onPress={showLoading}>
          <Text style={styles.btnText}>Loading加载</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.btn} onPress={() => setActionSheetVisible(true)}>
          <Text style={styles.btnText}>ActionSheet操作列表</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.btn} onPress={showToast}>
          <Text style={styles.btnText}>Toast轻提示</Text>
        </TouchableOpacity>
      </View>

      {/* Modal遮罩层 */}
      <Overlay visible={modalVisible} onClose={() => setModalVisible(false)}>
        <Text style={styles.modalTitle}>确认删除?</Text>
        <Text style={styles.modalDesc}>删除后数据将无法恢复,请确认操作</Text>
        <View style={styles.modalBtnGroup}>
          <TouchableOpacity style={styles.modalCancelBtn} onPress={() => setModalVisible(false)}>
            <Text style={styles.modalCancelText}>取消</Text>
          </TouchableOpacity>
          <TouchableOpacity style={styles.modalConfirmBtn} onPress={() => setModalVisible(false)}>
            <Text style={styles.modalConfirmText}>确认</Text>
          </TouchableOpacity>
        </View>
      </Overlay>

      {/* Loading遮罩层(不可关闭) */}
      <Overlay visible={loadingVisible} type="loading" closable={false} />

      {/* ActionSheet遮罩层(底部弹出) */}
      <Overlay
        visible={actionSheetVisible}
        type="actionSheet"
        position="bottom"
        onClose={() => setActionSheetVisible(false)}
      >
        <TouchableOpacity style={styles.actionItem}>
          <Text style={styles.actionText}>拍照</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.actionItem}>
          <Text style={styles.actionText}>从相册选择</Text>
        </TouchableOpacity>
        <TouchableOpacity style={styles.actionItem}>
          <Text style={styles.actionText}>取消</Text>
        </TouchableOpacity>
      </Overlay>

      {/* Toast遮罩层(底部显示,自动关闭) */}
      <Overlay
        visible={toastVisible}
        type="toast"
        position="bottom"
        onClose={() => setToastVisible(false)}
        duration={2000}
      >
        {/* 自定义Toast内容 */}
        <Text style={styles.toastCustomText}>删除成功!</Text>
      </Overlay>
    </View>
  );
};

// 演示页面样式
const demoStyles = StyleSheet.create({
  demoContainer: {
    flex: 1,
    backgroundColor: '#F8F9FA',
  },
  navBar: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 16,
    backgroundColor: '#F97316',
    paddingTop: StatusBar.currentHeight || 20,
  },
  backBtn: {
    marginRight: 12,
  },
  backText: {
    color: '#FFFFFF',
    fontSize: 16,
  },
  navTitle: {
    flex: 1,
    color: '#FFFFFF',
    fontSize: 18,
    fontWeight: 'bold',
    textAlign: 'center',
  },
  btnGroup: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    gap: 20,
    padding: 20,
  },
  btn: {
    backgroundColor: '#3B82F6',
    paddingHorizontal: 40,
    paddingVertical: 12,
    borderRadius: 8,
    width: '80%',
    alignItems: 'center',
  },
  btnText: {
    color: '#FFFFFF',
    fontSize: 16,
  },
  // 自定义Modal样式
  modalTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#334155',
    marginBottom: 12,
  },
  modalDesc: {
    fontSize: 14,
    color: '#64748B',
    textAlign: 'center',
    marginBottom: 20,
  },
  modalBtnGroup: {
    flexDirection: 'row',
    gap: 16,
    marginTop: 12,
  },
  modalCancelBtn: {
    paddingHorizontal: 24,
    paddingVertical: 8,
    borderWidth: 1,
    borderColor: '#E2E8F0',
    borderRadius: 6,
  },
  modalCancelText: {
    color: '#64748B',
    fontSize: 14,
  },
  modalConfirmBtn: {
    paddingHorizontal: 24,
    paddingVertical: 8,
    backgroundColor: '#EF4444',
    borderRadius: 6,
  },
  modalConfirmText: {
    color: '#FFFFFF',
    fontSize: 14,
  },
  // 自定义ActionSheet样式
  actionItem: {
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#F1F5F9',
    alignItems: 'center',
  },
  actionText: {
    fontSize: 16,
    color: '#334155',
  },
  // 自定义Toast样式
  toastCustomText: {
    color: '#FFFFFF',
    fontSize: 14,
  },
});

核心优化点总结

相比原文章的实现,本次优化主要集中在扩展性兼容性交互性工程化四个维度,共10项核心优化:

  1. 新增Toast、ActionSheet 原生动画支持,从单一淡入淡出升级为淡入+位移动画
  2. 完善TypeScript类型定义,新增自定义样式、位置、时长等可配置项
  3. 提升zIndex至9999,解决OpenHarmony原生组件层级冲突问题
  4. 结合pointerEvents+disabled+stopPropagation,彻底解决触摸穿透
  5. 适配OpenHarmony安全区域,兼容刘海屏、挖孔屏等多设备
  6. 新增内置默认UI,开箱即用,减少重复开发
  7. 优化内存管理,清除定时器、停止动画,避免内存泄漏
  8. 按类型划分专属样式,符合OpenHarmony平台交互规范
  9. 新增动画并行执行,提升动画流畅度,减少JS桥接耗时
  10. 简化使用方式,支持自定义内容覆盖默认内容,灵活性更高

项目源码

完整优化版代码已开源,包含组件封装完整演示样式适配全量代码,可直接在OpenHarmony 6.0.0项目中复用:

扩展开发建议

  1. 全局注册:可通过React Context将Overlay注册为全局组件,无需重复引入
  2. 自定义动画:暴露动画配置项,支持自定义动画时长、缓动函数
  3. 主题适配:结合OpenHarmony主题系统,实现遮罩层样式的主题切换
  4. 手势支持 :为ActionSheet添加下滑关闭手势,提升交互体验
  5. 多遮罩管理:实现遮罩层队列,支持多个遮罩层按顺序显示/隐藏
  6. 无障碍适配 :添加accessibilityLabel,适配OpenHarmony无障碍功能

✨ 坚持用 清晰的图解 +易懂的硬件架构 + 硬件解析, 让每个知识点都 简单明了 !

🚀 个人主页一只大侠的侠 · CSDN

💬 座右铭 : "所谓成功就是以自己的方式度过一生。"

相关推荐
嵌入式×边缘AI:打怪升级日志2 小时前
第十一章:主控访问多个传感器(Modbus 网关/桥接器设计)
开发语言·javascript·ecmascript
Highcharts.js2 小时前
Highcharts 3D漏斗图(Funnel 3D)完全指南:从模块加载到一文学会三维漏斗可视化
javascript·开发文档·highcharts·图表开发·漏斗图·3d 图表
我是伪码农2 小时前
Vue 2.11
前端·javascript·vue.js
wuhen_n2 小时前
JavaScript 防抖与节流进阶:从原理到实战
前端·javascript
打瞌睡的朱尤2 小时前
Vue day11商品详细页,加入购物车,购物车
前端·javascript·vue.js
不会敲代码12 小时前
从零开始学 React Hooks:useState 与 useEffect 核心解析
react.js
SuperEugene2 小时前
《对象与解构赋值:接口数据解包的 10 个常见写法》
前端·javascript
Never_Satisfied3 小时前
在JavaScript中,如何给字符串数组中的元素去重
开发语言·javascript·ecmascript
一只大侠的侠3 小时前
HarmonyOS实战:React Native实现Popover弹出位置精准控制
react native·华为·harmonyos