RN中Text嵌套View的实现

最近在做项目的时候,遇到了这样的一个设计:

image.png

乍看之下平平无奇,但其实暗藏玄机...我想了蛮久的时间才比较完美的实现了这个样式,所以特此记录。

我们仔细观察这个样式,它是由两部分组成,一个View组件和Text文本。
而它的特别之处就在于这个View组件是融合进Text文本中的,而不是独立的两个区域。

经过翻看官方文档得知,Text组件里面是可以包裹Text组件的:


image.png

代码如下:

 <Text style={{color:'red'}}>我是第一个Text<Text style={{color:'blue'}}>我是第二个</Text></Text>

既然如此,我们能不能在Text组件里包裹View组件呢?
通过查阅文档,我发现是可以的(虽然文档写着仅限iOS,但实测Android也是可以的...)


image.png

于是乎我写了如下的代码:

<View style={{width:300,backgroundColor:colors.gray}}>
    <Text style={{color:'red'}}>
       <View style={{width:100,height:30,backgroundColor:'blue'}}></View>
       {'我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本我是后面的文本'}
    </Text>
</View>

得到了这样的效果:


上为 iOS,下为 Android

嗯...似乎还不错,那我们按照设计稿接着写:

<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组件,得到了如下效果:


image.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组件的宽,再把它赋值给占位组件就行了。

image.png

嗯...这Android是咋回事?!

在尝试之后发现,在Android上似乎不能动态更新Text组件内那个View组件的宽度,我怀疑可能和Android的渲染机制有关...

既然如此...那这种方法也只好放弃了

于是我又开始对着设计稿沉思...突然又一道灵光乍现...

我们其实可以把这个样式看成三部分:

image.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>

效果如下:


image.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>

这样似乎没啥大毛病了,我们给它稍微加一点样式:

image.png

测试单行的情况也是OK的:


image.png

结束

今天的博客就记录到这里,如果大家有什么疑问,或者有更好的实现方式欢迎在下方评论留言,谢谢大家观看~

晚安~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 221,635评论 6 515
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,543评论 3 399
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 168,083评论 0 360
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,640评论 1 296
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,640评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,262评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,833评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,736评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,280评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,369评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,503评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,185评论 5 350
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,870评论 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,340评论 0 24
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,460评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,909评论 3 376
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,512评论 2 359