【精通篇】打造React Native鸿蒙跨平台开发高级复合组件库开发系列:Field 输入框(表单中的输入框组件)
本文介绍如何在React Native中为鸿蒙设备实现时间选择功能。推荐使用react-native-datetimepicker库,该库在鸿蒙系统(基于Android)上兼容性良好。具体步骤包括:1) 安装该库;2) 在组件中导入并使用DateTimePicker,设置时间/日期模式、24小时制等参数;3) 处理平台差异,如Android原生组件可能存在的显示问题。注意事项包括检查组件兼容性、处
在React Native中实现一个时间选择器,你可以使用第三方库比如react-native-datetimepicker来在鸿蒙(HarmonyOS)设备上实现时间选择功能。鸿蒙操作系统基于Android系统,因此大多数Android的React Native组件和库都可以在鸿蒙设备上运行,包括时间选择器。
步骤 1: 安装 react-native-datetimepicker
首先,你需要安装react-native-datetimepicker库。在你的React Native项目中,打开终端并运行以下命令:
npm install @react-native-community/datetimepicker
或者,如果你使用yarn:
yarn add @react-native-community/datetimepicker
步骤 2: 导入并使用 DateTimePicker
在你的React Native组件中,你可以这样使用DateTimePicker:
import React, { useState } from 'react';
import { View, Button, Platform } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';
const TimePickerExample = () => {
const [date, setDate] = useState(new Date(1598051730000)); // 默认时间
const [mode, setMode] = useState('date'); // 'date' 或 'time'
const [show, setShow] = useState(false);
const onChange = (event, selectedDate) => {
const currentDate = selectedDate || date;
setShow(Platform.OS === 'ios'); // 只在iOS上显示,因为安卓原生组件存在问题
setDate(currentDate);
};
const showMode = (currentMode) => {
setShow(true);
setMode(currentMode);
};
const showDatepicker = () => {
showMode('date');
};
const showTimepicker = () => {
showMode('time');
};
return (
<View>
<Button title="Show date picker" onPress={showDatepicker} />
<Button title="Show time picker" onPress={showTimepicker} />
{show && (
<DateTimePicker
testID="dateTimePicker"
value={date}
mode={mode}
is24Hour={true} // 24小时制
display="default" // 默认显示模式,还有其他如'spinner', 'inline'等模式可选
onChange={onChange}
/>
)}
</View>
);
};
export default TimePickerExample;
注意事项:
- 平台兼容性:虽然大多数React Native组件在鸿蒙(HarmonyOS)上可以工作,但最好还是检查每个组件的文档,确保其在鸿蒙设备上的兼容性。对于日期和时间选择器,通常没有问题。
- 显示问题:在Android设备上,特别是在鸿蒙操作系统上,有时原生组件的显示可能会有所不同或存在问题。在这种情况下,可以考虑使用第三方库如
react-native-datetimepicker。在上述代码中,我们通过Platform.OS === 'ios'来避免在Android上显示时间选择器,因为原生组件可能存在问题。对于Android,我们使用第三方库来提供一致的用户体验。 - 权限:确保你的应用有访问时间和日期的权限。通常,这些权限在Android应用中是自动授予的,但在某些情况下,你可能需要在应用的
AndroidManifest.xml中明确请求它们。对于React Native应用,通常不需要手动添加这些权限。
通过以上步骤,你应该能够在React Native应用中为鸿蒙设备实现一个功能齐全的时间选择器。
真实组件案例演示:
import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions, Modal, Platform } 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 'clock': return '⏰';
case 'calendar': return '📅';
case 'time': return '⏱️';
case 'confirm': return '✅';
case 'cancel': return '❌';
case 'up': return '▲';
case 'down': 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>
);
};
// DatetimePicker Component
interface DatetimePickerProps {
mode?: 'date' | 'time' | 'datetime';
value?: Date;
onChange: (date: Date) => void;
placeholder?: string;
}
const DatetimePicker: React.FC<DatetimePickerProps> = ({
mode = 'datetime',
value = new Date(),
onChange,
placeholder = '请选择时间'
}) => {
const [visible, setVisible] = useState(false);
const [tempDate, setTempDate] = useState(value);
const openPicker = () => {
setTempDate(value);
setVisible(true);
};
const closePicker = () => {
setVisible(false);
};
const confirmSelection = () => {
onChange(tempDate);
closePicker();
};
const formatDate = (date: Date): string => {
if (mode === 'date') {
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日`;
} else if (mode === 'time') {
return `${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
} else {
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
}
};
const getDisplayText = (): string => {
return value ? formatDate(value) : placeholder;
};
// Number picker component for datetime selection
const NumberPicker = ({
value,
min,
max,
onChange,
label
}: {
value: number;
min: number;
max: number;
onChange: (value: number) => void;
label: string;
}) => {
const increment = () => {
if (value < max) {
onChange(value + 1);
}
};
const decrement = () => {
if (value > min) {
onChange(value - 1);
}
};
return (
<View style={styles.numberPickerContainer}>
<TouchableOpacity
style={styles.pickerButton}
onPress={increment}
disabled={value >= max}
>
<Icon name="up" size={16} color={value >= max ? '#ccc' : '#333'} />
</TouchableOpacity>
<View style={styles.pickerValueContainer}>
<Text style={styles.pickerValue}>{value.toString().padStart(2, '0')}</Text>
<Text style={styles.pickerLabel}>{label}</Text>
</View>
<TouchableOpacity
style={styles.pickerButton}
onPress={decrement}
disabled={value <= min}
>
<Icon name="down" size={16} color={value <= min ? '#ccc' : '#333'} />
</TouchableOpacity>
</View>
);
};
// Year picker
const YearPicker = () => (
<NumberPicker
value={tempDate.getFullYear()}
min={1900}
max={2100}
onChange={(year) => setTempDate(new Date(year, tempDate.getMonth(), tempDate.getDate(), tempDate.getHours(), tempDate.getMinutes()))}
label="年"
/>
);
// Month picker
const MonthPicker = () => (
<NumberPicker
value={tempDate.getMonth() + 1}
min={1}
max={12}
onChange={(month) => setTempDate(new Date(tempDate.getFullYear(), month - 1, tempDate.getDate(), tempDate.getHours(), tempDate.getMinutes()))}
label="月"
/>
);
// Day picker
const DayPicker = () => {
const daysInMonth = new Date(tempDate.getFullYear(), tempDate.getMonth() + 1, 0).getDate();
return (
<NumberPicker
value={tempDate.getDate()}
min={1}
max={daysInMonth}
onChange={(day) => setTempDate(new Date(tempDate.getFullYear(), tempDate.getMonth(), day, tempDate.getHours(), tempDate.getMinutes()))}
label="日"
/>
);
};
// Hour picker
const HourPicker = () => (
<NumberPicker
value={tempDate.getHours()}
min={0}
max={23}
onChange={(hour) => setTempDate(new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(), hour, tempDate.getMinutes()))}
label="时"
/>
);
// Minute picker
const MinutePicker = () => (
<NumberPicker
value={tempDate.getMinutes()}
min={0}
max={59}
onChange={(minute) => setTempDate(new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(), tempDate.getHours(), minute))}
label="分"
/>
);
return (
<>
<TouchableOpacity style={styles.datetimeTrigger} onPress={openPicker}>
<Icon name={mode === 'time' ? 'clock' : 'calendar'} size={20} color="#666" style={styles.triggerIcon} />
<Text style={styles.datetimeText}>{getDisplayText()}</Text>
<Icon name="down" size={16} color="#999" />
</TouchableOpacity>
<Modal
animationType="slide"
transparent={true}
visible={visible}
onRequestClose={closePicker}
>
<View style={styles.datetimeOverlay}>
<View style={styles.datetimeContainer}>
<View style={styles.datetimeHeader}>
<Text style={styles.datetimeTitle}>
{mode === 'date' ? '选择日期' : mode === 'time' ? '选择时间' : '选择日期和时间'}
</Text>
<TouchableOpacity onPress={closePicker}>
<Icon name="cancel" size={20} color="#999" />
</TouchableOpacity>
</View>
<View style={styles.datetimePickers}>
{mode !== 'time' && (
<>
<YearPicker />
<MonthPicker />
<DayPicker />
</>
)}
{mode !== 'date' && (
<>
<HourPicker />
<MinutePicker />
</>
)}
</View>
<View style={styles.datetimeActions}>
<TouchableOpacity style={styles.actionButton} onPress={closePicker}>
<Text style={styles.actionText}>取消</Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.actionButton, styles.confirmButton]} onPress={confirmSelection}>
<Text style={[styles.actionText, styles.confirmText]}>确定</Text>
</TouchableOpacity>
</View>
</View>
</View>
</Modal>
</>
);
};
// Main App Component
const DatetimePickerComponentApp = () => {
const [selectedDate, setSelectedDate] = useState(new Date());
const [selectedTime, setSelectedTime] = useState(new Date());
const [selectedDateTime, setSelectedDateTime] = useState(new Date());
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.pickerWrapper}>
<DatetimePicker
mode="date"
value={selectedDate}
onChange={setSelectedDate}
placeholder="请选择日期"
/>
</View>
<View style={styles.selectedValueCard}>
<Text style={styles.selectedValueText}>选中日期: {selectedDate.toLocaleDateString('zh-CN')}</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>时间选择</Text>
<View style={styles.pickerWrapper}>
<DatetimePicker
mode="time"
value={selectedTime}
onChange={setSelectedTime}
placeholder="请选择时间"
/>
</View>
<View style={styles.selectedValueCard}>
<Text style={styles.selectedValueText}>选中时间: {selectedTime.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })}</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>日期时间选择</Text>
<View style={styles.pickerWrapper}>
<DatetimePicker
mode="datetime"
value={selectedDateTime}
onChange={setSelectedDateTime}
placeholder="请选择日期和时间"
/>
</View>
<View style={styles.selectedValueCard}>
<Text style={styles.selectedValueText}>选中时间: {selectedDateTime.toLocaleString('zh-CN')}</Text>
</View>
</View>
<View style={styles.section}>
<Text style={styles.sectionTitle}>功能演示</Text>
<View style={styles.demosContainer}>
<View style={styles.demoItem}>
<Icon name="calendar" 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="clock" 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="time" size={24} color="#722ed1" 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}>{'<DatetimePicker'}</Text>
<Text style={styles.codeText}> mode="datetime"</Text>
<Text style={styles.codeText}> value={'{selectedDate}'}</Text>
<Text style={styles.codeText}> onChange={'{setSelectedDate}'}{'\n'}/></Text>
</View>
<Text style={styles.description}>
DatetimePicker组件提供了完整的日期时间选择功能,支持日期、时间或日期时间选择模式。
通过mode属性设置选择模式,value属性传递初始值,onChange回调处理选择结果。
</Text>
</View>
<View style={styles.featuresSection}>
<Text style={styles.sectionTitle}>功能特性</Text>
<View style={styles.featuresList}>
<View style={styles.featureItem}>
<Icon name="calendar" size={20} color="#1890ff" style={styles.featureIcon} />
<Text style={styles.featureText}>三种选择模式</Text>
</View>
<View style={styles.featureItem}>
<Icon name="clock" size={20} color="#52c41a" style={styles.featureIcon} />
<Text style={styles.featureText}>数字滚动选择</Text>
</View>
<View style={styles.featureItem}>
<Icon name="confirm" size={20} color="#722ed1" style={styles.featureIcon} />
<Text style={styles.featureText}>确认/取消操作</Text>
</View>
<View style={styles.featureItem}>
<Icon name="time" size={20} color="#fa8c16" style={styles.featureIcon} />
<Text style={styles.featureText}>自定义格式显示</Text>
</View>
</View>
</View>
<View style={styles.footer}>
<Text style={styles.footerText}>© 2023 时间选择器 | 现代化UI组件库</Text>
</View>
</ScrollView>
);
};
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f0f5ff',
},
header: {
backgroundColor: '#ffffff',
paddingVertical: 30,
paddingHorizontal: 20,
marginBottom: 10,
borderBottomWidth: 1,
borderBottomColor: '#e8e8e8',
},
headerTitle: {
fontSize: 28,
fontWeight: '700',
color: '#1d39c4',
textAlign: 'center',
marginBottom: 5,
},
headerSubtitle: {
fontSize: 16,
color: '#8c8c8c',
textAlign: 'center',
},
section: {
marginBottom: 25,
},
sectionTitle: {
fontSize: 20,
fontWeight: '700',
color: '#1d39c4',
paddingHorizontal: 20,
paddingBottom: 15,
},
pickerWrapper: {
backgroundColor: '#ffffff',
marginHorizontal: 15,
borderRadius: 12,
padding: 15,
elevation: 3,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4,
},
selectedValueCard: {
backgroundColor: '#f0f5ff',
marginHorizontal: 15,
borderRadius: 12,
padding: 15,
marginTop: 10,
borderWidth: 1,
borderColor: '#adc6ff',
},
selectedValueText: {
fontSize: 16,
color: '#1d39c4',
fontWeight: '500',
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: '#1d1f21',
borderRadius: 8,
padding: 15,
marginBottom: 15,
},
codeText: {
fontFamily: 'monospace',
color: '#c5c8c6',
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,
},
// DatetimePicker Styles
datetimeTrigger: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#ffffff',
borderWidth: 1,
borderColor: '#d9d9d9',
borderRadius: 6,
paddingHorizontal: 15,
paddingVertical: 12,
justifyContent: 'space-between',
},
triggerIcon: {
marginRight: 10,
},
datetimeText: {
flex: 1,
fontSize: 16,
color: '#595959',
},
datetimeOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
justifyContent: 'flex-end',
},
datetimeContainer: {
backgroundColor: '#ffffff',
height: height * 0.5,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
},
datetimeHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 20,
borderBottomWidth: 1,
borderBottomColor: '#f0f0f0',
},
datetimeTitle: {
fontSize: 18,
fontWeight: '600',
color: '#262626',
},
datetimePickers: {
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
flex: 1,
paddingVertical: 20,
},
numberPickerContainer: {
alignItems: 'center',
},
pickerButton: {
padding: 10,
},
pickerValueContainer: {
alignItems: 'center',
marginVertical: 10,
},
pickerValue: {
fontSize: 24,
fontWeight: '600',
color: '#262626',
},
pickerLabel: {
fontSize: 14,
color: '#8c8c8c',
marginTop: 5,
},
datetimeActions: {
flexDirection: 'row',
padding: 20,
borderTopWidth: 1,
borderTopColor: '#f0f0f0',
},
actionButton: {
flex: 1,
paddingVertical: 12,
alignItems: 'center',
borderRadius: 6,
borderWidth: 1,
borderColor: '#d9d9d9',
marginHorizontal: 10,
},
confirmButton: {
backgroundColor: '#1890ff',
borderColor: '#1890ff',
},
actionText: {
fontSize: 16,
color: '#595959',
},
confirmText: {
color: '#ffffff',
},
});
export default DatetimePickerComponentApp;
这段代码展示了 React Native 在鸿蒙系统上的两种日期选择器实现方案,涉及重要的跨平台兼容性技术考量。
第三方库方案的技术实现
代码使用了 @react-native-community/datetimepicker 库,这是 React Native 生态中处理日期时间选择的推荐方案。在鸿蒙系统上,该库通过鸿蒙的 ACE 引擎桥接层调用系统原生日期选择组件。Platform.OS === ‘ios’ 这一条件判断体现了对鸿蒙系统特性的深度理解——由于鸿蒙基于 Android 开源项目,其 Platform.OS 返回值通常为 android,因此鸿蒙设备会遵循 Android 平台的显示逻辑。
状态管理的技术细节
组件通过三个 useState 钩子管理核心状态:date 存储当前选择的日期时间值,采用 Unix 时间戳初始化确保跨平台一致性;mode 控制选择器的工作模式;show 管理组件的显示状态。onChange 回调函数的参数结构 (event, selectedDate) 遵循了 React Native 社区标准。
鸿蒙系统的渲染机制
在鸿蒙系统上,DateTimePicker 组件通过鸿蒙的 ArkUI 框架进行渲染。当用户触发选择操作时,event 对象包含 type 属性标识操作类型,selectedDate 提供用户选择的时间值。条件渲染逻辑 {show && …} 确保了组件只在需要时挂载到渲染树中。
自定义组件的架构设计
第二种方案采用了完全自定义的组件架构。Icon 组件通过 Unicode 符号映射机制实现图标功能,这种设计避免了在鸿蒙系统上可能存在的图标资源加载问题。getIconSymbol 方法通过 switch-case 语句将语义化的图标名称转换为对应的 Unicode 字符。
日期处理的核心算法
formatDate 函数根据不同的 mode 参数实现了条件格式化逻辑。对于日期模式返回年月日格式,时间模式返回时分格式,完整模式则组合显示。这种算法设计确保了在不同使用场景下的输出一致性。

数值选择器的边界控制
NumberPicker 组件实现了完整的数值边界检查机制。increment 和 decrement 方法通过比较当前值与最大最小值,防止用户选择超出有效范围的数值。
鸿蒙系统的权限管理
代码注释中提到的权限问题在鸿蒙系统上有特殊考量。虽然 React Native 应用通常不需要手动声明权限,但在鸿蒙的分布式架构中,访问系统时间可能需要通过 ohos.permission.GET_BUNDLE_INFO 或相关权限,这取决于鸿蒙系统的具体安全策略。
平台特定的渲染优化
在鸿蒙系统上,自定义日期选择器的渲染性能需要特别关注。由于鸿蒙采用方舟编译器的 AOT 优化,JavaScript 代码在鸿蒙上的执行效率可能不同于传统 Android 系统,这需要在组件设计时考虑渲染性能的优化。
打包
接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

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

最后运行效果图如下显示:
更多推荐




所有评论(0)