在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;

注意事项:

  1. 鸿蒙OS适配:虽然鸿蒙OS是基于Android的,但某些细节可能会有所不同。确保你已经在你的项目中配置了鸿蒙OS的兼容性。例如,你可能需要检查并适配特定的UI或行为差异。通常,@react-native-community/datetimepicker 应该能够很好地工作在鸿蒙OS上,因为它主要是针对Android开发的。但最好还是测试一下具体的行为。
  2. 测试和调试:在开发时,确保在目标设备(或模拟器)上测试你的应用,以验证时间选择器在不同平台上的表现。特别是鸿蒙OS,由于其是基于Android的,大部分情况下应该表现相似,但仍需验证。
  3. 鸿蒙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工程目录去:

在这里插入图片描述

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

请添加图片描述

Logo

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

更多推荐