android React原生:确定文本组件的行数

nfeuvbwi  于 2023-02-06  发布在  Android
关注(0)|答案(7)|浏览(196)

正如标题所说,我一直试图找到一种方法来确定文本组件在被赋予文本后的行数。

<Text>
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi semper ut ipsum in ultrices. Vivamus fringilla lacinia odio in accumsan. Proin sit amet pellentesque tortor. Nam mollis sit amet ligula id convallis. Etiam in semper diam. Cras id elit consectetur, interdum ante id, tincidunt nisi. Integer non elit placerat, dignissim nibh at, faucibus sem. Curabitur nec posuere turpis. Vivamus rhoncus nulla vitae mi imperdiet, elementum eleifend mi laoreet. Vestibulum molestie turpis non nibh elementum, sed ornare magna tristique. Aliquam erat volutpat. Phasellus volutpat mi vel tempor finibus.
</Text>

在运行时,我如何确定这个文本组件已经渲染了多少行。这个数字会因设备而异(例如,iPhone 5需要渲染更多行,而iPhone 6+需要渲染更多行,因为它的屏幕尺寸较小)。我已经检查了文本组件的源代码,但似乎没有我要找的东西。
我正在使用React Native 0.24。
有什么想法吗?
干杯。

b5buobof

b5buobof1#

我想提供一个现代的解决方案。现在有一个onTextLayout事件,其中包含一个lines数组,可以确定正在渲染的行数。lines数组中还有其他细节,如每行的实际高度和宽度,可以进一步用于确定文本是否被截断。

const NUM_OF_LINES = 5;
const SOME_LONG_TEXT_BLOCK = 'Lorem ipsum ...';

function SomeComponent () { 
  const [ showMore, setShowMore ] = useState(false);
  const onTextLayout = useCallback(e => {
    setShowMore(e.nativeEvent.lines.length > NUM_OF_LINES);
  }, []);

  return (
    <Text numberOfLines={NUM_OF_LINES} onTextLayout={onTextLayout}>
      {SOME_LONG_TEXT_BLOCK}
    </Text>
  );
}
svmlkihl

svmlkihl2#

这是一个工作的iOS和Android

const [ loadMore, setLoadMore ] = useState(false);
    const [ numOfLines, setNumOfLines ] = useState(0);

    const onTextLayout = useCallback(e => {
        if(numOfLines == 0)
            setNumOfLines(e.nativeEvent.lines.length);
    });

    const onLoadMoreToggle = () => {
        setLoadMore(!loadMore);
    }
    
    return (
        <View style={styles.container}>
            <Text 
                numberOfLines={numOfLines == 0 ? null : loadMore ? numOfLines : NUM_OF_LINES} 
                onTextLayout={onTextLayout} 
                style={styles.bodyText}
            >
                {props.children}
            </Text>
            {
                (numOfLines > NUM_OF_LINES) &&
                <View style={styles.linkContainer}>
                    <TouchableRipple onPress={onLoadMoreToggle}>
                        <Text style={styles.linkText}>{ loadMore? 'Load Less' :'Load More'}</Text>
                    </TouchableRipple>
                </View>
            }
        </View>
    )

const styles = StyleSheet.create({
    container: {
        display: 'flex',
        flexDirection:'column',
    },
    bodyText: {
        flex:1,
    },
    linkContainer: {
        flexDirection: 'row',
        justifyContent: 'flex-end'
    },  
    linkText: {
        color: '#2196f3'
    }
})
ckocjqey

ckocjqey3#

看起来React Native 0.24实现了一个onLayout函数
http://facebook.github.io/react-native/docs/text.html#onlayout
onLayout函数
在装载和布局更改时调用
{本地事件:{布局:{x,y,宽度,高度}}}
因此,您似乎可以传递一个onLayout回调函数,获取文本组件的高度,然后使用行高进行一些计算,以获得行数

0kjbasz6

0kjbasz64#

您可以使用以下公式:

CPL =宽度/(字体大小/字体常量)

font-constant =为每个字体指定的常量。CPL =每行的字符数
下面是一些字体及其常量:

- Serif Fonts:
 American Typewriter — 2.14
 Baskerville — 2.14
 Georgia — 1.91
 Times New Roman — 2.21

 - Sans-serif Fonts:
 Arial — 1.91
 Calibri — 2.1
 Helvetica Neue — 1.9
 Lucida Grande — 1.91
 Tahoma — 1.91
 Trebuchet MS — 2.11
 Verdana — 1.73

 - Monospace Font:
 Courier New — 1.64

例如:

function getNumberOfLines(text, fontSize, fontConstant, containerWidth){

    let cpl = Math.floor(containerWidth / (fontSize / fontConstant) );
    const words = text.split(' ');
    const elements = [];
    let line = '';

    while(words.length > 0){
        if(line.length + words[0].length + 1 <= cpl || line.length === 0 && words[0].length + 1 >= cpl){
            let word = words.splice(0,1);
            if(line.length === 0){
                line = word;
            }else {
                line = line + " " + word;
            }
            if(words.length === 0){
                elements.push(line);
            }
        }
        else {
            elements.push(line);
            line = "";
        }
    }
    return elements.length;
}
w8rqjzmb

w8rqjzmb5#

加勒特McCullough提供的解决方案似乎对我很有效,我只想添加一些代码示例:

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

const styles = StyleSheet.create({
    text: {
      fontSize: 24,
      lineHeight: 30,
    }
});

export default class App extends React.Component {

    onLayout = e => {
        const { height } = e.nativeEvent.layout;
        this.count = Math.floor(height / styles.text.lineHeight)
    }

    render() {
        return (
            <View style={styles.page}>
              <Text onLayout={this.onLayout} style={styles.text}>
                Random text. Random text. Random text. Random text. Random text. Random text. Random text.
                Random text. Random text. Random text. Random text. Random text. Random text. Random text.
                Random text. Random text. Random text. Random text. Random text. Random text. Random text.
                Random text. Random text. Random text. Random text. Random text. Random text.
              </Text>

              <TouchableHighlight onPress={() => alert(`text lines count is ${this.count}`)}>
                <Text style={{ fontSize: 50 }}>touch me!</Text>
              </TouchableHighlight>
            </View>
        );
    }
}

https://snack.expo.io/@devastr1/text-lines-count-example

9wbgstp7

9wbgstp76#

我已经尝试过上面的解决方案。恕我直言,下面的代码块很适合列表。因为它在初始渲染时不会 Flink 或显示长文本

export const SomeComponent = ({text}) => {

  const NUM_LINES = 4; //Just define the minimum lines that you want to show, no matter what

  const [hasMore, setHasMore] = useState(false);
  const [showMore, setShowMore] = useState(false);
  const [numOfLines, setNumOfLines] = useState(NUM_LINES);
  const readMoreText = showMore ? 'Show Less' : 'Read More...';

  
  const onLoadMoreToggle = () => {
    setShowMore(!showMore);
  };

  const onTextLayout = e => {
    //we are checking if the original text lines are above our minimum number of lines
    setHasMore(e.nativeEvent.lines.length > NUM_LINES);
    //storing original number of lines
    setNumOfLines(e.nativeEvent.lines.length);
  };

  if (isBlank(post.text)) {
    return null;
  }

  return (
    <Pressable onPress={() => onLoadMoreToggle()}>
      <Text
        onTextLayout={onTextLayout}
        style={{opacity: 0, position: 'absolute', ...styles.text}}>
        {post.text}
      </Text>
      <Text
        style={styles.text}
        numberOfLines={
          !hasMore ? NUM_LINES : showMore ? numOfLines : NUM_LINES
        }>
        {text}
      </Text>
      {hasMore && (
        <View style={styles.readMoreContainer}>
          <Text style={styles.readMore}>{readMoreText}</Text>
        </View>
      )}
    </Pressable>
  );
};
cld4siwp

cld4siwp7#

我的解决方案是这样的:你需要考虑到onTextLayout()会在你每次调整任何属性(比如numberOfLines)时触发,所以你只想在你没有截断文本(=将undefined传递给numberOfLines)时一次设置总行数。
@Swizes也有一个很好的解决方案,如果你想计算的totalNumberOfLines没有任何 Flink 的文本.(我似乎没有任何 Flink ,但它是可能的)

const TRUNCATED_NUMBER_OF_LINES = 3;

const ReadMoreCustomText: React.FC<Props> = () => {
    const [isExpanded, setIsExpanded] = useState<boolean>(false);
    const [totalNumberOfLines, setTotalNumberOfLines] = useState<number>(0);
    const canReadMore = totalNumberOfLines > TRUNCATED_NUMBER_OF_LINES;

    useEffect(() => {
        onReadMoreChange?.(isExpanded);
    }, [isExpanded]);

    const onPress = () => setIsExpanded(wasExpanded => !wasExpanded);
    const onTextLayout = ({ nativeEvent: { lines } }: NativeSyntheticEvent<TextLayoutEventData>) => {
        // Only set it once to the total number of lines
        if (totalNumberOfLines === 0) {
            setTotalNumberOfLines(lines.length);
        }
    };

    return (
        <View style={ApplicationStyles.container}>
            <CustomText
                {...customTextProps}
                numberOfLines={isExpanded || !totalNumberOfLines ? undefined : TRUNCATED_NUMBER_OF_LINES}
                onTextLayout={onTextLayout}
            />
            {canReadMore && (
                <CustomText
                        text={t(isExpanded ? 'readLess' : 'readMore')}
                />
            )}
        </View>
    );
};

相关问题