最近在做项目的时候,遇到了这样的一个设计:
![](https://img.haomeiwen.com/i2649238/b315836c8ef1bb0d.png)
乍看之下平平无奇,但其实暗藏玄机...我想了蛮久的时间才比较完美的实现了这个样式,所以特此记录。
我们仔细观察这个样式,它是由两部分组成,一个View组件和Text文本。
而它的特别之处就在于这个View组件是融合进Text文本中的,而不是独立的两个区域。
经过翻看官方文档得知,Text组件里面是可以包裹Text组件的:
![](https://img.haomeiwen.com/i2649238/9399aad53ea3a82f.png)
代码如下:
<Text style={{color:'red'}}>我是第一个Text<Text style={{color:'blue'}}>我是第二个</Text></Text>
既然如此,我们能不能在Text组件里包裹View组件呢?
通过查阅文档,我发现是可以的(虽然文档写着仅限iOS,但实测Android也是可以的...)
![](https://img.haomeiwen.com/i2649238/87713f949355cd01.png)
于是乎我写了如下的代码:
<View style={{width:300,backgroundColor:colors.gray}}>
<Text style={{color:'red'}}>
<View style={{width:100,height:30,backgroundColor:'blue'}}></View>
{'我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本'}
</Text>
</View>
得到了这样的效果:
![](https://img.haomeiwen.com/i2649238/255b32ccbb285ac6.png)
嗯...似乎还不错,那我们按照设计稿接着写:
<View style={{width:300,backgroundColor:colors.gray}}>
<Text style={{color:'red'}}>
<View style={{width:100,height:30,backgroundColor:'blue'}}>
<Text>{'我是名字'}</Text>
</View>
{'我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本'}
</Text>
</View>
这次我们在View组件中加了个展示名字的Text组件,得到了如下效果:
![](https://img.haomeiwen.com/i2649238/634547bff753f7b2.png)
我们的代码在iOS上并没有展示出内部的Text组件,而且在Android上还得到了一个报错。
因为时间紧迫,我没有深入研究Android中为何会报错,不过翻看了下源码,大概就是这种写法会导致获取不到最内部的Text组件吧
// If a CSS node has measure defined, the layout algorithm will not visit its children. Even
// more, it asserts that you don't add children to nodes with measure functions.
if (mYogaNode != null && !isYogaLeafNode()) {
YogaNode childYogaNode = child.mYogaNode;
if (childYogaNode == null) {
throw new RuntimeException(
"Cannot add a child that doesn't have a YogaNode to a parent without a measure "
+ "function! (Trying to add a '"
+ child.toString()
+ "' to a '"
+ toString()
+ "')");
}
mYogaNode.addChildAt(childYogaNode, i);
}
代码写到这里...我突然感觉事情并没有我想象中那么简单...
于是我只能放弃这种思路,开始思考别的实现方式。
又仔细研究了下设计稿上的样式,突然来了灵感...
其实我们可以把View组件拿出来,使用 position:'absolute' 这种定位方式,将其放置在左上角,我们要做的就是计算出这个View占用了多少的宽,然后在Text头部放一个一样的View占位,把文字挤过去,这样不就达到效果了吗?
参考代码如下:
<View style={{width:300,backgroundColor:colors.gray}}>
<Text style={{color:'red'}}>
{/*下面这个是占位的View组件,this.data.viewWidth 是真实View组件测量之后的宽度*/}
<View style={{width:this.data.viewWidth,height:30,backgroundColor:'blue'}}/>
{'我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本'}
</Text>
{/*下面这个真实的View组件*/}
<View onLayout={(e)=>{
e.persist();
console.log('onLayout',e.nativeEvent.layout.width);
this.data.viewWidth = parseInt(e.nativeEvent.layout.width,10);
}} style={{height:30,position:'absolute',left:0,top:0,justifyContent:'center',alignItems:'center'}}>
<Text style={{color:colors.white}}>{'我是名字'}</Text>
</View>
</View>
大功告成,我们只需要测量出真实的View组件的宽,再把它赋值给占位组件就行了。
![](https://img.haomeiwen.com/i2649238/48aefa3a56301168.png)
嗯...这Android是咋回事?!
在尝试之后发现,在Android上似乎不能动态更新Text组件内那个View组件的宽度,我怀疑可能和Android的渲染机制有关...
既然如此...那这种方法也只好放弃了
于是我又开始对着设计稿沉思...突然又一道灵光乍现...
我们其实可以把这个样式看成三部分:
![](https://img.haomeiwen.com/i2649238/4a43bb2ac77886a9.png)
首先View组件是一部分,旁边的Text组件是一部分,底下的Text组件是一部分,那么我们首先渲染出View组件和第一行的Text组件,如果第一行的Text组件需要换行,我们计算出第一行的Text渲染了多少个字,再计算出剩余的Text需要渲染多少个字即可。
参考代码如下:
<View style={{width:300,backgroundColor:colors.gray}}>
<View style={{width:300,flexDirection:'row',alignItems:'center'}}>
<View style={{width:100,height:30,backgroundColor:colors.black_second,justifyContent:'center',alignItems:'center'}}>
<Text style={{fontSize:10,color:colors.white}}>这是你的View</Text>
</View>
<Text style={{flex:1,lineHeight:30,fontSize:14}} ellipsizeMode={'clip'} onLayout={(e)=>{
e.persist();
console.log('onLayout',e.nativeEvent);
if(e.nativeEvent.layout.height > 30){
this.data.needNextLine = true;
this.data.firstLineTextNum = parseInt(e.nativeEvent.layout.width / 14,10);
console.log('firstLineTextNum',this.data.firstLineTextNum);
this.data.nextLineText = this.data.text.substr(this.data.firstLineTextNum,this.data.text.length - this.data.firstLineTextNum);
}
}} numberOfLines={this.data.needNextLine ? 1 : 0}>{this.data.text}</Text>
</View>
{this.data.nextLineText ? <Text style={{lineHeight:30}}>{this.data.nextLineText}</Text>:null}
</View>
效果如下:
![](https://img.haomeiwen.com/i2649238/74c54ee8815dfed8.png)
似乎...有那味儿了,但是第二部分末尾的被暴力的裁剪掉了,我们需要处理一下。
处理的方式也简单,我们定义一个 firstLineText 在需要换行的情况下,手动计算出第一行需要显示多少字就行了
参考代码如下:
<View style={{width:300,backgroundColor:colors.gray}}>
<View style={{width:300,flexDirection:'row',alignItems:'center'}}>
<View style={{width:100,height:30,backgroundColor:colors.black_second,justifyContent:'center',alignItems:'center'}}>
<Text style={{fontSize:10,color:colors.white}}>这是你的View</Text>
</View>
<Text style={{flex:1,lineHeight:30,fontSize:14}} ellipsizeMode={'clip'} onLayout={(e)=>{
e.persist();
console.log('onLayout',e.nativeEvent);
if(e.nativeEvent.layout.height > 30){
this.data.needNextLine = true;
this.data.firstLineTextNum = parseInt(e.nativeEvent.layout.width / 14,10);
console.log('firstLineTextNum',this.data.firstLineTextNum);
this.data.nextLineText = this.data.text.substr(this.data.firstLineTextNum,this.data.text.length - this.data.firstLineTextNum);
//修改部分如下
this.data.firstLineText = this.data.text.substr(0,this.data.firstLineTextNum);
}
}} numberOfLines={this.data.needNextLine ? 1 : 0}>{this.data.needNextLine ? this.data.firstLineText : this.data.text}</Text>
</View>
{this.data.nextLineText ? <Text style={{lineHeight:30}}>{this.data.nextLineText}</Text>:null}
</View>
这样似乎没啥大毛病了,我们给它稍微加一点样式:
![](https://img.haomeiwen.com/i2649238/49da69ec596d0a42.png)
测试单行的情况也是OK的:
![](https://img.haomeiwen.com/i2649238/c476b0c55a63fb60.png)
结束
今天的博客就记录到这里,如果大家有什么疑问,或者有更好的实现方式欢迎在下方评论留言,谢谢大家观看~
晚安~
网友评论