【精通篇】打造React Native鸿蒙跨平台开发高级复合组件库开发系列:DatetimePicker 时间选择(支持日期、年月、时分等维度)
本文介绍了在React Native中实现跨平台时间选择器的方法,重点针对Android和鸿蒙OS系统。通过使用@react-native-community/datetimepicker库,开发者可以轻松创建日期时间选择组件。文章详细说明了安装步骤和基本使用方法,并提供了完整的组件代码示例,包括日期格式化、状态管理等核心功能。特别强调了鸿蒙OS的兼容性注意事项,建议开发者进行实际设备测试以确保功
在React Native开发中,如果你想在Android鸿蒙(HarmonyOS)系统上实现一个时间选择器(DateTimePicker),你可以使用@react-native-community/datetimepicker库。这个库提供了一个跨平台的DateTimePicker组件,可以用来选择日期和时间。
步骤 1: 安装@react-native-community/datetimepicker
首先,你需要安装这个库。在你的React Native项目的根目录下,运行以下命令:
npm install @react-native-community/datetimepicker
或者,如果你使用yarn:
yarn add @react-native-community/datetimepicker
步骤 2: 使用DateTimePicker组件
在你的React组件中,你可以这样使用DateTimePicker:
import React, { useState } from 'react';
import { View, Button, Platform } from 'react-native';
import DateTimePicker from '@react-native-community/datetimepicker';
const App = () => {
const [date, setDate] = useState(new Date(1598051730000)); // 初始日期和时间
const [mode, setMode] = useState('date'); // 可以是 'date' 或 'time' 或 'datetime'
const [show, setShow] = useState(false); // 控制日期选择器的显示状态
const onChange = (event, selectedDate) => {
const currentDate = selectedDate || date;
setShow(Platform.OS === 'android'); // 在Android上隐藏,在iOS上保持显示状态
setDate(currentDate);
};
const showMode = (currentMode) => {
setShow(true);
setMode(currentMode);
};
const showDatepicker = () => {
showMode('date');
};
const showTimepicker = () => {
showMode('time');
};
const showDatetimepicker = () => {
showMode('datetime');
};
return (
<View>
<Button title="Show date picker" onPress={showDatepicker} />
<Button title="Show time picker" onPress={showTimepicker} />
<Button title="Show datetime picker" onPress={showDatetimepicker} />
{show && (
<DateTimePicker
testID="dateTimePicker"
value={date}
mode={mode}
is24Hour={true} // 24小时制显示时间
display="default" // 可以是 'default', 'spinner', 'inline' 或 'calendar'(取决于平台)
onChange={onChange}
/>
)}
</View>
);
};
export default App;
注意事项:
- 鸿蒙OS适配:虽然鸿蒙OS是基于Android的,但某些细节可能会有所不同。确保你已经在你的项目中配置了鸿蒙OS的兼容性。例如,你可能需要检查并适配特定的UI或行为差异。通常,
@react-native-community/datetimepicker应该能够很好地工作在鸿蒙OS上,因为它主要是针对Android开发的。但最好还是测试一下具体的行为。 - 测试和调试:在开发时,确保在目标设备(或模拟器)上测试你的应用,以验证时间选择器在不同平台上的表现。特别是鸿蒙OS,由于其是基于Android的,大部分情况下应该表现相似,但仍需验证。
- 鸿蒙OS特有的适配:虽然主要问题在于库的支持和兼容性,但如果有特定的鸿蒙OS适配需求(如特定的UI调整或性能优化),你可能需要查阅鸿蒙OS的开发文档或社区资源来获取更具体的指导。
通过上述步骤,你应该能够在React Native应用中实现一个功能齐全的时间选择器,无论是在普通Android还是鸿蒙OS上。
真实组件案例演示:
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 的日期时间选择器组件,其技术架构体现了现代前端组件化开发的设计理念。
从组件结构来看,代码采用了分层设计模式。最基础的 Icon 组件通过 Unicode 符号映射机制实现图标功能,这种设计避免了对外部图标库的依赖,通过 getIconSymbol 方法将语义化的图标名称转换为对应的 Unicode 字符。Icon 组件内部通过 View 容器和 Text 元素的组合,确保了图标在不同尺寸下的居中显示和视觉一致性。这种纯文本图标方案在性能和包体积方面具有明显优势。
DatetimePicker 主组件通过 useState 管理两个关键状态:visible 控制弹窗的显示隐藏,tempDate 用于存储用户正在选择但尚未确认的临时日期值。这种状态分离的设计使得用户可以取消选择操作而不影响原始值,提升了交互的友好性。
在日期时间选择的核心逻辑中,代码采用了组合式组件架构。NumberPicker 作为基础数字选择器,通过 increment 和 decrement 方法处理数值增减,并提供了完整的边界检查机制。每个时间单位都有对应的专门选择器组件,如 YearPicker、MonthPicker 等,这些组件都是基于 NumberPicker 的特化实现。
formatDate 函数根据不同的 mode 参数实现了灵活的日期格式化逻辑。对于 date 模式,返回年月日格式;对于 time 模式,返回时分格式;对于 datetime 模式则返回完整的日期时间字符串。这种设计使得组件能够适应不同的使用场景需求。

在日期选择的具体实现中,DayPicker 组件动态计算当前月份的天数,确保日期选择的准确性。这种动态计算避免了硬编码的天数限制,能够正确处理不同月份的天数差异,包括闰年二月的特殊情况。
组件的事件处理机制采用了典型的 React 模式。openPicker 方法在打开选择器时初始化临时日期状态,确保用户总是在当前值的基础上进行修改。confirmSelection 方法在用户确认选择时,通过 onChange 回调将最终值传递给父组件,同时关闭弹窗。
整个组件系统的设计体现了单一职责原则,每个组件都专注于特定的功能。同时通过 props 接口定义了清晰的组件契约,使得组件具有良好的可测试性和可维护性。
打包
接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

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

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

更多推荐




所有评论(0)