新文章标签:让用户一眼发现最新内容

案例项目开源地址:https://atomgit.com/nutpi/wanandroid_rn_openharmony

刷资讯的时候,你肯定希望第一时间看到最新的内容。但文章列表那么长,怎么知道哪些是新发布的?

最简单的办法就是给新文章打个标签。一个醒目的"新"字,用户扫一眼就知道这篇文章是刚发布的。

今天来聊聊 WanAndroid 项目里这个"新"标签是怎么实现的。

需求分析

WanAndroid 的 API 返回的文章数据里有一个 fresh 字段,布尔值,true 表示是新文章。

我们要做的就是:当 freshtrue 时,在文章卡片上显示一个红色的"新"标签。

看起来很简单,但要做好还是有些细节要考虑:

  1. 标签放在哪个位置?
  2. 用什么颜色?
  3. 标签的大小和样式?
  4. 怎么不遮挡文章标题?

数据结构

先看看 API 返回的文章数据:

tsx 复制代码
interface Article {
  id: number;
  title: string;
  author: string;
  niceDate: string;
  link: string;
  fresh: boolean;  // 是否是新文章
  // ... 其他字段
}

fresh 字段就是我们要用的。API 会根据文章发布时间自动判断,一般 24 小时内发布的文章 freshtrue

这个逻辑在服务端处理,我们前端只需要根据这个字段显示标签就行。

基本实现

tsx 复制代码
export const ArticleCard = ({item}: Props) => {
  const {theme} = useTheme();

  return (
    <TouchableOpacity style={[styles.card, {backgroundColor: theme.card}]}>
      <View style={styles.content}>
        {item.fresh && (
          <View style={[styles.freshTag, {backgroundColor: theme.danger}]}>
            <Text style={styles.freshText}>新</Text>
          </View>
        )}
        <Text style={[styles.title, {color: theme.text}]} numberOfLines={2}>
          {item.title}
        </Text>
        {/* 其他内容 */}
      </View>
    </TouchableOpacity>
  );
};

核心就是这个条件渲染:{item.fresh && (...)}

item.freshtrue 时,&& 后面的 JSX 会被渲染;为 false 时,什么都不渲染。

这是 React 里最常用的条件渲染方式,简洁明了。

为什么用 && 而不是三元表达式

三元表达式是这样写:

tsx 复制代码
{item.fresh ? <View>...</View> : null}

效果一样,但 && 更简洁。当"不满足条件时什么都不显示"的场景,用 && 就够了。

三元表达式适合"满足条件显示 A,不满足显示 B"的场景。

标签的样式

tsx 复制代码
freshTag: {
  position: 'absolute',
  top: 8,
  right: 8,
  paddingHorizontal: 6,
  paddingVertical: 2,
  borderRadius: 4
},
freshText: {
  color: '#fff',
  fontSize: 10,
  fontWeight: 'bold'
},

绝对定位

position: 'absolute' 让标签脱离文档流,可以自由定位。

top: 8, right: 8 把标签放在卡片内容区域的右上角,距离边缘 8 像素。

为什么放右上角?因为用户阅读习惯是从左到右、从上到下。标题在左边,标签放右边不会遮挡标题,同时又在视线范围内。

内边距

paddingHorizontal: 6 左右各 6 像素内边距。

paddingVertical: 2 上下各 2 像素内边距。

水平方向的内边距比垂直方向大,因为"新"是一个汉字,宽度比高度小,加大水平内边距让标签看起来更协调。

圆角

borderRadius: 4 给标签加 4 像素的圆角。

完全的直角看起来太生硬,加一点圆角更柔和。4 像素是一个小圆角,不会太圆,保持了标签的"方正感"。

文字样式

color: '#fff' 白色文字,在红色背景上清晰可见。

fontSize: 10 很小的字号。标签只是辅助信息,不需要太大,10 像素刚好能看清。

fontWeight: 'bold' 加粗。小字号加粗后更清晰,不会显得太细弱。

主题色的使用

tsx 复制代码
<View style={[styles.freshTag, {backgroundColor: theme.danger}]}>

标签背景色用 theme.danger,这是主题里定义的"危险/警告"色,通常是红色。

为什么用 danger 而不是直接写红色?

  1. 统一管理。所有需要红色的地方都用 theme.danger,想改颜色只需要改一处。

  2. 主题适配。深色模式和浅色模式的红色可能略有不同,用主题色可以自动适配。

  3. 语义化。danger 表示"需要注意",比直接写颜色值更有意义。

theme.danger 的定义

在 ThemeContext 里,danger 的定义大概是这样:

tsx 复制代码
const lightTheme = {
  danger: '#ff6b6b',
  // ...
};

const darkTheme = {
  danger: '#ff7675',
  // ...
};

浅色模式用稍深的红色,深色模式用稍亮的红色,保证在不同背景下都醒目。

标签位置的考量

我们把标签放在 content 容器里,用绝对定位。还有另一种方案:放在卡片最外层。

tsx 复制代码
// 方案一:放在 content 里(我们的实现)
<TouchableOpacity style={styles.card}>
  <View style={styles.content}>
    {item.fresh && <View style={styles.freshTag}>...</View>}
    <Text>标题</Text>
  </View>
</TouchableOpacity>

// 方案二:放在卡片最外层
<TouchableOpacity style={styles.card}>
  {item.fresh && <View style={styles.freshTag}>...</View>}
  <View style={styles.content}>
    <Text>标题</Text>
  </View>
</TouchableOpacity>

两种方案效果类似,但有细微差别:

方案一:标签相对于 content 定位,会受到 content 的 padding 影响。

方案二:标签相对于整个卡片定位,可以贴着卡片边缘。

我们选择方案一,因为 content 有 12 像素的 padding,标签放在 content 里,top: 8 实际上是距离 content 顶部 8 像素,视觉上更协调。

完整的文章卡片代码

tsx 复制代码
import React from 'react';
import {View, Text, TouchableOpacity, StyleSheet, Linking, Alert} from 'react-native';
import {Article} from '../types';
import {useTheme} from '../context/ThemeContext';

interface Props {
  item: Article;
}

export const ArticleCard = ({item}: Props) => {
  const {theme} = useTheme();

  const openLink = (url: string) => {
    Linking.openURL(url).catch(() => Alert.alert('错误', '无法打开链接'));
  };

  return (
    <TouchableOpacity
      style={[styles.card, {backgroundColor: theme.card, borderColor: theme.border}]}
      onPress={() => openLink(item.link)}
      activeOpacity={0.7}
    >
      <View style={styles.content}>
        {/* 新文章标签 */}
        {item.fresh && (
          <View style={[styles.freshTag, {backgroundColor: theme.danger}]}>
            <Text style={styles.freshText}>新</Text>
          </View>
        )}
        
        {/* 文章标题 */}
        <Text style={[styles.title, {color: theme.text}]} numberOfLines={2}>
          {item.title.replace(/<[^>]+>/g, '')}
        </Text>
        
        {/* 作者和分类 */}
        <View style={styles.meta}>
          <Text style={[styles.author, {color: theme.accent}]}>
            {item.author || item.shareUser || '匿名'}
          </Text>
          <Text style={[styles.chapter, {color: theme.subText}]}>
            {item.superChapterName}/{item.chapterName}
          </Text>
        </View>
        
        {/* 日期 */}
        <View style={styles.footer}>
          <Text style={[styles.date, {color: theme.subText}]}>{item.niceDate}</Text>
        </View>
      </View>
    </TouchableOpacity>
  );
};

const styles = StyleSheet.create({
  card: {
    borderRadius: 12,
    borderWidth: 1,
    marginBottom: 12,
    overflow: 'hidden'
  },
  content: {
    padding: 12
  },
  freshTag: {
    position: 'absolute',
    top: 8,
    right: 8,
    paddingHorizontal: 6,
    paddingVertical: 2,
    borderRadius: 4
  },
  freshText: {
    color: '#fff',
    fontSize: 10,
    fontWeight: 'bold'
  },
  title: {
    fontSize: 16,
    fontWeight: '600',
    lineHeight: 22,
    marginBottom: 6,
    paddingRight: 30  // 给标签留出空间
  },
  meta: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 8,
    flexWrap: 'wrap',
    gap: 8
  },
  author: {
    fontSize: 12,
    fontWeight: '500'
  },
  chapter: {
    fontSize: 11
  },
  footer: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center'
  },
  date: {
    fontSize: 11
  },
});

paddingRight: 30 的作用

注意 title 样式里有 paddingRight: 30。这是为了给标签留出空间。

标签用绝对定位放在右上角,如果标题太长,可能会和标签重叠。加上右边距,标题就不会延伸到标签下面。

30 像素大概是标签的宽度(6 + 一个汉字宽度 + 6 + 一点余量)。

其他标签的扩展

除了"新"标签,还可以加其他标签,比如"置顶"、"热门"等。

tsx 复制代码
{/* 置顶标签 */}
{item.isTop && (
  <View style={[styles.topTag, {backgroundColor: theme.accent}]}>
    <Text style={styles.tagText}>置顶</Text>
  </View>
)}

{/* 新文章标签 */}
{item.fresh && (
  <View style={[styles.freshTag, {backgroundColor: theme.danger}]}>
    <Text style={styles.tagText}>新</Text>
  </View>
)}

多个标签可以用不同颜色区分:置顶用主题色(紫色),新文章用警告色(红色)。

位置也可以错开:置顶放左上角,新文章放右上角。或者都放右上角,垂直排列。

标签的动态效果

如果想让标签更醒目,可以加一些动态效果,比如闪烁或脉冲。

tsx 复制代码
const pulseAnim = useRef(new Animated.Value(1)).current;

useEffect(() => {
  if (item.fresh) {
    Animated.loop(
      Animated.sequence([
        Animated.timing(pulseAnim, {toValue: 1.1, duration: 500, useNativeDriver: true}),
        Animated.timing(pulseAnim, {toValue: 1, duration: 500, useNativeDriver: true}),
      ])
    ).start();
  }
}, [item.fresh]);

// 使用
<Animated.View style={[styles.freshTag, {transform: [{scale: pulseAnim}]}]}>
  <Text style={styles.freshText}>新</Text>
</Animated.View>

这样标签会有一个轻微的放大缩小效果,更吸引眼球。

不过要注意,动画会消耗性能。如果列表很长,每个卡片都有动画,可能会影响滚动流畅度。所以动画要谨慎使用,或者只在可见区域的卡片上启用。

小结

一个小小的"新"标签,涉及到的知识点还不少:

  • 条件渲染(&& 运算符)
  • 绝对定位(position: 'absolute'
  • 主题色的使用
  • 样式的细节调整

这些都是 React Native 开发中的基础技能,掌握了就能做出各种各样的 UI 效果。

好的 UI 不是堆砌复杂的效果,而是把简单的元素做到位。一个标签,位置对、颜色对、大小对,就能起到很好的引导作用。


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

相关推荐
Eric.Lee20216 小时前
python实现 mp4转gif文件
开发语言·python·手势识别·手势交互·手势建模·xr混合现实
EntyIU6 小时前
python开发中虚拟环境配置
开发语言·python
wszy18096 小时前
顶部标题栏的设计与实现:让用户知道自己在哪
java·python·react native·harmonyos
Van_Moonlight7 小时前
RN for OpenHarmony 实战 TodoList 项目:空状态占位图
javascript·开源·harmonyos
kaizq7 小时前
AI-MCP-SQLite-SSE本地服务及CherryStudio便捷应用
python·sqlite·llm·sse·mcp·cherry studio·fastmcp
程序员小假7 小时前
我们来说一下无锁队列 Disruptor 的原理
java·后端
资生算法程序员_畅想家_剑魔7 小时前
Kotlin常见技术分享-02-相对于Java 的核心优势-协程
java·开发语言·kotlin
ProgramHan7 小时前
Spring Boot 3.2 新特性:虚拟线程的落地实践
java·jvm·spring boot
nbsaas-boot8 小时前
Go vs Java 的三阶段切换路线图
java·开发语言·golang