【精通篇】打造React Native鸿蒙跨平台开发高级复合组件库开发系列:Dialog 弹出框(用于消息提示、消息确认)
本文介绍了在React Native中开发鸿蒙(HarmonyOS)应用的关键步骤。首先需要从华为开发者联盟官网下载并集成HarmonyOS SDK到React Native项目中。其次,通过创建原生模块来调用鸿蒙特定API,如示例中的HarmonyModule。文章还详细说明了测试调试方法,推荐使用华为DevEco Studio工具。最后重点介绍了鸿蒙特色功能的适配方案,包括暗黑模式、权限管理和
在 React Native 中进行鸿蒙(HarmonyOS)开发时,由于鸿蒙操作系统是基于华为自家开发的 HarmonyOS 系统,它与 OpenHarmony 系统有一定的相似性,但也存在一些差异。如果你想在 React Native 应用中加入鸿蒙特有的功能或者适配鸿蒙系统,你可以通过以下步骤来实现:
- 环境准备
确保你的开发环境支持鸿蒙开发。华为提供了一个 HarmonyOS SDK,你可以通过华为开发者联盟官网下载并集成到你的 React Native 项目中。
-
集成 HarmonyOS SDK
-
下载 SDK:从华为开发者网站下载 HarmonyOS SDK。
-
配置项目:解压 SDK,按照文档指导在你的 React Native 项目中配置 SDK。
-
使用鸿蒙特定的组件或API
由于 React Native 主要是基于 OpenHarmony 和 iOS,直接使用鸿蒙特定的组件或 API 可能需要一些额外的步骤。你可以通过以下方式实现:
-
使用原生模块:你可以创建一个原生模块来调用鸿蒙的特定 API。例如,使用 Java 或 Kotlin 编写一个原生模块,然后在 React Native 中调用它。
// 在 OpenHarmony 原生模块中 public class HarmonyModule extends ReactContextBaseJavaModule { @ReactMethod public void showHarmonyDialog(String message) { // 调用鸿蒙特定的对话框API } }然后在 React Native 中调用:
import { NativeModules } from 'react-native'; const { HarmonyModule } = NativeModules; HarmonyModule.showHarmonyDialog('Hello Harmony');
- 测试和调试
在开发过程中,确保你能够在鸿蒙设备或模拟器上测试你的应用。使用华为提供的开发工具(如 DevEco Studio)来部署和调试你的应用。
- 适配鸿蒙特有的功能
- 暗黑模式适配:确保你的应用能够适配鸿蒙系统的暗黑模式。在 React Native 中,你可以通过修改主题或者使用条件样式来实现。
- 权限管理:使用鸿蒙的权限管理 API 来请求和检查权限。
- 多窗口支持:如果你的应用需要支持多窗口,确保你的界面布局和交互能够适应多窗口环境。
真实实际案列演示效果:
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 'success': return '✅';
case 'error': return '❌';
case 'warning': return '⚠️';
case 'info': return 'ℹ️';
case 'question': return '❓';
case 'close': return '✕';
case 'check': return '✓';
case 'cancel': return '✕';
case 'delete': return '🗑️';
case 'edit': return '✏️';
case 'download': return '📥';
case 'upload': 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>
);
};
// Dialog Component
interface DialogProps {
visible: boolean;
onClose: () => void;
title?: string;
message: string;
type?: 'alert' | 'confirm' | 'prompt' | 'custom';
confirmText?: string;
cancelText?: string;
onConfirm?: () => void;
onCancel?: () => void;
showIcon?: boolean;
iconType?: 'success' | 'error' | 'warning' | 'info' | 'question';
actions?: {
text: string;
onPress: () => void;
type?: 'primary' | 'secondary' | 'danger';
}[];
}
const Dialog: React.FC<DialogProps> = ({
visible,
onClose,
title,
message,
type = 'alert',
confirmText = '确定',
cancelText = '取消',
onConfirm,
onCancel,
showIcon = true,
iconType = 'info',
actions
}) => {
const [scaleAnim] = useState(new Animated.Value(0.8));
const [opacityAnim] = useState(new Animated.Value(0));
React.useEffect(() => {
if (visible) {
Animated.parallel([
Animated.timing(scaleAnim, {
toValue: 1,
duration: 200,
easing: Easing.out(Easing.ease),
useNativeDriver: true
}),
Animated.timing(opacityAnim, {
toValue: 1,
duration: 200,
useNativeDriver: true
})
]).start();
} else {
Animated.parallel([
Animated.timing(scaleAnim, {
toValue: 0.8,
duration: 150,
useNativeDriver: true
}),
Animated.timing(opacityAnim, {
toValue: 0,
duration: 150,
useNativeDriver: true
})
]).start();
}
}, [visible]);
const getIconColor = () => {
switch (iconType) {
case 'success': return '#52c41a';
case 'error': return '#ff4d4f';
case 'warning': return '#faad14';
case 'question': return '#1890ff';
default: return '#1890ff';
}
};
const handleConfirm = () => {
onConfirm && onConfirm();
onClose();
};
const handleCancel = () => {
onCancel && onCancel();
onClose();
};
const renderActions = () => {
if (actions) {
return (
<View style={styles.actionsContainer}>
{actions.map((action, index) => (
<TouchableOpacity
key={index}
style={[
styles.actionButton,
action.type === 'primary' && styles.primaryButton,
action.type === 'danger' && styles.dangerButton,
action.type === 'secondary' && styles.secondaryButton,
index !== actions.length - 1 && styles.actionButtonSeparator
]}
onPress={() => {
action.onPress();
onClose();
}}
>
<Text
style={[
styles.actionButtonText,
action.type === 'primary' && styles.primaryButtonText,
action.type === 'danger' && styles.dangerButtonText,
action.type === 'secondary' && styles.secondaryButtonText
]}
>
{action.text}
</Text>
</TouchableOpacity>
))}
</View>
);
}
switch (type) {
case 'confirm':
return (
<View style={styles.actionsContainer}>
<TouchableOpacity
style={[styles.actionButton, styles.secondaryButton]}
onPress={handleCancel}
>
<Text style={[styles.actionButtonText, styles.secondaryButtonText]}>
{cancelText}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.actionButton, styles.primaryButton]}
onPress={handleConfirm}
>
<Text style={[styles.actionButtonText, styles.primaryButtonText]}>
{confirmText}
</Text>
</TouchableOpacity>
</View>
);
default:
return (
<TouchableOpacity
style={[styles.actionButton, styles.primaryButton]}
onPress={handleConfirm}
>
<Text style={[styles.actionButtonText, styles.primaryButtonText]}>
{confirmText}
</Text>
</TouchableOpacity>
);
}
};
return (
<Modal
visible={visible}
transparent
animationType="none"
onRequestClose={onClose}
>
<TouchableOpacity
style={styles.overlay}
onPress={type === 'alert' ? handleConfirm : handleCancel}
activeOpacity={1}
>
<Animated.View
style={[
styles.dialogContainer,
{
transform: [{ scale: scaleAnim }],
opacity: opacityAnim
}
]}
>
<View style={styles.dialogContent}>
{(title || showIcon) && (
<View style={styles.header}>
{showIcon && (
<Icon
name={iconType}
size={32}
color={getIconColor()}
style={styles.headerIcon}
/>
)}
{title && <Text style={styles.title}>{title}</Text>}
</View>
)}
<View style={styles.messageContainer}>
<Text style={styles.message}>{message}</Text>
</View>
{renderActions()}
</View>
</Animated.View>
</TouchableOpacity>
</Modal>
);
};
// Main App Component
const DialogComponentApp = () => {
const [alertView, setAlertView] = useState(false);
const [confirmView, setConfirmView] = useState(false);
const [warningView, setWarningView] = useState(false);
const [customView, setCustomView] = useState(false);
const [multiActionView, setMultiActionView] = useState(false);
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.dialogGroupsContainer}>
<TouchableOpacity
style={styles.dialogButton}
onPress={() => setAlertView(true)}
>
<Text style={styles.dialogButtonText}>基础弹出框</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.dialogButton}
onPress={() => setConfirmView(true)}
>
<Text style={styles.dialogButtonText}>确认弹出框</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.dialogButton}
onPress={() => setWarningView(true)}
>
<Text style={styles.dialogButtonText}>警告弹出框</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.dialogButton}
onPress={() => setCustomView(true)}
>
<Text style={styles.dialogButtonText}>自定义弹出框</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.dialogButton}
onPress={() => setMultiActionView(true)}
>
<Text style={styles.dialogButtonText}>多操作弹出框</Text>
</TouchableOpacity>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>应用场景</Text>
<View style={styles.scenariosContainer}>
<View style={styles.scenarioCard}>
<Icon name="info" size={32} color="#1890ff" style={styles.scenarioIcon} />
<Text style={styles.scenarioTitle}>信息提示</Text>
<Text style={styles.scenarioDesc}>普通信息展示</Text>
</View>
<View style={styles.scenarioCard}>
<Icon name="question" size={32} color="#faad14" style={styles.scenarioIcon} />
<Text style={styles.scenarioTitle}>操作确认</Text>
<Text style={styles.scenarioDesc}>重要操作确认</Text>
</View>
<View style={styles.scenarioCard}>
<Icon name="error" 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="info" 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="check" 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="question" size={24} color="#faad14" 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}>{'<Dialog'}</Text>
<Text style={styles.codeText}> visible={'{isVisible}'}</Text>
<Text style={styles.codeText}> onClose={'{setVisible}'}</Text>
<Text style={styles.codeText}> title="标题"</Text>
<Text style={styles.codeText}> message="内容信息"{'\n'}/></Text>
</View>
<Text style={styles.description}>
Dialog组件提供了完整的弹出框功能,包括信息提示、操作确认、警告提示等。
通过visible控制显示状态,onClose处理关闭事件,支持自定义标题、内容和操作按钮。
</Text>
</View>
<View style={styles.featuresSection}>
<Text style={styles.sectionTitle}>功能特性</Text>
<View style={styles.featuresList}>
<View style={styles.featureItem}>
<Icon name="info" size={20} color="#1890ff" style={styles.featureIcon} />
<Text style={styles.featureText}>多种类型</Text>
</View>
<View style={styles.featureItem}>
<Icon name="check" size={20} color="#52c41a" style={styles.featureIcon} />
<Text style={styles.featureText}>图标支持</Text>
</View>
<View style={styles.featureItem}>
<Icon name="question" size={20} color="#faad14" style={styles.featureIcon} />
<Text style={styles.featureText}>自定义操作</Text>
</View>
<View style={styles.featureItem}>
<Icon name="error" size={20} color="#ff4d4f" style={styles.featureIcon} />
<Text style={styles.featureText}>状态反馈</Text>
</View>
</View>
</View>
<View style={styles.footer}>
<Text style={styles.footerText}>© 2023 弹出框组件 | 现代化UI组件库</Text>
</View>
{/* Dialogs */}
<Dialog
visible={alertView}
onClose={() => setAlertView(false)}
title="信息提示"
message="这是一个基础的信息提示弹出框,用于向用户展示重要信息。"
iconType="info"
onConfirm={() => console.log('Alert confirmed')}
/>
<Dialog
visible={confirmView}
onClose={() => setConfirmView(false)}
title="操作确认"
message="您确定要执行此操作吗?此操作无法撤销。"
type="confirm"
iconType="question"
onConfirm={() => console.log('Confirmed')}
onCancel={() => console.log('Cancelled')}
/>
<Dialog
visible={warningView}
onClose={() => setWarningView(false)}
title="警告提示"
message="您的操作可能导致数据丢失,请谨慎操作!"
iconType="warning"
type="confirm"
confirmText="继续操作"
cancelText="取消"
onConfirm={() => console.log('Warning confirmed')}
onCancel={() => console.log('Warning cancelled')}
/>
<Dialog
visible={customView}
onClose={() => setCustomView(false)}
title="自定义操作"
message="您可以自定义弹出框的按钮和操作,满足不同的业务需求。"
iconType="success"
actions={[
{
text: '查看详情',
onPress: () => console.log('View details'),
type: 'secondary'
},
{
text: '立即处理',
onPress: () => console.log('Handle now'),
type: 'primary'
}
]}
/>
<Dialog
visible={multiActionView}
onClose={() => setMultiActionView(false)}
title="多操作选择"
message="请选择您要执行的操作,不同的操作会产生不同的结果。"
iconType="question"
actions={[
{
text: '编辑',
onPress: () => console.log('Edit'),
type: 'secondary'
},
{
text: '下载',
onPress: () => console.log('Download'),
type: 'secondary'
},
{
text: '删除',
onPress: () => console.log('Delete'),
type: 'danger'
}
]}
/>
</ScrollView>
);
};
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fffbe6',
},
header: {
backgroundColor: '#ffffff',
paddingVertical: 30,
paddingHorizontal: 20,
marginBottom: 10,
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0',
},
headerTitle: {
fontSize: 28,
fontWeight: '700',
color: '#d46b08',
textAlign: 'center',
marginBottom: 5,
},
headerSubtitle: {
fontSize: 16,
color: '#d4883a',
textAlign: 'center',
},
section: {
marginBottom: 25,
},
sectionTitle: {
fontSize: 20,
fontWeight: '700',
color: '#d46b08',
paddingHorizontal: 20,
paddingBottom: 15,
},
dialogGroupsContainer: {
backgroundColor: '#ffffff',
marginHorizontal: 15,
borderRadius: 12,
padding: 20,
elevation: 3,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 4,
marginBottom: 10,
},
dialogButton: {
backgroundColor: '#fff5e6',
borderRadius: 8,
paddingVertical: 15,
paddingHorizontal: 20,
marginBottom: 15,
borderWidth: 1,
borderColor: '#ffd699',
},
dialogButtonLast: {
marginBottom: 0,
},
dialogButtonText: {
fontSize: 16,
color: '#d46b08',
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: '#5a3b1c',
marginBottom: 5,
},
scenarioDesc: {
fontSize: 14,
color: '#d4883a',
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: '#5a3b1c',
marginBottom: 3,
},
demoDesc: {
fontSize: 14,
color: '#d4883a',
},
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: '#4b2e1e',
borderRadius: 8,
padding: 15,
marginBottom: 15,
},
codeText: {
fontFamily: 'monospace',
color: '#f5e4d7',
fontSize: 14,
lineHeight: 22,
},
description: {
fontSize: 15,
color: '#8c6a4d',
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: '#5a3b1c',
},
footer: {
paddingVertical: 20,
alignItems: 'center',
},
footerText: {
color: '#d4b58a',
fontSize: 14,
},
// Dialog Styles
overlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.6)',
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
dialogContainer: {
width: '100%',
maxWidth: 350,
},
dialogContent: {
backgroundColor: '#ffffff',
borderRadius: 12,
overflow: 'hidden',
elevation: 10,
shadowColor: '#000',
shadowOffset: { width: 0, height: 5 },
shadowOpacity: 0.3,
shadowRadius: 10,
},
header: {
alignItems: 'center',
padding: 25,
paddingBottom: 15,
},
headerIcon: {
marginBottom: 15,
},
title: {
fontSize: 20,
fontWeight: '700',
color: '#5a3b1c',
textAlign: 'center',
},
messageContainer: {
paddingHorizontal: 25,
paddingBottom: 25,
},
message: {
fontSize: 16,
color: '#8c6a4d',
textAlign: 'center',
lineHeight: 24,
},
actionsContainer: {
flexDirection: 'row',
borderTopWidth: 1,
borderTopColor: '#f0f0f0',
},
actionButton: {
flex: 1,
paddingVertical: 18,
alignItems: 'center',
justifyContent: 'center',
},
primaryButton: {
backgroundColor: '#d46b08',
},
secondaryButton: {
backgroundColor: '#ffffff',
},
dangerButton: {
backgroundColor: '#ff4d4f',
},
actionButtonSeparator: {
borderRightWidth: 1,
borderRightColor: '#f0f0f0',
},
actionButtonText: {
fontSize: 16,
fontWeight: '600',
},
primaryButtonText: {
color: '#ffffff',
},
secondaryButtonText: {
color: '#d46b08',
},
dangerButtonText: {
color: '#ffffff',
},
});
export default DialogComponentApp;
从鸿蒙ArkUI开发角度深入分析这段React Native Dialog组件的代码逻辑:
该Dialog组件在鸿蒙中对应着CustomDialogController的实现模式,通过@State装饰器管理visible状态来控制对话框的显示与隐藏。当visible状态变更时,触发useEffect中的动画逻辑,这与鸿蒙的aboutToAppear和aboutToDisappear生命周期函数的作用相似。
动画系统采用Animated API实现复合动画效果,scaleAnim控制缩放变换,opacityAnim控制透明度变化。Animated.parallel实现多个动画的同步执行,Easing.out(Easing.ease)对应鸿蒙的Curve.EaseOut插值器。useNativeDriver设置为true表明启用原生动画驱动,这与鸿蒙的动画硬件加速机制对应。
Icon组件通过Unicode符号映射实现图标显示,getIconSymbol方法中的switch-case逻辑对应鸿蒙的图标资源管理。在鸿蒙中,图标通过Symbol组件显示,支持系统图标和自定义图标资源。getIconColor函数根据iconType返回对应的主题色,这与鸿蒙的动态主题色系统设计一致。

组件采用条件渲染策略,通过renderActions函数动态生成操作按钮区域。当actions数组存在时,采用map方法遍历渲染自定义操作按钮;否则根据type参数渲染默认的确认/取消按钮组合。这种设计模式在鸿蒙中通过@Builder装饰器实现类似的动态UI构建能力。
事件处理机制通过handleConfirm和handleCancel函数封装业务逻辑,确保在触发回调后自动调用onClose关闭对话框。这种设计保证了对话框状态的一致性,避免出现状态不同步的问题。
状态管理采用受控组件模式,所有状态变更都通过props传递,这与鸿蒙的@Prop装饰器数据流设计理念相符。组件支持多种对话框类型(alert/confirm/prompt/custom),通过type参数控制不同的交互模式。
布局结构采用Modal作为根容器,实现独立的渲染层级和背景遮罩。Animated.View承载对话框内容,通过transform和opacity样式属性绑定动画值,实现流畅的入场和退场动画效果。
打包
接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

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

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

更多推荐




所有评论(0)