React Native如何确定文本何时被截断?

27

我正在使用 react-native,只想展示有限数量的行数,并在末尾使用 ... 进行省略。

<Text numberOfLines={4}> {myText} </Text>
现在我的问题是,如果文本被截断,我想显示一些特殊的图片以导航到新视图。我想知道是否有一种可以测试文本是否被截断的属性可以使用?
现在我的问题是,如果文本被截断,我想显示一些特殊的图片以导航到新视图。我想知道是否有一种可以测试文本是否被截断的属性可以使用?
6个回答

10

目前还没有这个属性(不幸的是)。

这里有一个功能请求:https://github.com/facebook/react-native/issues/2496(也提供了一些建议,但实现链接已失效)。

您可以测量一定数量行占用的空间,然后自己处理?但这并不理想。


10
现在有一个名为onTextLayout的新事件,它可以给出文本中的行数。一旦获取了这个数字,您可以检查它是否大于您想要的最大行数,如果是这样,您可以设置一些状态来添加numberOfLines属性,并且还可以用于确定文本是否被截断。
以下是获取行数的示例:
 <Text
  onTextLayout={(e) => console.log("Number of lines: " + e.nativeEvent.lines.length) }
>
  {someText}                
</Text>

很遗憾,在iOS上无法正常工作。 在iOS上,如果文本被截断,lines.length等于numberOfLines属性。 - undefined

2

react-native-read-more-text 会这样检测它:

  async componentDidMount() {
    // ...

    // Get the height of the text with no restriction on number of lines
    const fullHeight = await measureHeightAsync(this._text);
    this.setState({ measured: true });
    await nextFrameAsync();

    if (!this._isMounted) {
      return;
    }

    // Get the height of the text now that number of lines has been set
    const limitedHeight = await measureHeightAsync(this._text);

    if (fullHeight > limitedHeight) {
      this.setState({ doWhatYouWant: true });
    }
  }

function measureHeightAsync(component) {
  return new Promise(resolve => {
    component.measure((x, y, w, h) => {
      resolve(h);
    });
  });
}

如果您想显示任何元素(文本、按钮等),可以像这样使用它:
export class DescriptionCard extends React.Component {
  render() {
    let { text } = this.props;

    return (
      <View>
        <View style={styles.cardLabel}>
          <BoldText style={styles.cardLabelText}>
            Description
          </BoldText>
        </View>

        <View style={styles.card}>
          <View style={styles.cardBody}>
            <ReadMore
              numberOfLines={3}
              renderTruncatedFooter={this._renderTruncatedFooter}
              renderRevealedFooter={this._renderRevealedFooter}
              onReady={this._handleTextReady}>
              <RegularText style={styles.cardText}>
                {text}
              </RegularText>
            </ReadMore>
          </View>
        </View>
      </View>
    );
  }

  _renderTruncatedFooter = (handlePress) => {
    return (
      <RegularText style={{color: Colors.tintColor, marginTop: 5}} onPress={handlePress}>
        Read more
      </RegularText>
    );
  }

  _renderRevealedFooter = (handlePress) => {
    return (
      <RegularText style={{color: Colors.tintColor, marginTop: 5}} onPress={handlePress}>
        Show less
      </RegularText>
    );
  }

  _handleTextReady = () => {
    // ...
  }
}

1
@KashishGrover 谁谈论过“将其与文本放在一行中”? - Made in Moon
问题是在文本被截断时显示特殊内容。您可以渲染所需内容,并针对内联定位,使用已知的样式进行调整。您还可以检查模块源代码以了解它们如何计算截断。 - Made in Moon
我更新了答案,以便那些无法点击链接的人 :) - Made in Moon
1
我已经分享了我的答案 :) - Kashish Grover
1
ReadMore 在 FlatList 项内无法正常工作。在 FlatList 中,它仅适用于初始可见项,而不适用于所有项。 - K Khan
显示剩余2条评论

2

onTextLayout 提供了 lines 数组,我们可以用它来检查文本是否被截断。在 Android 上很容易:如果文本被截断,lines.length 大于 numberOfLines 属性。

在 iOS 上,如果文本被截断,lines.length 等于 numberOfLines 属性。不幸的是,即使您的文本恰好适合 numberOfLines,这也会发生。我们可以使用最后一行的长度和宽度进行启发式分析,以检查文本是否被截断:

const IS_ANDROID = Platform.OS === 'android';
const MAX_LINE_COUNT = 4;

const ExpandableText = ({children, style}) => {
  const [numberOfLines, setNumberOfLines] = useState(MAX_LINE_COUNT);
  const [moreVisible, setMoreVisible] = useState(false);
  const [width, setWidth] = useState(0);

  const handleLayout = (e) => {
    if (!IS_ANDROID)
      setWidth(e.nativeEvent.layout.width);
  }

  const handleTextLayout = (e) => {
    setMoreVisible(isTruncated(e.nativeEvent.lines));
  }

  const isTruncated = lines => {
    if (numberOfLines == 0 || lines.length < numberOfLines)
      return false;
    if (IS_ANDROID)
      return lines.length > numberOfLines;
    // lines.length equals to numberOfLines on iOS if text is truncated or it
    // takes exactly numberOfLines
    return lines[lines.length-1].text.length > lines[0].text.length ||
      lines[lines.length-1].width >= width * 0.82;
  }

  return (
    <View style={style}>
      <Text style={styles.text} numberOfLines={numberOfLines}
        onLayout={handleLayout} onTextLayout={handleTextLayout}>
        {children}
      </Text>
      { moreVisible &&
        <Pressable style={({ pressed }) => pressed ? {
          backgroundColor: 'lightgray'} : {}} hitSlop={20}
          onPress={() => setNumberOfLines(0)}>
          <Text style={styles.link}>More</Text>
        </Pressable>
      }
    </View>
  );
};

2

我在这里回答了一个相关的问题--https://dev59.com/e7jna4cB1Zd3GeqP4Rn9#60500348

到目前为止,我所工作过的几乎所有React Native应用都需要这个功能。我终于有了一个解决方案,并且已经开源了。

https://github.com/kashishgrover/react-native-see-more-inline

https://www.npmjs.com/package/react-native-see-more-inline

正如我在仓库中提到的那样,

我构建这个库的动机是找不到任何将“查看更多”链接与文本内联的库或实现。我发现的所有其他实现都会将链接放在文本下面。此软件包使用文本宽度,并使用简单的二分搜索(几乎)准确地计算出应该放置“查看更多”链接的位置。

我不确定这是否是最有效的解决方案,但它解决了我的用例。如果我找到更好的解决方案,我将更新答案。

这就是我所做的:

findTruncationIndex = async (containerWidth) => {
  if (
    this.containerWidthToTruncationIndexMap
    && this.containerWidthToTruncationIndexMap[containerWidth]
  ) {
    this.setState({ truncationIndex: this.containerWidthToTruncationIndexMap[containerWidth] });
    return;
  }

  const {
    children: text,
    style: { fontSize, fontFamily, fontWeight },
    seeMoreText,
    numberOfLines,
  } = this.props;

  const { width: textWidth } = await reactNativeTextSize.measure({
    text,
    fontSize,
    fontFamily,
    fontWeight,
  });

  const textWidthLimit = containerWidth * numberOfLines;

  if (textWidth < textWidthLimit) {
    this.setState({ truncationIndex: undefined });
    return;
  }

  const { width: seeMoreTextWidth } = await reactNativeTextSize.measure({
    text: ` ...${seeMoreText}`,
    fontSize,
    fontFamily,
    fontWeight,
  });

  const truncatedWidth = textWidthLimit - 2 * seeMoreTextWidth;

  let index = 0;
  let start = 0;
  let end = text.length - 1;

  while (start <= end) {
    const middle = start + (end - start) / 2;
    // eslint-disable-next-line no-await-in-loop
    const { width: partialWidth } = await reactNativeTextSize.measure({
      text: text.slice(0, middle),
      fontSize,
      fontFamily,
      fontWeight,
    });
    if (Math.abs(truncatedWidth - partialWidth) <= 10) {
      index = middle;
      break;
    } else if (partialWidth > truncatedWidth) {
      end = middle - 1;
    } else {
      start = middle + 1;
    }
  }

  const truncationIndex = Math.floor(index);

  // Map truncation index to width so that we don't calculate it again
  this.containerWidthToTruncationIndexMap = {
    ...this.containerWidthToTruncationIndexMap,
    [containerWidth]: truncationIndex,
  };
  this.setState({ truncationIndex });
};

您可以在我上面分享的 GitHub 链接中查看此组件的完整实现。

-4

Text组件具有onPress事件,可以处理导航到另一个场景。要启用导航,包含文本的组件应放置在NavigationIOS组件中。Text组件还具有ellipsizeMode属性,该属性将“…”放置在文本末尾。

<Text 
  numberOfLines={4}
  ellipsizeMode="tail"
  onPress={(e) => this.props.navigator.push({component: Detail})}
>
  {myText}
</Text>

智能解决方案! - Made in Moon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接