在React Native中开发针对华为鸿蒙操作系统(HarmonyOS)的应用时,通常需要使用华为的HarmonyOS SDK和开发工具。华为提供了专门的API和组件库,用以支持在HarmonyOS设备上开发应用。对于使用React Native开发的应用,如果你想集成华为特有的功能或者使用特定的HarmonyOS组件,你可以通过以下步骤进行:

  1. 设置开发环境

确保你的开发环境已经安装了React Native CLI、Node.js以及华为的HarmonyOS SDK。

  1. 配置项目
  • 安装华为移动服务SDK:在你的React Native项目中,可以通过npm或yarn安装华为移动服务SDK。

    npm install @hms-react-native/hms-plugin
    
  • 配置OpenHarmony项目:在你的OpenHarmony/app/build.gradle文件中添加华为的依赖和配置。

    dependencies {
        implementation 'com.huawei.hms:base:5.1.0.300'
        // 其他必要的依赖
    }
    
  1. 使用HarmonyOS特定组件
  • 动作面板(ActionSheet):华为提供了一个名为HMSActionSheet的组件,用于实现类似iOS的ActionSheet功能。

    • 首先,确保在你的项目中已经正确集成了华为移动服务SDK。

    • 然后,你可以通过以下方式使用HMSActionSheet

      import { HMSActionSheet } from '@hms-react-native/hms-plugin';
      
      const showActionSheet = () => {
        HMSActionSheet.show({
          options: ['选项1', '选项2', '取消'],
          cancelButtonIndex: 2, // 取消按钮的索引,从0开始计数
          title: '请选择', // 可选标题
        }, (buttonIndex) => {
          console.log('你选择了: ', buttonIndex); // 输出选择的索引
        });
      };
      
  1. 测试和调试
  • 在实际设备或使用华为提供的模拟器进行测试,确保所有功能正常工作。
  • 使用华为的开发者工具和日志系统来调试应用。
  1. 发布应用
  • 在应用准备好后,使用华为的HarmonyOS AppGallery Connect进行应用的发布和分发。

通过上述步骤,你可以在React Native应用中集成并使用HarmonyOS特有的功能,如动作面板等。确保遵循华为的官方文档和指南,以获取最新的API信息和最佳实践。同时,注意检查React Native社区和华为开发者论坛,以获取更多关于React Native与HarmonyOS集成的最新信息和帮助。


真实项目组件案列场景:

import React, { useState } from 'react';
import { View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity, Modal, Animated, Easing } from 'react-native';

// Simple Icon Component using Unicode symbols
interface IconProps {
  name: string;
  size?: number;
  color?: string;
  style?: object;
}

const Icon: React.FC<IconProps> = ({ 
  name, 
  size = 24, 
  color = '#333333',
  style 
}) => {
  const getIconSymbol = () => {
    switch (name) {
      case 'share': return '📤';
      case 'delete': return '🗑️';
      case 'edit': return '✏️';
      case 'copy': return '📋';
      case 'download': return '📥';
      case 'favorite': return '❤️';
      case 'more': return '⋯';
      case 'camera': return '📷';
      case 'gallery': return '🖼️';
      case 'document': return '📄';
      case 'link': return '🔗';
      case 'qr': return '🔲';
      case 'settings': return '⚙️';
      case 'info': return 'ℹ️';
      case 'close': return '✕';
      default: return '⋯';
    }
  };

  return (
    <View style={[{ width: size, height: size, justifyContent: 'center', alignItems: 'center' }, style]}>
      <Text style={{ fontSize: size * 0.8, color, includeFontPadding: false, textAlign: 'center' }}>
        {getIconSymbol()}
      </Text>
    </View>
  );
};

// Action Sheet Component
interface ActionSheetProps {
  visible: boolean;
  onClose: () => void;
  actions: {
    title: string;
    icon?: string;
    color?: string;
    disabled?: boolean;
    destructive?: boolean;
  }[];
  title?: string;
  message?: string;
  cancelText?: string;
}

const ActionSheet: React.FC<ActionSheetProps> = ({ 
  visible, 
  onClose, 
  actions, 
  title, 
  message,
  cancelText = '取消'
}) => {
  const [slideAnim] = useState(new Animated.Value(Dimensions.get('window').height));

  React.useEffect(() => {
    if (visible) {
      Animated.timing(slideAnim, {
        toValue: 0,
        duration: 300,
        easing: Easing.out(Easing.ease),
        useNativeDriver: true
      }).start();
    } else {
      Animated.timing(slideAnim, {
        toValue: Dimensions.get('window').height,
        duration: 250,
        useNativeDriver: true
      }).start();
    }
  }, [visible]);

  const handleActionPress = (index: number) => {
    if (actions[index].disabled) return;
    onClose();
    // In a real app, you would call the action's onPress handler here
  };

  return (
    <Modal
      visible={visible}
      transparent
      animationType="none"
      onRequestClose={onClose}
    >
      <TouchableOpacity 
        style={styles.overlay}
        onPress={onClose}
        activeOpacity={1}
      >
        <Animated.View 
          style={[
            styles.actionSheetContainer,
            { transform: [{ translateY: slideAnim }] }
          ]}
        >
          {title && (
            <View style={styles.header}>
              <Text style={styles.title}>{title}</Text>
              {message && <Text style={styles.message}>{message}</Text>}
            </View>
          )}
          
          <View style={styles.actionsContainer}>
            {actions.map((action, index) => (
              <TouchableOpacity
                key={index}
                style={[
                  styles.actionItem,
                  action.destructive && styles.destructiveAction,
                  action.disabled && styles.disabledAction
                ]}
                onPress={() => handleActionPress(index)}
                disabled={action.disabled}
                activeOpacity={action.disabled ? 1 : 0.7}
              >
                {action.icon && (
                  <Icon 
                    name={action.icon} 
                    size={24} 
                    color={
                      action.disabled 
                        ? '#cccccc' 
                        : action.destructive 
                          ? '#ff4d4f' 
                          : action.color || '#1890ff'
                    } 
                    style={styles.actionIcon}
                  />
                )}
                <Text 
                  style={[
                    styles.actionText,
                    action.destructive && styles.destructiveText,
                    action.disabled && styles.disabledText,
                    { color: action.color || (action.destructive ? '#ff4d4f' : '#262626') }
                  ]}
                >
                  {action.title}
                </Text>
              </TouchableOpacity>
            ))}
          </View>
          
          <View style={styles.separator} />
          
          <TouchableOpacity
            style={styles.cancelButton}
            onPress={onClose}
          >
            <Text style={styles.cancelText}>{cancelText}</Text>
          </TouchableOpacity>
        </Animated.View>
      </TouchableOpacity>
    </Modal>
  );
};

// Main App Component
const ActionSheetComponentApp = () => {
  const [basicVisible, setBasicVisible] = useState(false);
  const [withTitleVisible, setWithTitleVisible] = useState(false);
  const [destructiveVisible, setDestructiveVisible] = useState(false);
  const [iconVisible, setIconVisible] = useState(false);

  const basicActions = [
    { title: '选项一' },
    { title: '选项二' },
    { title: '选项三' },
    { title: '选项四' }
  ];

  const titledActions = [
    { title: '分享到朋友圈', icon: 'share' },
    { title: '发送给朋友', icon: 'send' },
    { title: '复制链接', icon: 'copy' },
    { title: '二维码', icon: 'qr' }
  ];

  const destructiveActions = [
    { title: '编辑', icon: 'edit' },
    { title: '收藏', icon: 'favorite' },
    { title: '下载', icon: 'download' },
    { title: '删除', icon: 'delete', destructive: true }
  ];

  const iconActions = [
    { title: '拍照', icon: 'camera', color: '#1890ff' },
    { title: '从相册选择', icon: 'gallery', color: '#52c41a' },
    { title: '新建文档', icon: 'document', color: '#722ed1' },
    { title: '链接分享', icon: 'link', color: '#fa8c16' },
    { title: '更多', icon: 'more', color: '#13c2c2' }
  ];

  return (
    <ScrollView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>动作面板组件</Text>
        <Text style={styles.headerSubtitle}>美观实用的操作菜单控件</Text>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>基础用法</Text>
        <View style={styles.actionGroupsContainer}>
          <TouchableOpacity 
            style={styles.actionButton}
            onPress={() => setBasicVisible(true)}
          >
            <Text style={styles.actionButtonText}>显示基础动作面板</Text>
          </TouchableOpacity>
          
          <TouchableOpacity 
            style={styles.actionButton}
            onPress={() => setWithTitleVisible(true)}
          >
            <Text style={styles.actionButtonText}>带标题的动作面板</Text>
          </TouchableOpacity>
          
          <TouchableOpacity 
            style={styles.actionButton}
            onPress={() => setDestructiveVisible(true)}
          >
            <Text style={styles.actionButtonText}>带危险操作</Text>
          </TouchableOpacity>
          
          <TouchableOpacity 
            style={styles.actionButton}
            onPress={() => setIconVisible(true)}
          >
            <Text style={styles.actionButtonText}>带图标的动作面板</Text>
          </TouchableOpacity>
        </View>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>应用场景</Text>
        <View style={styles.scenariosContainer}>
          <View style={styles.scenarioCard}>
            <Icon name="share" size={32} color="#1890ff" style={styles.scenarioIcon} />
            <Text style={styles.scenarioTitle}>分享操作</Text>
            <Text style={styles.scenarioDesc}>社交分享菜单</Text>
          </View>
          
          <View style={styles.scenarioCard}>
            <Icon name="edit" size={32} color="#52c41a" style={styles.scenarioIcon} />
            <Text style={styles.scenarioTitle}>编辑操作</Text>
            <Text style={styles.scenarioDesc}>内容编辑选项</Text>
          </View>
          
          <View style={styles.scenarioCard}>
            <Icon name="delete" size={32} color="#ff4d4f" style={styles.scenarioIcon} />
            <Text style={styles.scenarioTitle}>删除操作</Text>
            <Text style={styles.scenarioDesc}>危险操作确认</Text>
          </View>
        </View>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>功能演示</Text>
        <View style={styles.demosContainer}>
          <View style={styles.demoItem}>
            <Icon name="more" size={24} color="#1890ff" style={styles.demoIcon} />
            <View>
              <Text style={styles.demoTitle}>底部弹出</Text>
              <Text style={styles.demoDesc}>从屏幕底部滑出的操作面板</Text>
            </View>
          </View>
          
          <View style={styles.demoItem}>
            <Icon name="share" size={24} color="#52c41a" style={styles.demoIcon} />
            <View>
              <Text style={styles.demoTitle}>图标支持</Text>
              <Text style={styles.demoDesc}>支持自定义图标和颜色</Text>
            </View>
          </View>
          
          <View style={styles.demoItem}>
            <Icon name="delete" size={24} color="#ff4d4f" style={styles.demoIcon} />
            <View>
              <Text style={styles.demoTitle}>危险操作</Text>
              <Text style={styles.demoDesc}>特殊标识危险操作项</Text>
            </View>
          </View>
        </View>
      </View>
      
      <View style={styles.usageSection}>
        <Text style={styles.sectionTitle}>使用方法</Text>
        <View style={styles.codeBlock}>
          <Text style={styles.codeText}>{'<ActionSheet'}</Text>
          <Text style={styles.codeText}>  visible={'{isVisible}'}</Text>
          <Text style={styles.codeText}>  onClose={'{setVisible}'}</Text>
          <Text style={styles.codeText}>  actions={'{actions}'}</Text>
          <Text style={styles.codeText}>{'/>'}</Text>
        </View>
        <Text style={styles.description}>
          ActionSheet组件提供了完整的动作面板功能,包括底部弹出、图标支持、危险操作标识等。
          通过visible控制显示状态,onClose处理关闭事件,actions定义操作项。
        </Text>
      </View>
      
      <View style={styles.featuresSection}>
        <Text style={styles.sectionTitle}>功能特性</Text>
        <View style={styles.featuresList}>
          <View style={styles.featureItem}>
            <Icon name="more" size={20} color="#1890ff" style={styles.featureIcon} />
            <Text style={styles.featureText}>底部弹出</Text>
          </View>
          <View style={styles.featureItem}>
            <Icon name="share" size={20} color="#52c41a" style={styles.featureIcon} />
            <Text style={styles.featureText}>图标支持</Text>
          </View>
          <View style={styles.featureItem}>
            <Icon name="delete" size={20} color="#ff4d4f" style={styles.featureIcon} />
            <Text style={styles.featureText}>危险操作</Text>
          </View>
          <View style={styles.featureItem}>
            <Icon name="settings" size={20} color="#722ed1" style={styles.featureIcon} />
            <Text style={styles.featureText}>状态控制</Text>
          </View>
        </View>
      </View>
      
      <View style={styles.footer}>
        <Text style={styles.footerText}>© 2023 动作面板组件 | 现代化UI组件库</Text>
      </View>
      
      {/* Action Sheets */}
      <ActionSheet
        visible={basicVisible}
        onClose={() => setBasicVisible(false)}
        actions={basicActions}
      />
      
      <ActionSheet
        visible={withTitleVisible}
        onClose={() => setWithTitleVisible(false)}
        actions={titledActions}
        title="分享到"
        message="选择分享方式"
      />
      
      <ActionSheet
        visible={destructiveVisible}
        onClose={() => setDestructiveVisible(false)}
        actions={destructiveActions}
        title="操作选项"
      />
      
      <ActionSheet
        visible={iconVisible}
        onClose={() => setIconVisible(false)}
        actions={iconActions}
        title="选择操作"
        cancelText="取消操作"
      />
    </ScrollView>
  );
};

const { width, height } = Dimensions.get('window');

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  header: {
    backgroundColor: '#ffffff',
    paddingVertical: 30,
    paddingHorizontal: 20,
    marginBottom: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#e8e8e8',
  },
  headerTitle: {
    fontSize: 28,
    fontWeight: '700',
    color: '#262626',
    textAlign: 'center',
    marginBottom: 5,
  },
  headerSubtitle: {
    fontSize: 16,
    color: '#8c8c8c',
    textAlign: 'center',
  },
  section: {
    marginBottom: 25,
  },
  sectionTitle: {
    fontSize: 20,
    fontWeight: '700',
    color: '#262626',
    paddingHorizontal: 20,
    paddingBottom: 15,
  },
  actionGroupsContainer: {
    backgroundColor: '#ffffff',
    marginHorizontal: 15,
    borderRadius: 12,
    padding: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
    marginBottom: 10,
  },
  actionButton: {
    backgroundColor: '#f0f5ff',
    borderRadius: 8,
    paddingVertical: 15,
    paddingHorizontal: 20,
    marginBottom: 15,
    borderWidth: 1,
    borderColor: '#d9e6ff',
  },
  actionButtonLast: {
    marginBottom: 0,
  },
  actionButtonText: {
    fontSize: 16,
    color: '#1890ff',
    fontWeight: '500',
    textAlign: 'center',
  },
  scenariosContainer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    paddingHorizontal: 15,
  },
  scenarioCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 20,
    width: (width - 60) / 3,
    alignItems: 'center',
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
  },
  scenarioIcon: {
    marginBottom: 15,
  },
  scenarioTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#262626',
    marginBottom: 5,
  },
  scenarioDesc: {
    fontSize: 14,
    color: '#8c8c8c',
    textAlign: 'center',
  },
  demosContainer: {
    backgroundColor: '#ffffff',
    marginHorizontal: 15,
    borderRadius: 15,
    padding: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
  },
  demoItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 20,
  },
  demoItemLast: {
    marginBottom: 0,
  },
  demoIcon: {
    marginRight: 15,
  },
  demoTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#262626',
    marginBottom: 3,
  },
  demoDesc: {
    fontSize: 14,
    color: '#8c8c8c',
  },
  usageSection: {
    backgroundColor: '#ffffff',
    marginHorizontal: 15,
    borderRadius: 15,
    padding: 20,
    marginBottom: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
  },
  codeBlock: {
    backgroundColor: '#2d3748',
    borderRadius: 8,
    padding: 15,
    marginBottom: 15,
  },
  codeText: {
    fontFamily: 'monospace',
    color: '#e2e8f0',
    fontSize: 14,
    lineHeight: 22,
  },
  description: {
    fontSize: 15,
    color: '#595959',
    lineHeight: 22,
  },
  featuresSection: {
    backgroundColor: '#ffffff',
    marginHorizontal: 15,
    borderRadius: 15,
    padding: 20,
    marginBottom: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
  },
  featuresList: {
    paddingLeft: 10,
  },
  featureItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 15,
  },
  featureIcon: {
    marginRight: 15,
  },
  featureText: {
    fontSize: 16,
    color: '#262626',
  },
  footer: {
    paddingVertical: 20,
    alignItems: 'center',
  },
  footerText: {
    color: '#bfbfbf',
    fontSize: 14,
  },
  // Action Sheet Styles
  overlay: {
    flex: 1,
    backgroundColor: 'rgba(0, 0, 0, 0.5)',
    justifyContent: 'flex-end',
  },
  actionSheetContainer: {
    backgroundColor: '#ffffff',
    borderTopLeftRadius: 20,
    borderTopRightRadius: 20,
    maxHeight: height * 0.7,
    paddingTop: 15,
  },
  header: {
    paddingHorizontal: 20,
    paddingBottom: 15,
    alignItems: 'center',
  },
  title: {
    fontSize: 18,
    fontWeight: '600',
    color: '#262626',
    marginBottom: 5,
  },
  message: {
    fontSize: 14,
    color: '#8c8c8c',
  },
  actionsContainer: {
    paddingHorizontal: 10,
  },
  actionItem: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingVertical: 15,
    paddingHorizontal: 20,
  },
  destructiveAction: {
    backgroundColor: '#fff1f0',
  },
  disabledAction: {
    opacity: 0.5,
  },
  actionIcon: {
    marginRight: 15,
  },
  actionText: {
    fontSize: 18,
    color: '#262626',
    fontWeight: '400',
  },
  destructiveText: {
    color: '#ff4d4f',
    fontWeight: '500',
  },
  disabledText: {
    color: '#cccccc',
  },
  separator: {
    height: 10,
    backgroundColor: '#f5f5f5',
  },
  cancelButton: {
    paddingVertical: 18,
    alignItems: 'center',
  },
  cancelText: {
    fontSize: 18,
    color: '#1890ff',
    fontWeight: '500',
  },
});

export default ActionSheetComponentApp;

从鸿蒙ArkUI开发角度分析,这段React Native ActionSheet组件的实现逻辑体现了鸿蒙模态交互的核心设计理念。ActionSheet作为底部弹出的操作菜单,在鸿蒙中对应着CustomDialogController的实现模式,通过@State装饰器管理可见性状态,当visible属性变更时触发动画效果。

Icon组件通过Unicode符号映射实现图标显示,这与鸿蒙的Symbol组件设计思路一致。在鸿蒙中,图标资源通过ResourceManager统一管理,支持多分辨率适配和主题切换。getIconSymbol方法中的switch-case逻辑对应鸿蒙的图标资源映射机制。

动画系统采用Animated API实现平移动画,slideAnim通过useState初始化为屏幕高度,在useEffect中根据visible状态触发不同的动画序列。这与鸿蒙的animateTo方法对应,支持duration、easing等动画参数配置。useNativeDriver设置为true对应鸿蒙的动画硬件加速优化。

在这里插入图片描述

组件结构采用Modal作为容器,实现透明背景和独立渲染层级。TouchableOpacity作为交互元素,通过activeOpacity控制点击反馈效果,这与鸿蒙的Button组件点击态设计相似。overlay区域点击关闭的功能对应鸿蒙的onClick事件处理。

actions数组的渲染采用map方法遍历,每个actionItem支持icon、title、color、disabled、destructive等属性配置。状态控制逻辑通过条件样式实现,disabled状态时降低透明度并阻止点击事件,destructive状态时显示警告色。

布局系统采用Flexbox实现垂直排列,header区域条件渲染标题和消息,actionsContainer承载操作列表,separator作为视觉分隔,cancelButton提供取消操作。这种结构与鸿蒙的Column容器布局完全对应。

事件处理机制通过handleActionPress函数实现,当action被点击时首先检查disabled状态,然后调用onClose关闭菜单并触发对应的业务逻辑。这种设计模式在鸿蒙应用中广泛用于分享、设置、删除确认等场景。


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

在这里插入图片描述

最后运行效果图如下显示:

请添加图片描述

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐