React Native for Harmony:Rating 评分组件- 支持全星 / 半星 / 禁用 / 自定义样式

目录

[一、核心知识点:Rating 星级评分组件 完整核心用法](#一、核心知识点:Rating 星级评分组件 完整核心用法)

[二、实战: 鸿蒙原生适配 Rating 评分组件](#二、实战: 鸿蒙原生适配 Rating 评分组件)

[三、OpenHarmony6.0 专属避坑指南](#三、OpenHarmony6.0 专属避坑指南)

[四、扩展用法:Rating 评分组件 高频进阶技巧](#四、扩展用法:Rating 评分组件 高频进阶技巧)

[扩展 1:全局评分常量 + 主题配色封装](#扩展 1:全局评分常量 + 主题配色封装)

[扩展 2:带评分文字联动的星级评分](#扩展 2:带评分文字联动的星级评分)

[扩展 3:自定义星星样式 + 多场景适配](#扩展 3:自定义星星样式 + 多场景适配)

[扩展 4:评分持久化存储 + 默认值初始化](#扩展 4:评分持久化存储 + 默认值初始化)

[扩展 5:评分防抖 + 连续点击优化](#扩展 5:评分防抖 + 连续点击优化)


一、核心知识点:Rating 星级评分组件 完整核心用法

1、核心原生实现能力介绍本次实现星级评分用到的所有能力均为 RN 原生自带,无需任何第三方依赖 / 额外安装,零基础易理解、易复用,全部完美适配鸿蒙端的评分交互逻辑,无任何兼容修改,所有功能纯原生封装,无第三方组件的类型冲突、版本兼容问题,企业级项目可直接复用:

核心能力 / 语法 作用说明 核心特性
原生文本星星「★」 评分星级的核心展示载体,替代第三方 Icon 组件,无需引入字体库 鸿蒙端渲染无锯齿、无加载延迟,字号 / 颜色可灵活修改,适配所有鸿蒙机型
TouchableWithoutFeedback 核心交互组件,监听评分区域的点击事件,计算选中星级 无按压高亮反馈,贴合鸿蒙评分组件的交互规范,点击响应无延迟,鸿蒙端原生支持
动态样式绑定 根据选中评分值,动态切换星星的选中 / 未选中颜色、半星截断展示 响应式更新,评分变化时即时渲染样式,无卡顿,鸿蒙端视觉过渡流畅
精准坐标计算 通过 measure 获取容器宽度,结合点击 X 轴坐标计算选中星级 / 半星 适配鸿蒙不同屏幕尺寸,无评分错位问题,全星 / 半星判断精准无误差

2、鸿蒙端 Rating 评分组件 核心实现原则基于 RN 原生能力实现鸿蒙端的星级评分功能,核心遵循「抽离统一、精准计算、动态渲染」三大原则,逻辑极简无任何复杂点,全程只有固定 4 步,零基础可无脑套用,也是企业级鸿蒙 RN 项目的标准开发规范,永久复用,所有评分相关需求均可基于此原则扩展:

抽离全局评分常量:将「选中色、未选中色、星星尺寸、间距、默认星数」抽离为统一的常量对象,集中管理,一处修改全局生效,避免硬编码导致的样式混乱;精准计算选中星级:监听评分区域的点击事件,通过坐标计算点击位置对应的星星序号,区分全星 / 半星选中逻辑,评分值精准无偏差;动态绑定星星样式:根据当前选中的评分值,对每个星星做「全选中、半选中、未选中」的状态判断,绑定对应配色和样式,半星通过 overflow 截断实现完美展示;完善交互边界处理:增加评分值范围限制(0~ 总星数)、禁用态拦截、空回调兼容,所有交互逻辑闭环,鸿蒙端无点击失效、评分越界等异常问题。

3、鸿蒙端官方评分组件设计规范鸿蒙系统对应用的星级评分组件有统一的设计规范,遵循该规范开发的评分功能,不会出现「星星显示模糊、配色刺眼、点击不精准、视觉层级混乱」的问题,完美贴合鸿蒙系统的交互体验,也是开发鸿蒙应用的基础要求,本次实战全程遵循该规范,核心设计规则如下:

  • 配色规范:选中态星级优先使用「鸿蒙主题蓝 #007DFF」,未选中态使用浅灰色 #E5E5E5;评分场景化配色可替换(商品评价用橙色 #FF9500、差评用红色 #FF4D4F),深色模式下选中色保持原色不变,未选中色改用深灰色 #333333,保证视觉识别性;
  • 尺寸规范:星星默认字号 24px,星星间距 8px,为鸿蒙手机端最优视觉尺寸,既不会过大拥挤,也不会过小点击不精准;可根据业务需求微调,但建议星星尺寸≥20px,间距≥5px;
  • 交互规范:点击星星任意位置即可选中对应星级,半星模式下点击星星左侧区域选中半星、右侧区域选中全星,禁用态评分仅展示、无点击反馈,且自动降低透明度至 0.6,视觉区分明显;
  • 样式规范:星星无多余装饰,纯文本「★」原生渲染,鸿蒙端无模糊锯齿;半星展示无拉伸变形,严格按 50% 宽度截断,视觉规整统一。

二、实战: 鸿蒙原生适配 Rating 评分组件

javascript 复制代码
import React, { useState, useRef } from 'react';
import {
  View, Text, StyleSheet, TouchableWithoutFeedback,
  SafeAreaView
} from 'react-native';

const RATING_CONSTANT = {
  totalStars: 5,          // 默认总星数
  starSize: 24,           // 星星尺寸(鸿蒙标准)
  starSpacing: 8,         // 星星间距(鸿蒙标准)
  activeColor: '#007DFF', // 选中色(鸿蒙主题蓝)
  inactiveColor: '#E5E5E5',// 未选中色
  disabledOpacity: 0.6    // 禁用态透明度
};

interface RatingProps {
  totalStars?: number;
  value?: number;
  allowHalf?: boolean;
  disabled?: boolean;
  activeColor?: string;
  inactiveColor?: string;
  size?: number;
  spacing?: number;
  onChange?: (value: number) => void; // 设为可选属性+明确类型
}
const Rating: React.FC<RatingProps> = ({
  totalStars = RATING_CONSTANT.totalStars,
  value = 0,
  allowHalf = false,
  disabled = false,
  activeColor = RATING_CONSTANT.activeColor,
  inactiveColor = RATING_CONSTANT.inactiveColor,
  size = RATING_CONSTANT.starSize,
  spacing = RATING_CONSTANT.starSpacing,
  onChange
}) => {
  const [currentScore, setCurrentScore] = useState(value);
  const ratingRef = useRef<View>(null);

  // 计算单个星星的选中状态:full-全星 / half-半星 / empty-未选中
  const getStarStatus = (index: number) => {
    const starIdx = index + 1;
    if (currentScore >= starIdx) return 'full';
    if (allowHalf && currentScore > starIdx - 1) return 'half';
    return 'empty';
  };

  const handleRatingPress = (e: any) => {
    if (disabled || !onChange) return;
    ratingRef.current?.measure((_, __, containerWidth) => {
      // 计算单个星星的实际宽度
      const singleStarWidth = (containerWidth - (totalStars - 1) * spacing) / totalStars;
      const clickX = e.nativeEvent.locationX;
      const starIndex = Math.floor(clickX / (singleStarWidth + spacing)) + 1;
      const starOffset = clickX % (singleStarWidth + spacing);

      // 半星/全星 判断逻辑
      let newScore = starIndex;
      if (allowHalf && starOffset < singleStarWidth / 2) {
        newScore = starIndex - 0.5;
      }
      // 评分值边界限制:0 ~ 总星数
      newScore = Math.max(0, Math.min(totalStars, newScore));
      setCurrentScore(newScore);
      onChange(newScore);
    });
  };

  return (
    <TouchableWithoutFeedback onPress={handleRatingPress} disabled={disabled}>
      <View ref={ratingRef} style={[styles.ratingWrap, { gap: spacing }]}>
        {Array.from({ length: totalStars }).map((_, index) => {
          const status = getStarStatus(index);
          const starStyle = [
            styles.star,
            {
              fontSize: size,
              color: status === 'full' ? activeColor : inactiveColor,
              opacity: disabled ? RATING_CONSTANT.disabledOpacity : 1
            }
          ];
          // 半星渲染:overflow:hidden 截断实现完美半星效果
          if (status === 'half') {
            return (
              <View key={index} style={[styles.halfStarBox, { width: size / 2 }]}>
                <Text style={[starStyle, { color: activeColor }]}>★</Text>
              </View>
            );
          }
          return <Text key={index} style={starStyle}>★</Text>;
        })}
      </View>
    </TouchableWithoutFeedback>
  );
};

const RatingBasicDemo = () => {
  const [fullScore, setFullScore] = useState(3);
  const [halfScore, setHalfScore] = useState(2.5);
  const [disableScore] = useState(4);

  return (
    <SafeAreaView style={styles.pageContainer}>
      <View style={styles.demoItem}>
        <Text style={styles.demoTitle}>基础全星评分(当前:{fullScore}星)</Text>
        <Rating value={fullScore} onChange={setFullScore} />
      </View>

      <View style={styles.demoItem}>
        <Text style={styles.demoTitle}>半星精准评分(当前:{halfScore}星)</Text>
        <Rating allowHalf={true} value={halfScore} activeColor="#FF9500" onChange={setHalfScore} />
      </View>

      <View style={styles.demoItem}>
        <Text style={styles.demoTitle}>禁用态评分</Text>
        <Rating value={disableScore} disabled={true} activeColor="#FF4D4F" />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  pageContainer: {
    flex: 1,
    backgroundColor: '#f7f8fa',
    padding: 20,
    paddingTop: 22,
  },
  demoItem: {
    marginBottom: 28,
  },
  demoTitle: {
    fontSize: 16,
    color: '#333333',
    marginBottom: 10,
    fontWeight: '500',
  },
  ratingWrap: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  star: {
    lineHeight: 24,
    textAlign: 'center',
  },
  halfStarBox: {
    overflow: 'hidden',
    alignItems: 'flex-start',
  },
});

export default RatingBasicDemo;

目前发现评分的整体滑动效果很差,评分不跟随滑动,经过一轮重构后,滑动效果有改善。

javascript 复制代码
import React, { useState, useRef, useCallback } from 'react';
import {
  View, Text, StyleSheet, SafeAreaView,
  PanResponder, LayoutAnimation, PixelRatio
} from 'react-native';

const RATING_CONSTANT = {
  totalStars: 5,          // 默认总星数
  starSize: 24,           // 星星尺寸(鸿蒙标准)
  starSpacing: 8,         // 星星间距(鸿蒙标准)
  activeColor: '#007DFF', // 选中色(鸿蒙主题蓝)
  inactiveColor: '#E5E5E5',// 未选中色
  disabledOpacity: 0.6    // 禁用态透明度
};

export interface RatingProps {
  totalStars?: number;
  value?: number;
  allowHalf?: boolean;
  disabled?: boolean;
  activeColor?: string;
  inactiveColor?: string;
  size?: number;
  spacing?: number;
  onChange?: (value: number) => void; 
}

const Rating: React.FC<RatingProps> = ({
  totalStars = RATING_CONSTANT.totalStars,
  value = 0,
  allowHalf = false,
  disabled = false,
  activeColor = RATING_CONSTANT.activeColor,
  inactiveColor = RATING_CONSTANT.inactiveColor,
  size = RATING_CONSTANT.starSize,
  spacing = RATING_CONSTANT.starSpacing,
  onChange
}) => {
  const [currentScore, setCurrentScore] = useState(value);
  const ratingRef = useRef<View>(null);
  const [containerWidth, setContainerWidth] = useState(0);
  const singleStarWidth = useRef(0);

  // 初始化容器宽度和单星宽度 - 只执行一次
  const initSize = useCallback(() => {
    if (ratingRef.current && !containerWidth) {
      ratingRef.current.measure((_, __, width) => {
        setContainerWidth(width);
        singleStarWidth.current = (width - (totalStars - 1) * spacing) / totalStars;
      });
    }
  }, [totalStars, spacing, containerWidth]);

  // 计算单个星星的选中状态:full-全星 / half-半星 / empty-未选中
  const getStarStatus = (index: number) => {
    const starIdx = index + 1;
    if (currentScore >= starIdx) return 'full';
    if (allowHalf && currentScore > starIdx - 1) return 'half';
    return 'empty';
  };

  const calculateScore = (x: number) => {
    if (x <= 0) return 0;
    if (x >= containerWidth) return totalStars;
    // 计算点击/滑动位置对应的星级
    let score = x / (singleStarWidth.current + spacing);
    // 半星模式:保留小数点后1位 只允许0.5的步长
    if (allowHalf) {
      score = Math.round(score * 2) / 2;
    } else {
      // 全星模式:取整 只允许整数星级
      score = Math.round(score);
    }
    // 边界限制:评分值始终在 0 ~ 总星数 之间
    return Math.max(0, Math.min(totalStars, score));
  };

  const panResponder = PanResponder.create({
    // 是否响应手势:禁用态不响应 否则响应所有手势
    onStartShouldSetPanResponder: () => !disabled,
    onMoveShouldSetPanResponder: () => !disabled,
    // 1. 滑动中/点击中:实时计算评分 + 更新状态 滑动实时高亮 核心流畅体验
    onPanResponderMove: (_, gestureState) => {
      initSize();
      const newScore = calculateScore(gestureState.moveX);
      setCurrentScore(newScore);
    },
    // 2. 滑动结束/点击结束:锁定最终评分 + 触发外部回调 逻辑闭环
    onPanResponderRelease: (_, gestureState) => {
      initSize();
      const finalScore = calculateScore(gestureState.moveX);
      setCurrentScore(finalScore);
      onChange && onChange(finalScore);
    },
    // 3. 手势中断:重置为初始值 避免评分异常
    onPanResponderTerminate: () => {
      setCurrentScore(value);
    }
  });

  return (
    <View
      ref={ratingRef}
      style={[styles.ratingWrap, { gap: spacing, opacity: disabled ? RATING_CONSTANT.disabledOpacity : 1 }]}
      {...(disabled ? {} : panResponder.panHandlers)} // 禁用态解绑手势 正常态绑定
    >
      {Array.from({ length: totalStars }).map((_, index) => {
        const status = getStarStatus(index);
        const starStyle = [
          styles.star,
          {
            fontSize: size,
            color: status === 'full' ? activeColor : inactiveColor,
          }
        ];
        // 半星渲染:overflow:hidden 截断实现完美半星 鸿蒙端无变形无闪烁
        if (status === 'half') {
          return (
            <View key={index} style={[styles.halfStarBox, { width: size / 2 }]}>
              <Text style={[starStyle, { color: activeColor }]}>★</Text>
            </View>
          );
        }
        return <Text key={index} style={starStyle}>★</Text>;
      })}
    </View>
  );
};

const RatingOptimizeDemo = () => {
  const [fullScore, setFullScore] = useState(3);
  const [halfScore, setHalfScore] = useState(2.5);
  const [disableScore] = useState(4);

  return (
    <SafeAreaView style={styles.pageContainer}>
      <View style={styles.demoItem}>
        <Text style={styles.demoTitle}>基础全星评分(当前:{fullScore}星)</Text>
        <Rating value={fullScore} onChange={setFullScore} />
      </View>

      <View style={styles.demoItem}>
        <Text style={styles.demoTitle}>半星精准评分(当前:{halfScore}星)</Text>
        <Rating allowHalf={true} value={halfScore} activeColor="#FF9500" onChange={setHalfScore} />
      </View>

      <View style={styles.demoItem}>
        <Text style={styles.demoTitle}>禁用态评分</Text>
        <Rating value={disableScore} disabled={true} activeColor="#FF4D4F" />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  pageContainer: {
    flex: 1,
    backgroundColor: '#f7f8fa',
    padding: 20,
    paddingTop: 22,
  },
  demoItem: {
    marginBottom: 28,
  },
  demoTitle: {
    fontSize: 16,
    color: '#333333',
    marginBottom: 10,
    fontWeight: '500',
  },
  ratingWrap: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  star: {
    lineHeight: 24,
    textAlign: 'center',
  },
  halfStarBox: {
    overflow: 'hidden',
    alignItems: 'flex-start',
  },
});

export default RatingOptimizeDemo;

三、扩展用法:Rating 评分组件

扩展 1:全局评分常量 + 主题配色封装

将评分的「星星尺寸、间距、配色、半星截断规则」与鸿蒙的深浅色主题配色结合,封装到全局 ThemeContext 中,结合 useColorScheme 自动跟随系统主题切换,评分组件内部通过 useContext 获取主题配色,无需层层传递 props,是企业级鸿蒙 RN 项目的标准全局评分管理方案,逻辑清晰,维护成本极低,一处修改所有页面评分样式同步更新。

扩展 2:带评分文字联动的星级评分

在评分组件下方增加评分描述文字,实现「星级与文字联动」,比如:0 星 = 极差、1~2 星 = 不满意、2.5~3.5 星 = 一般、4~4.5 星 = 满意、5 星 = 非常满意,该功能是商品评价、服务打分的高频刚需,只需在评分回调中根据评分值做区间判断,动态渲染对应文字即可,文字配色可同步绑定评分选中色,视觉体验更统一。

扩展 3:自定义星星样式 + 多场景适配

无需使用文本「★」,可替换为鸿蒙端的本地图片 / 远程图标实现自定义星星样式(比如空心星 / 实心星、彩色星星、异形星星),只需修改星星的展示载体,核心的点击计算、状态判断逻辑完全不变;同时支持自定义星数(如 10 星评分)、自定义尺寸间距,适配「精细化评分、满意度调研」等特殊场景,鸿蒙端无样式适配问题。

扩展 4:评分持久化存储 + 默认值初始化

结合 RN 原生的 AsyncStorage 实现评分的持久化存储,用户选中的评分值可保存到本地,APP 重启后自动读取并初始化评分组件,无需重新选择;所有 AsyncStorage 的 get/set 操作包裹在 async/await 异步函数中,在 useEffect 中执行初始化逻辑,彻底规避鸿蒙端异步存储的执行顺序错乱、Promise 报错问题,评分数据持久化无丢失风险。

扩展 5:评分防抖 + 连续点击优化

针对鸿蒙端快速连续点击评分区域导致的评分值频繁变化、样式闪烁问题,添加简单的防抖逻辑,设置 50ms 的防抖延迟,避免短时间内多次触发评分回调,既保证点击响应的即时性,又能优化视觉体验,无卡顿无闪烁,鸿蒙低端机型也能流畅运行。

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
grd42 小时前
RN for OpenHarmony 小工具 App 实战:屏幕尺子实现
笔记·harmonyos
No Silver Bullet2 小时前
HarmonyOS NEXT开发进阶(十九):如何在 DevEco Studio 中查看已安装应用的运行日志
华为·harmonyos
弓.长.3 小时前
React Native 鸿蒙跨平台开发:BottomSheet 底部面板详解
javascript·react native·react.js
摘星编程3 小时前
React Native for OpenHarmony 实战:Permissions 权限管理详解
javascript·react native·react.js
大雷神3 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地
华为·harmonyos
摘星编程4 小时前
React Native for OpenHarmony 实战:SearchBar 搜索栏详解
javascript·react native·react.js
南村群童欺我老无力.5 小时前
Flutter 框架跨平台鸿蒙开发 - 开发双人对战五子棋游戏
flutter·游戏·华为·typescript·harmonyos
夜雨声烦丿5 小时前
Flutter 框架跨平台鸿蒙开发 - 消消乐游戏开发教程
flutter·游戏·华为·harmonyos
数通工程师5 小时前
IPv4和IPv6 地址分配:从划分到工具全解析
网络·网络协议·tcp/ip·华为