RN | 系统组件之 View、Text 📝

571 阅读8分钟

RN 的 StyleSheet

在写系统组件之前,先写下 StyleSheet 样式表的使用。

StyleSheet 是 React Native 特有的。

React 使用内联样式、css 文件、CSS Modules 的形式书写样式。

而 React Native 使用则是使用 StyleSheet 或者内联样式(JavaScript 对象)来定义样式。

如何使用 StyleSheet

import React from 'react';
import { View, StyleSheet } from 'react-native';

const SimpleViewComponent = () => {
  return (
    <View style={styles.container}>
      {/* 这里可以添加其他组件或内容 */}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1, // 占满整个屏幕
    justifyContent: 'center', // 垂直居中
    alignItems: 'center', // 水平居中
    backgroundColor: '#f0f0f0', // 背景颜色
  },
});

export default SimpleViewComponent;

StyleSheet 的优势

  1. 性能优化:
// StyleSheet.create() 会在应用启动时创建一个样式表注册表
const styles = StyleSheet.create({
  container: {
    // 样式定义会被优化和缓存
  }
});
  1. 类型检查:
// StyleSheet 提供了类型检查,错误的样式属性会在编译时报错
const styles = StyleSheet.create({
  container: {
    colour: 'red'  // ❌ 错误:拼写错误会被检测出来
    color: 'red'   // ✅ 正确
  }
});
  1. 样式复用:
const styles = StyleSheet.create({
  base: {
    padding: 10,
    backgroundColor: '#fff',
  },
  primary: {
    ...StyleSheet.flatten(styles.base),  // 扩展基础样式
    backgroundColor: 'blue',
  }
});
  1. 特殊方法:
const styles = StyleSheet.create({
  container: {
    // 绝对填充
    ...StyleSheet.absoluteFillObject,
    // 等同于:
    // position: 'absolute',
    // left: 0,
    // right: 0,
    // top: 0,
    // bottom: 0
  }
});

View

View 组件的 Flex 布局

flexDirection 定义主轴方向,参数值有:

  • row
  • row-reverse
  • column
  • column-reverse

flexFrow 和 flex 属性的区别

flex 是一个简写属性,包含了 flexGrow、flexShrink 和 flexBasis 三个属性。

  1. 空间分配不同
// flex 会同时影响增长和收缩
const styles = StyleSheet.create({
  box1: {
    flex: 1,  // 占用所有可用空间的 1/3
  },
  box2: {
    flex: 2,  // 占用所有可用空间的 2/3
  },
});

// flexGrow 只影响增长
const styles = StyleSheet.create({
  box1: {
    flexGrow: 1,  // 多余空间分配比例为 1
    flexBasis: 100,  // 初始大小为 100
  },
  box2: {
    flexGrow: 2,  // 多余空间分配比例为 2
    flexBasis: 100,  // 初始大小为 100
  },
});
  1. 影响初始尺寸
// flex 会重置 flexBasis 为 0
const styles = StyleSheet.create({
  box: {
    flex: 1,
    width: 100,  // 这个宽度会被忽略
  },
});

// flexGrow 保留初始尺寸
const styles = StyleSheet.create({
  box: {
    flexGrow: 1,
    width: 100,  // 这个宽度会被保留作为初始尺寸
  },
});

使用建议

  1. 使用 flex:
  • 需要元素完全填充可用空间
  • 不关心元素的初始尺寸
  • 想要简化代码
  1. 使用 flexGrow:
  • 需要保留元素的初始尺寸
  • 只想控制元素如何分配额外空间
  • 需要更精确的布局控制

position 绝对定位

  • 绝对定位元素相对于最近的 position: 'relative' 的祖先元素定位
  • 如果没有父组件设置 position: 'relative',绝对定位的元素会相对于屏幕视口(viewport)进行定位
  • 可以理解为:位置是相对于最近的非 static 定位的父元素

1. 父容器关系:

// 绝对定位元素相对于最近的 position: 'relative' 的祖先元素定位
const styles = StyleSheet.create({
  parent: {
    position: 'relative', // 重要!
    // ...
  },
  child: {
    position: 'absolute',
    // ...
  },
});

2. 性能考虑

// 避免过多使用绝对定位,可能影响性能
// 优先使用 Flex 布局
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});
  1. 跨平台兼容性:
// 某些定位值在不同平台可能表现不同
const styles = StyleSheet.create({
  element: {
    position: 'absolute',
    ...Platform.select({
      ios: {
        top: 20,
      },
      android: {
        top: 0,
      },
    }),
  },
});

View 组件的 onLayout 属性

onLayout 是 React Native View 组件的一个重要属性,它在组件布局完成或布局发生变化时触发。这个事件可以让我们获取组件的尺寸和位置信息。

初始化时机:可能在组件挂载后立即触发,但也可能需要等待一段时间

const Component = () => {
  useEffect(() => {
    // onLayout 可能在组件挂载后立即触发
    // 但也可能需要等待一段时间
  }, []);

  return (
    <View 
      onLayout={({ nativeEvent }) => {
        // 确保在使用布局信息前已经获取到有效值
        if (nativeEvent.layout.width > 0) {
          // 处理布局
        }
      }}
    >
      {/* 内容 */}
    </View>
  );
};

例子:

import React, {useState} from 'react';
import {View, Text, StyleSheet} from 'react-native';

export default () => {
  const [dimensions, setDimensions] = useState({
    width: 0,
    height: 0,
    x: 0,
    y: 0,
  });

  return (
    <View
      style={styles.container}
      onLayout={event => {
        const {width, height, x, y} = event.nativeEvent.layout;
        setDimensions({width, height, x, y});
      }}>
      <Text>
        宽度: {dimensions.width}
        {'\n'}
        高度: {dimensions.height}
        {'\n'}
        X坐标: {dimensions.x}
        {'\n'}
        Y坐标: {dimensions.y}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
    margin: 10,
    backgroundColor: 'red',
  },
});

image.png

setNativeProps

setNativeProps 是 React Native 提供的一个直接操作原生视图的方法,它允许我们在不触发 React 重新渲染的情况下直接修改原生视图的属性。这对于需要频繁更新的动画或者实时更新非常有用。

例子:

import React, { useRef, useEffect } from 'react';
import { View, Animated } from 'react-native';

const AnimationExample = () => {
  const viewRef = useRef(null);
  
  useEffect(() => {
    let position = 0;
    
    const animate = () => {
      position += 1;
      viewRef.current?.setNativeProps({
        style: {
          transform: [{ translateX: position % 100 }]
        }
      });
      
      requestAnimationFrame(animate);
    };
    
    requestAnimationFrame(animate);
  }, []);

  return (
    <View 
      ref={viewRef}
      style={styles.animatedBox}
    />
  );
};

使用限制

const Component = () => {
  const viewRef = useRef<View>(null);

  // ✅ 可以更新的属性
  viewRef.current?.setNativeProps({
    style: {
      opacity: 0.5,
      transform: [{ scale: 1.1 }],
      backgroundColor: 'red',
    }
  });

  // ❌ 不能更新的属性
  viewRef.current?.setNativeProps({
    children: <Text>新内容</Text>, // 错误:不能更新子组件
    onPress: () => {}, // 错误:不能更新事件处理器
  });
};

Text

<Text
  style={{
    // 文本特有样式
    fontSize: 16,
    fontWeight: 'bold',
    lineHeight: 24,
    textAlign: 'center',
    textDecorationLine: 'underline',
    
    // 文本特有属性
    numberOfLines={2}
    ellipsizeMode="tail"
  }}
>
  文字内容
</Text>

Text 的 numberOfLines 属性

numberOfLines 是 Text 组件的一个重要属性,用于限制文本显示的行数。当文本内容超过指定的行数时,会自动截断并显示省略号

属性值指定显示的文本行数。

Text 的 ellipsizeMode 属性

ellipsizeMode 是 Text 组件的一个属性,用于控制文本被截断时省略号的显示位置

它有四个可选值:'head'、'middle'、'tail' 和 'clip'。

  • 默认值是 'tail',在尾部显示省略号
  • head,在头部显示省略号
  • middle 在中部显示省略号
  • clip,裁剪掉文字

例子:

import React from 'react';
import { View, Text, StyleSheet } from 'react-native';

const EllipsisExample = () => {
  const longText = "这是一段非常长的文本内容,用来演示不同的省略模式效果";
  
  return (
    <View style={styles.container}>
      {/* 在开头显示省略号 */}
      <Text 
        numberOfLines={1} 
        ellipsizeMode="head"
        style={styles.text}
      >
        {longText}
      </Text>

      {/* 在中间显示省略号 */}
      <Text 
        numberOfLines={1} 
        ellipsizeMode="middle"
        style={styles.text}
      >
        {longText}
      </Text>

      {/* 在末尾显示省略号(默认) */}
      <Text 
        numberOfLines={1} 
        ellipsizeMode="tail"
        style={styles.text}
      >
        {longText}
      </Text>

      {/* 直接裁剪文本,不显示省略号 */}
      <Text 
        numberOfLines={1} 
        ellipsizeMode="clip"
        style={styles.text}
      >
        {longText}
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  text: {
    fontSize: 16,
    marginBottom: 10,
  },
});

image.png

Text 的 selectable、selectionColor 属性

  • selectable 文本是否可选中
  • selectionColor 选中文本的颜色
import React from 'react';
import {View, Text, StyleSheet} from 'react-native';

export default () => {
  return (
    <View style={styles.container}>

      {/* 自定义选择颜色 */}
      <Text
        selectable={true}
        selectionColor="#AEAEAE"
        style={styles.coloredText}>
        这段文本被选中时会显示橙色背景
      </Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  coloredText: {
    fontSize: 16,
    lineHeight: 24,
  },
});

image.png

Text 的 onPress、onLongPress 属性

onPress 和 onLongPress 是 Text 组件的点击事件处理属性,用于响应用户的点击和长按操作。

例子:

import React, { useState } from 'react';
import { Text, View, StyleSheet } from 'react-native';

const TextPressExample = () => {
  const [pressCount, setPressCount] = useState(0);
  const [lastPress, setLastPress] = useState('无');

  return (
    <View style={styles.container}>
      <Text 
        style={styles.text}
        onPress={() => {
          setPressCount(prev => prev + 1);
          setLastPress('点击');
        }}
        onLongPress={() => {
          setLastPress('长按');
        }}
      >
        点击或长按这段文字
      </Text>

      <Text>点击次数: {pressCount}</Text>
      <Text>最后操作: {lastPress}</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  text: {
    fontSize: 16,
    color: 'blue',
    padding: 10,
  },
});

点击两次,最后长按效果:

image.png

Text 的 allowFontScaling 属性

allowFontScaling 是 Text 组件的一个属性,用于控制文本是否随系统字体大小设置而缩放。这对于创建响应式和无障碍的应用程序非常重要。

    <View style={styles.container}>
      {/* 允许字体缩放(默认行为) */}
      <Text>
        这段文字会随系统字体大小变化而缩放
      </Text>

      {/* 禁止字体缩放 */}
      <Text allowFontScaling={false}>
        这段文字大小保持固定
      </Text>
    </View>

Text 的 maxFontSizeMultiplier 属性

const MaxScaleExample = () => {
  return (
    <View>
      <Text 
        allowFontScaling={true}
        maxFontSizeMultiplier={1.5} // 最大放大到 1.5 
        style={styles.text}
      >
        这段文字最多放大到 1.5 倍
      </Text>
    </View>
  );
};

Text 的 textAlign、textAlignVertical 属性

textAlign 和 textAlignVertical 是 Text 组件的对齐方式属性,分别控制文本的水平和垂直对齐

Text 装饰属性

  • textDecorationLine:定义装饰线的位置(下划线、删除线、上划线)

  • textDecorationStyle:定义装饰线的样式(实线、虚线、点线、双线)

Text 的文字阴影

  • textShadowColor,控制阴影的颜色

  • textShadowOffset,控制阴影的偏移位置,包含 width(水平偏移)和 height(垂直偏移)

  • textShadowRadius,控制阴影的模糊半径,值越大阴影越模糊

例子:

const styles = StyleSheet.create({
  container: {
    padding: 20,
  },
  // 基本阴影
  basicShadow: {
    fontSize: 24,
    marginVertical: 10,
    textShadowColor: 'rgba(0, 0, 0, 0.75)',
    textShadowOffset: {width: 2, height: 2},
    textShadowRadius: 1,
  },
  // 模糊阴影
  blurredShadow: {
    fontSize: 24,
    marginVertical: 10,
    textShadowColor: 'rgba(0, 0, 0, 0.5)',
    textShadowOffset: {width: 3, height: 3},
    textShadowRadius: 5,
  },
  // 多重阴影
  multipleShadow: {
    fontSize: 24,
    marginVertical: 10,
    color: '#fff',
    textShadowColor: '#000',
    textShadowOffset: {width: -1, height: 1},
    textShadowRadius: 10,
  },
  // 发光效果
  glowEffect: {
    fontSize: 24,
    marginVertical: 10,
    color: '#fff',
    textShadowColor: '#0066ff',
    textShadowOffset: {width: 0, height: 0},
    textShadowRadius: 15,
  },
});

image.png

Text 的无障碍支持

const AccessibleText = ({ text }) => {
  return (
    <Text
      allowFontScaling={true}
      accessible={true}
      accessibilityLabel={text}
      style={styles.text}
    >
      {text}
    </Text>
  );
};

RN 的 View 和 Text 组件的区别

1. 基本定位和用途

View 组件:

// View 是一个容器组件,类似于 HTML 中的 div
<View style={styles.container}>
  <View style={styles.box}>
    <Text>内容</Text>
  </View>
</View>

Text 组件:

// Text 专门用于显示文本内容,类似于 HTML 中的 span 或 p
<Text style={styles.text}>
  这是一段文字
  <Text style={styles.highlight}>高亮文字</Text>
</Text>

2. 主要区别

  1. 嵌套规则
// ✅ 正确:Text 可以嵌套在 View 中
<View>
  <Text>文字内容</Text>
</View>

// ✅ 正确:Text 可以嵌套 Text
<Text>
  外层文字
  <Text>内层文字</Text>
</Text>

// ❌ 错误:View 不能直接嵌套在 Text 中
<Text>
  文字
  <View>  {/* 这样做会报错 */}
    <Text>更多文字</Text>
  </View>
</Text>
  1. 样式继承
// Text 组件有样式继承
<Text style={{ color: 'red', fontSize: 16 }}>
  父级文字
  <Text>
    子级文字 {/* 会继承父级的红色和字号 */}
  </Text>
</Text>

// View 组件没有样式继承
<View style={{ backgroundColor: 'red' }}>
  <View>  {/* 不会继承父级的背景色 */}
  </View>
</View>
  1. 特有属性
<View
  style={{
    // 布局相关
    flex: 1,
    flexDirection: 'row', // row-reverse | column | column-reverse
    
    // 边框相关
    borderRadius: 10,
    borderWidth: 1,
    
    // 背景
    backgroundColor: '#fff',
    
    // 阴影(iOS)
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.25,
    
    // 阴影(Android)
    elevation: 5,
  }}
/>
<Text
  style={{
    // 文本特有样式
    fontSize: 16,
    fontWeight: 'bold',
    lineHeight: 24,
    textAlign: 'center',
    textDecorationLine: 'underline',
    
    // 文本特有属性
    numberOfLines={2}
    ellipsizeMode="tail"
  }}
>
  文字内容
</Text>

3. 常见使用场景

  • View 当作布局容器使用
<View style={styles.container}>
  <View style={styles.header}>
    <Text style={styles.title}>标题</Text>
  </View>
  <View style={styles.content}>
    {/* 其他内容 */}
  </View>
</View>
  • Text 用来显示文本
<Text style={styles.paragraph}>
  这是一段普通文字
  <Text style={styles.bold}>加粗文字</Text>
  <Text style={styles.italic}>斜体文字</Text>
  {'\n'}
  <Text style={styles.link}>链接文字</Text>
</Text>

4. 注意事项

  • 所有文本内容必须包含在 Text 组件中
  • View 中不能直接放置文本内容
  • 避免过深的 View 嵌套
  • 合理使用 Text 的样式继承特性