我正在使用 react-native,只想展示有限数量的行数,并在末尾使用 ...
进行省略。
<Text numberOfLines={4}> {myText} </Text>
现在我的问题是,如果文本被截断,我想显示一些特殊的图片以导航到新视图。我想知道是否有一种可以测试文本是否被截断的属性可以使用?现在我的问题是,如果文本被截断,我想显示一些特殊的图片以导航到新视图。我想知道是否有一种可以测试文本是否被截断的属性可以使用?
我正在使用 react-native,只想展示有限数量的行数,并在末尾使用 ...
进行省略。
<Text numberOfLines={4}> {myText} </Text>
现在我的问题是,如果文本被截断,我想显示一些特殊的图片以导航到新视图。我想知道是否有一种可以测试文本是否被截断的属性可以使用?目前还没有这个属性(不幸的是)。
这里有一个功能请求:https://github.com/facebook/react-native/issues/2496(也提供了一些建议,但实现链接已失效)。
您可以测量一定数量行占用的空间,然后自己处理?但这并不理想。
onTextLayout
的新事件,它可以给出文本中的行数。一旦获取了这个数字,您可以检查它是否大于您想要的最大行数,如果是这样,您可以设置一些状态来添加numberOfLines
属性,并且还可以用于确定文本是否被截断。 <Text
onTextLayout={(e) => console.log("Number of lines: " + e.nativeEvent.lines.length) }
>
{someText}
</Text>
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 = () => {
// ...
}
}
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>
);
};
我在这里回答了一个相关的问题--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 });
};
Text组件具有onPress事件,可以处理导航到另一个场景。要启用导航,包含文本的组件应放置在NavigationIOS组件中。Text组件还具有ellipsizeMode属性,该属性将“…”放置在文本末尾。
<Text
numberOfLines={4}
ellipsizeMode="tail"
onPress={(e) => this.props.navigator.push({component: Detail})}
>
{myText}
</Text>