You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native中如何实现卡片顶部反向圆角缺口?

React Native 实现顶部带自适应反向圆角缺口的红色卡片

核心思路

用react-native-svg绘制自定义形状的卡片背景,通过获取内部标题文本的宽度动态调整缺口大小,同时保证卡片的圆角和内容布局正常。

步骤1:安装依赖

首先安装react-native-svg:

npm install react-native-svg
# 或者 yarn add react-native-svg

Expo项目直接安装即可;原生项目需额外执行pod install。

步骤2:实现自定义卡片组件

以下是完整可复用组件代码,支持自适应缺口、自定义内容:

import React, { useState, useRef } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import Svg, { Path } from 'react-native-svg';

const NotchedCard = ({ title, children, cardStyle, titleStyle, contentStyle }) => {
  const [titleWidth, setTitleWidth] = useState(0);
  const titleRef = useRef(null);

  // 获取标题文本宽度
  const handleTextLayout = (event) => {
    const { width } = event.nativeEvent.layout;
    setTitleWidth(width);
  };

  // 配置参数
  const cardRadius = 16;
  const notchPadding = 12;
  const notchRadius = 8;
  const cardWidth = 340;
  const notchTotalWidth = titleWidth + notchPadding * 2;
  const notchStartX = (cardWidth - notchTotalWidth) / 2;
  const notchEndX = notchStartX + notchTotalWidth;

  return (
    <View style={[styles.cardContainer, cardStyle]}>
      {/* 隐藏的标题,仅用于计算宽度 */}
      <Text
        ref={titleRef}
        onTextLayout={handleTextLayout}
        style={[styles.hiddenTitle, titleStyle]}
      >
        {title}
      </Text>
      {/* SVG绘制带缺口的红色卡片背景 */}
      <Svg
        width={cardWidth}
        height="100%"
        viewBox={`0 0 ${cardWidth} 100`}
        preserveAspectRatio="none"
        style={styles.svgBackground}
      >
        <Path
          d={`
            M ${cardRadius} 0
            L ${notchStartX + notchRadius} 0
            A ${notchRadius} ${notchRadius} 0 0 1 ${notchStartX} ${notchRadius}
            L ${notchStartX} ${notchRadius * 2}
            L ${notchEndX} ${notchRadius * 2}
            L ${notchEndX} ${notchRadius}
            A ${notchRadius} ${notchRadius} 0 0 1 ${notchEndX - notchRadius} 0
            L ${cardWidth - cardRadius} 0
            A ${cardRadius} ${cardRadius} 0 0 1 ${cardWidth} ${cardRadius}
            L ${cardWidth} 100
            L 0 100
            L 0 ${cardRadius}
            A ${cardRadius} ${cardRadius} 0 0 1 ${cardRadius} 0
          `}
          fill="#ff3b30"
        />
      </Svg>
      {/* 显示的标题,卡在缺口位置 */}
      <Text style={[styles.visibleTitle, titleStyle]}>{title}</Text>
      {/* 卡片内容区域 */}
      <View style={[styles.contentContainer, contentStyle]}>
        {children}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  cardContainer: {
    width: 340,
    alignSelf: 'center',
    marginVertical: 20,
    position: 'relative',
    overflow: 'hidden',
  },
  hiddenTitle: {
    position: 'absolute',
    opacity: 0,
    left: 0,
    top: 0,
  },
  svgBackground: {
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
  visibleTitle: {
    position: 'absolute',
    top: -16,
    alignSelf: 'center',
    backgroundColor: '#fff',
    paddingHorizontal: 12,
    fontSize: 16,
    fontWeight: '600',
    color: '#333',
  },
  contentContainer: {
    padding: 20,
    paddingTop: 24,
  },
});

export default NotchedCard;

关键细节说明

  • 自适应缺口:通过onTextLayout获取标题实际宽度,动态计算缺口的起始/结束位置,保证缺口随文字长度变化。
  • 背景透明问题:SVG直接绘制红色卡片形状,无多余背景层,缺口区域不会出现不透明问题。
  • 圆角效果:用SVG路径的A命令绘制卡片四角和缺口的圆角,完全符合预期样式。
  • 避免元素溢出:通过position: relative和overflow: hidden限制容器范围,SVG的preserveAspectRatio="none"保证背景自适应卡片高度。

使用示例

import NotchedCard from './NotchedCard';

const App = () => {
  return (
    <View style={{ flex: 1, backgroundColor: '#fff', paddingTop: 60 }}>
      <NotchedCard title="这是自适应长度的标题">
        <Text style={{ color: '#fff', fontSize: 14 }}>
          这里是卡片的内容区域,可以添加任意文本、组件等内容。
        </Text>
        <Text style={{ color: '#fff', fontSize: 14, marginTop: 10 }}>
          后续添加的内容也会正常显示在卡片内部。
        </Text>
      </NotchedCard>
    </View>
  );
};

内容的提问来源于stack exchange,提问作者Hunter

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 19:33:33