在 React Native 开发中,实现 Android 上的评分功能(例如在鸿蒙OS上),可以通过使用第三方库或者原生模块来实现。下面介绍几种常见的方法:

方法1:使用第三方库

  1. 使用 react-native-ratings

react-native-ratings 是一个流行的 React Native 库,用于在应用中添加评分功能。它支持自定义样式,易于集成。

步骤:

  1. 安装库:

    npm install react-native-ratings
    或者
    yarn add react-native-ratings
    
  2. 链接库(如果需要):

    react-native link react-native-ratings
    
  3. 使用组件:

    import AirbnbRating from 'react-native-ratings';
    
    const App = () => {
      return (
        <AirbnbRating />
      );
    };
    
    export default App;
    

方法2:使用原生模块

如果你需要更多的自定义或者想要直接与 Android 或 iOS 原生代码交互,你可以创建一个原生模块。

  1. 创建原生模块(Android)

步骤:

  1. 创建 Java 类:
    android/app/src/main/java/com/yourapp/RatingModule.java 中创建以下代码:

    package com.yourapp; // 确保包名正确
    
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    import com.facebook.react.bridge.Callback;
    import android.content.Intent;
    import android.net.Uri;
    
    public class RatingModule extends ReactContextBaseJavaModule {
        ReactApplicationContext reactContext;
    
        public RatingModule(ReactApplicationContext reactContext) {
            super(reactContext);
            this.reactContext = reactContext;
        }
    
        @ReactMethod
        public void openPlayStoreRatingPage() {
            try {
                Uri uri = Uri.parse("market://details?id=" + reactContext.getPackageName());
                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                reactContext.startActivity(intent);
            } catch (android.content.ActivityNotFoundException e) {
                Uri uri = Uri.parse("https://play.google.com/store/apps/details?id=" + reactContext.getPackageName());
                Intent intent = new Intent(Intent.ACTION_VIEW, uri);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                reactContext.startActivity(intent);
            }
        }
    
        @Override
        public String getName() {
            return "RatingModule";
        }
    }
    
  2. 注册模块: 在 MainApplication.java 中注册你的模块:

    import com.yourapp.RatingModule; // 确保导入你的模块类
    ...
    @Override
    protected List<ReactPackage> getPackages() {
        return Arrays.<ReactPackage>asList(
            new MainReactPackage(),
            new RatingModule() // 添加你的模块到列表中
        );
    }
    
  3. 在 JS 中调用:

    import { NativeModules } from 'react-native';
    const { RatingModule } = NativeModules; // 确保模块名称正确无误地引用它。
    ...
    RatingModule.openPlayStoreRatingPage(); // 调用原生方法打开评分页面。
    

    注意:这种方法主要用于打开 Google Play 的评分页面,而不是在应用内显示评分组件。如果你需要在应用内显示评分组件,建议使用方法1中的 react-native-ratings。对于鸿蒙OS的开发,确保你的应用兼容华为的商店和系统。你可能需要检查华为的 SDK 或者使用华为的应用市场链接。华为有自己的应用商店,类似于 Google Play,你可以通过类似的机制来引导用户到华为应用市场进行评分。例如,使用华为的 URI 方案 appmarket://details?id=<packageName> 来代替 Google Play 的 URI。确保在华为设备上进行适当的测试。


实际真实项目案例演示:

import React, { useState } from 'react';
import { View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity } 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 'star-full': return '★';
      case 'star-empty': return '☆';
      case 'heart-full': return '❤️';
      case 'heart-empty': return '♡';
      case 'thumb-up': return '👍';
      case 'thumb-down': return '👎';
      case 'like': return '👍';
      case 'dislike': return '👎';
      case 'smile': return '😊';
      case 'sad': return '😞';
      default: return '★';
    }
  };

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

// Rate Component
interface RateProps {
  value: number;
  onChange: (value: number) => void;
  count?: number;
  size?: number;
  color?: string;
  disabled?: boolean;
  allowHalf?: boolean;
  iconType?: 'star' | 'heart' | 'thumb';
  showText?: boolean;
}

const Rate: React.FC<RateProps> = ({ 
  value, 
  onChange,
  count = 5,
  size = 30,
  color = '#faad14',
  disabled = false,
  allowHalf = false,
  iconType = 'star',
  showText = false
}) => {
  const [hoverValue, setHoverValue] = useState(0);

  const getIconNames = (filled: boolean) => {
    switch (iconType) {
      case 'heart':
        return filled ? 'heart-full' : 'heart-empty';
      case 'thumb':
        return filled ? 'thumb-up' : 'thumb-down';
      default:
        return filled ? 'star-full' : 'star-empty';
    }
  };

  const getTextDescription = (rating: number) => {
    if (iconType === 'thumb') {
      return rating > 0 ? '赞' : '踩';
    }
    
    const descriptions = ['极差', '较差', '一般', '良好', '优秀'];
    return descriptions[Math.min(Math.floor(rating) - 1, descriptions.length - 1)] || '';
  };

  const handlePress = (index: number, isHalf?: boolean) => {
    if (disabled) return;
    
    const newValue = isHalf ? index + 0.5 : index + 1;
    onChange(newValue);
  };

  const renderStars = () => {
    const stars = [];
    const displayValue = hoverValue || value;

    for (let i = 0; i < count; i++) {
      const starValue = i + 1;
      
      if (allowHalf && displayValue > i && displayValue < starValue) {
        // Half star
        stars.push(
          <View key={i} style={{ flexDirection: 'row' }}>
            <TouchableOpacity 
              onPress={() => handlePress(i, true)}
              disabled={disabled}
              activeOpacity={disabled ? 1 : 0.7}
            >
              <Icon 
                name={getIconNames(false)} 
                size={size} 
                color={disabled ? '#f0f0f0' : '#e8e8e8'} 
              />
            </TouchableOpacity>
            <View style={{ position: 'absolute', overflow: 'hidden', width: size / 2 }}>
              <TouchableOpacity 
                onPress={() => handlePress(i, true)}
                disabled={disabled}
                activeOpacity={disabled ? 1 : 0.7}
              >
                <Icon 
                  name={getIconNames(true)} 
                  size={size} 
                  color={disabled ? '#f0f0f0' : color} 
                />
              </TouchableOpacity>
            </View>
          </View>
        );
      } else {
        // Full star or empty star
        const isFilled = displayValue >= starValue;
        stars.push(
          <TouchableOpacity 
            key={i}
            onPress={() => handlePress(i)}
            disabled={disabled}
            activeOpacity={disabled ? 1 : 0.7}
            onMouseEnter={() => !disabled && setHoverValue(starValue)}
            onMouseLeave={() => !disabled && setHoverValue(0)}
          >
            <Icon 
              name={getIconNames(isFilled)} 
              size={size} 
              color={disabled ? '#f0f0f0' : isFilled ? color : '#e8e8e8'} 
            />
          </TouchableOpacity>
        );
      }
    }

    return stars;
  };

  return (
    <View style={styles.rateContainer}>
      <View style={styles.starsContainer}>
        {renderStars()}
      </View>
      {showText && (
        <Text style={[styles.rateText, { color }]}>
          {getTextDescription(value)}
        </Text>
      )}
      {showText && iconType !== 'thumb' && (
        <Text style={styles.rateValue}>
          {value.toFixed(1)}
        </Text>
      )}
    </View>
  );
};

// Main App Component
const RateComponentApp = () => {
  const [productRating, setProductRating] = useState(3.5);
  const [serviceRating, setServiceRating] = useState(4);
  const [contentRating, setContentRating] = useState(5);
  const [thumbRating, setThumbRating] = useState(1);
  const [heartRating, setHeartRating] = useState(4);

  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.rateGroupsContainer}>
          <View style={styles.rateGroup}>
            <Text style={styles.rateLabel}>商品评价</Text>
            <Rate 
              value={productRating} 
              onChange={setProductRating} 
              showText 
            />
          </View>
          
          <View style={styles.rateGroup}>
            <Text style={styles.rateLabel}>服务态度</Text>
            <Rate 
              value={serviceRating} 
              onChange={setServiceRating} 
              allowHalf 
              showText 
            />
          </View>
          
          <View style={styles.rateGroup}>
            <Text style={styles.rateLabel}>内容质量</Text>
            <Rate 
              value={contentRating} 
              onChange={setContentRating} 
              count={10}
              showText 
            />
          </View>
        </View>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>不同图标</Text>
        <View style={styles.rateGroupsContainer}>
          <View style={styles.rateGroup}>
            <Text style={styles.rateLabel}>点赞评价</Text>
            <Rate 
              value={thumbRating} 
              onChange={setThumbRating} 
              iconType="thumb"
              showText 
            />
          </View>
          
          <View style={styles.rateGroup}>
            <Text style={styles.rateLabel}>喜爱程度</Text>
            <Rate 
              value={heartRating} 
              onChange={setHeartRating} 
              iconType="heart"
              color="#ff4d4f"
              allowHalf
              showText 
            />
          </View>
        </View>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>评分统计</Text>
        <View style={styles.statsSection}>
          <View style={styles.statCard}>
            <Text style={styles.statLabel}>平均评分</Text>
            <Text style={styles.statValue}>4.2</Text>
            <View style={styles.statStars}>
              <Icon name="star-full" size={16} color="#faad14" />
              <Icon name="star-full" size={16} color="#faad14" />
              <Icon name="star-full" size={16} color="#faad14" />
              <Icon name="star-full" size={16} color="#faad14" />
              <Icon name="star-empty" size={16} color="#e8e8e8" />
            </View>
          </View>
          
          <View style={styles.statCard}>
            <Text style={styles.statLabel}>参与人数</Text>
            <Text style={styles.statValue}>1,248</Text>
            <Text style={styles.statDesc}>人已评分</Text>
          </View>
          
          <View style={styles.statCard}>
            <Text style={styles.statLabel}>推荐度</Text>
            <Text style={styles.statValue}>92%</Text>
            <Text style={styles.statDesc}>用户推荐</Text>
          </View>
        </View>
      </View>
      
      <View style={styles.section}>
        <Text style={styles.sectionTitle}>功能演示</Text>
        <View style={styles.demosContainer}>
          <View style={styles.demoItem}>
            <Icon name="star-full" size={24} color="#faad14" style={styles.demoIcon} />
            <View>
              <Text style={styles.demoTitle}>星级评分</Text>
              <Text style={styles.demoDesc}>支持整星和半星评分</Text>
            </View>
          </View>
          
          <View style={styles.demoItem}>
            <Icon name="heart-full" size={24} color="#ff4d4f" style={styles.demoIcon} />
            <View>
              <Text style={styles.demoTitle}>多种图标</Text>
              <Text style={styles.demoDesc}>支持星星、爱心、拇指等多种图标</Text>
            </View>
          </View>
          
          <View style={styles.demoItem}>
            <Icon name="thumb-up" size={24} color="#1890ff" 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}>{'<Rate'}</Text>
          <Text style={styles.codeText}>  value={'{rating}'}</Text>
          <Text style={styles.codeText}>  onChange={'{setRating}'}</Text>
          <Text style={styles.codeText}>  allowHalf</Text>
          <Text style={styles.codeText}>  showText{'\n'}/></Text>
        </View>
        <Text style={styles.description}>
          Rate组件提供了完整的评分功能,包括整星、半星评分,多种图标支持和自定义样式。
          通过value控制当前评分,onChange处理评分变化,allowHalf支持半星评分。
        </Text>
      </View>
      
      <View style={styles.featuresSection}>
        <Text style={styles.sectionTitle}>功能特性</Text>
        <View style={styles.featuresList}>
          <View style={styles.featureItem}>
            <Icon name="star-full" size={20} color="#faad14" style={styles.featureIcon} />
            <Text style={styles.featureText}>整星/半星评分</Text>
          </View>
          <View style={styles.featureItem}>
            <Icon name="heart-full" size={20} color="#ff4d4f" style={styles.featureIcon} />
            <Text style={styles.featureText}>多种图标支持</Text>
          </View>
          <View style={styles.featureItem}>
            <Icon name="thumb-up" size={20} color="#1890ff" style={styles.featureIcon} />
            <Text style={styles.featureText}>自定义样式</Text>
          </View>
          <View style={styles.featureItem}>
            <Icon name="star-full" size={20} color="#52c41a" 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 } = Dimensions.get('window');

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fffaf5',
  },
  header: {
    backgroundColor: '#fff5eb',
    paddingVertical: 30,
    paddingHorizontal: 20,
    marginBottom: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#f0dccb',
  },
  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,
  },
  rateGroupsContainer: {
    backgroundColor: '#ffffff',
    marginHorizontal: 15,
    borderRadius: 12,
    padding: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
    marginBottom: 10,
  },
  rateGroup: {
    marginBottom: 20,
  },
  rateGroupLast: {
    marginBottom: 0,
  },
  rateLabel: {
    fontSize: 16,
    fontWeight: '500',
    color: '#5a3b1c',
    marginBottom: 10,
  },
  statsSection: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    paddingHorizontal: 15,
  },
  statCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 15,
    width: (width - 60) / 3,
    alignItems: 'center',
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 4,
  },
  statLabel: {
    fontSize: 14,
    color: '#d4883a',
    marginBottom: 5,
  },
  statValue: {
    fontSize: 24,
    fontWeight: '700',
    color: '#d46b08',
    marginBottom: 5,
  },
  statDesc: {
    fontSize: 12,
    color: '#d4883a',
  },
  statStars: {
    flexDirection: 'row',
  },
  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,
  },
  // Rate Styles
  rateContainer: {
    flexDirection: 'row',
    alignItems: 'center',
    flexWrap: 'wrap',
  },
  starsContainer: {
    flexDirection: 'row',
  },
  rateText: {
    fontSize: 16,
    fontWeight: '600',
    marginLeft: 10,
  },
  rateValue: {
    fontSize: 14,
    color: '#d4883a',
    marginLeft: 5,
  },
});

export default RateComponentApp;

这段 React Native 评分组件在鸿蒙系统上的技术实现展现了与系统架构的深度集成。

从 Icon 组件的 Unicode 符号映射机制来看,这种设计在鸿蒙的 ACE 引擎中具有特殊的性能优势。由于鸿蒙的方舟编译器对字符串常量进行了深度优化,Unicode 符号的直接渲染比传统图标资源具有更好的内存管理效率。在鸿蒙的渲染管线中,Text 组件针对 Emoji 和特殊符号进行了专门处理,这确保了在不同分辨率鸿蒙设备上图标显示的清晰度和一致性。

评分组件的状态管理体现了对鸿蒙分布式特性的适配。useState 管理的 hoverValue 状态在鸿蒙的多设备协同场景下需要保持同步。当用户在鸿蒙手机和平板之间切换时,悬停状态的视觉反馈需要与系统的交互框架保持协调。

在这里插入图片描述

在 renderStars 函数的实现中,条件渲染逻辑根据 allowHalf 参数决定是否显示半星效果。当 allowHalf 为 true 时,组件采用双层 View 结构实现半星效果,内层通过绝对定位和溢出隐藏控制图标的显示范围。这种渲染机制在鸿蒙的声明式UI框架中能够高效工作。

组件的交互事件处理在鸿蒙系统上需要考虑多模态输入特性。TouchableOpacity 组件不仅需要处理触摸事件,还需要与鸿蒙的语音交互、手势识别等输入方式进行协调。onMouseEnter 和 onMouseLeave 事件在鸿蒙的分布式输入框架中具有特殊的意义。

在鸿蒙的弹性布局系统中,评分组件的尺寸自适应需要特别关注。size 参数不仅影响图标大小,还需要与鸿蒙的屏幕密度缩放机制进行匹配,确保在不同设备上获得一致的视觉效果。


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述

Logo

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

更多推荐