web-dev-qa-db-fra.com

Flex box dynamique largeur et hauteur

J'essaie de créer une vue de messages à l'aide de react-native, par exemple:

enter image description here

Comme vous pouvez le voir:

  1. Les bulles ont une largeur et une hauteur dynamiques en fonction du contenu
  2. Il y a une largeur maximale pour les bulles et elles poussent vers le bas

J'essaie de recréer cette réaction en utilisant natif, cependant je ne peux que réaliser (2) et je ne suis pas sûr de savoir comment y parvenir à la fois .. c'est ce que j'ai jusqu'à présent:

enter image description here

  <View style={{flex:1,backgroundColor:'blue',flexDirection:'row'}}>
     <View style={{backgroundColor:'orange'}}>
            <View style={{width:90,flex:1}}>
              <Text>123</Text>
              </View>
     </View>
     <View style={{flex:0.25,backgroundColor:'red'}}>
              <Text>123</Text>
     </View>
  </View>

Si j'augmente la vue orange pour représenter une grosse bulle, elle disparaîtra de l'écran ... par exemple: 

enter image description here

14
kingkong

J'avais le même problème. J'ai essayé beaucoup de choses, y compris de mettre des enveloppes ici et là (comme mentionné dans les réponses précédentes). Aucun d'entre eux a travaillé.

La réponse de @ André Junges n'est pas correcte car @kingkong et moi avons des exigences différentes.

Ensuite, j'ai vu que flex peut aussi prendre la valeur -1. Et le réglage a résolu le problème.

Voici le code:

const messages = [
              'hello',
              'this is supposed to be a bit of a long line.',
              'bye'
              ];
return (
  <View style={{
            position: 'absolute',
            top: 0,
            left: 0,
            width: 150,
            alignItems: 'flex-end',
            justifyContent: 'flex-start',
            backgroundColor: '#fff',
            }}>

{messages.map( (message, index) => (
  <View key={index} style={{
                  flexDirection: 'row',
                  marginTop: 10
                }}>
    <View style={{
                  flex: -1,
                  marginLeft: 5,
                  marginRight: 5,
                  backgroundColor: '#CCC',
                  borderRadius: 10,
                  padding: 5,
                }}>
      <Text style={{
                    fontSize: 12,
                    }}>
        {message}
      </Text>
    </View>
    <Image source={require('some_path')} style={{width:30,height:30}} />
   </View> 
  ))} 
 </View>
)

Et voici le résultat:

 And here is the result:

15
vin

J'ai mis au point un moyen quelque peu artificiel de le faire. Regardons d'abord le problème.

Nous pouvons utiliser flexbox pour placer le "badge" à gauche et le texte à droite, puis les "lignes de message" descendant horizontalement. C'est facile, mais nous souhaitons que la ligne de message change de largeur en fonction de son contenu. Flexbox ne vous laissera pas le faire, car elle s'agrandit goulument pour occuper tout l'espace disponible.

Nous avons besoin d'un moyen de vérifier la largeur du texte du message, puis de redimensionner la vue en conséquence, en la forçant à une largeur spécifiée. Nous ne pouvons pas utiliser measure pour obtenir la largeur du texte car cela ne nous donne en fait que la largeur du nœud sous-jacent, pas le texte lui-même.

Pour ce faire, j'ai volé une idée à partir d'ici et créé un pont vers Obj-C qui crée un UILabel avec le texte et obtient ainsi sa largeur. 

//  TextMeasurer.h
#import "RCTBridgeModule.h"
#import <UIKit/UIKit.h>

@interface TextMeasurer : NSObject<RCTBridgeModule>

@end


//  TextMeasurer.m
#import "TextMeasurer.h"

@implementation TextMeasurer

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(get:(NSString *)text cb:(RCTResponseSenderBlock)callback)
{
  UILabel *label = [[UILabel alloc]init];
  label.font = [UIFont fontWithName:@"Helvetica" size:14.0];
  label.text = text;

  callback(@[[NSNumber numberWithDouble: label.intrinsicContentSize.width]]);
}

@end

J'ai ensuite intégré l'utilisation de ceci dans un composant:

var AutosizingText = React.createClass({
    getInitialState: function() {
        return {
            width: null
        }
    },

    componentDidMount() {
        setTimeout(() => {
            this.refs.view.measure((x, y, width, height) => {
                TextMeasurer.get(this.props.children, len => {
                    if(len < width) {
                        this.setState({
                            width: len
                        });
                    }
                })
            });
        });
    },

    render() {
        return <View ref="view" style={{backgroundColor: 'red', width: this.state.width}}><Text ref="text">{this.props.children}</Text></View>
    }
});

Cela ne fera que redimensionner la vue contenant si la largeur du texte est inférieure à la largeur originale de la vue - qui aura été définie par flexbox. Le reste de l'application ressemble à ceci:

var messages = React.createClass({
    render: function() {
        var rows = [
            'Message Text',
            'Message Text with lots of content ',
            'Message Text with lots of content put in here ok yeah? Keep on talking bla bla bla whatever is needed to stretch this message body out.',
            'Keep on talking bla bla bla whatever is needed to stretch this message body out.'
        ].map((text, idx) => {
            return <View style={styles.messageRow}>
                <View style={styles.badge}><Text>Me</Text></View>
                <View style={styles.messageOuter}><AutosizingText>{text}</AutosizingText></View>
            </View>
        });

        return (
            <View style={styles.container}>
                {rows}
            </View>
        );
    }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'flex-start',
    alignItems: 'stretch',
    flexDirection: 'column',
    backgroundColor: '#F5FCFF',
  },
  messageRow: {
    flexDirection: 'row',
    margin: 10
  },
  badge: {
    backgroundColor: '#eee',
    width: 80, height: 50
  },
  messageOuter: {
    flex: 1,
    marginLeft: 10
  },
  messageText: {
    backgroundColor: '#E0F6FF'
  }
});

AppRegistry.registerComponent('messages', () => messages);

Et cela vous donne ceci:

enter image description here

Je garderais un œil sur la question Github car cette solution est certainement un peu maladroite et à tout le moins je m'attendrais à voir un meilleur moyen de mesurer le texte dans RN à un moment donné.

4
Colin Ramsay

Je sais que cette question a été posée il y a un an, mais j'avais un problème similaire et je pense que cela peut être utile pour d'autres développeurs.

Donc, ce que j’essayais de faire, c’était de créer des boîtes comme celle ci-dessous, où j’aurais un texte à gauche et une flèche à droite.
Notez que les cases doivent avoir une hauteur auto - car le texte peut comporter plusieurs lignes.

 enter image description here

JSX

<View style={styles.container}>
  <View style={styles.textContainer}>
    <Text style={styles.text}>{props.message.text}</Text>
  </View>
  <View style={styles.iconContainer}>
    <Icon name="chevron-right" size={30} color="#999" />
  </View>
</View>

Modes

container: {
  flex: 1,
  flexDirection: 'row',
  alignItems: 'center',
  backgroundColor: '#FFF',
  marginBottom: Metrics.baseMargin,
  borderRadius: 3,
},
textContainer: {
  flex: 1,
  flexDirection: 'column',
  paddingVertical: 30,
  paddingHorizontal: Metrics.doubleBaseMargin,
},
text: {
  color: '#333',
},
iconContainer: {
  width: 35,
  borderLeftWidth: 1,
  borderLeftColor: '#ddd',
  justifyContent: 'center',
  alignItems: 'center',
  alignSelf: 'stretch',
},

Et bien sûr, ça n'a pas marché. Pour une raison quelconque, si j'avais un parent avec flexDirection: 'row', l'enfant ne grandirait pas. Le texte était entouré de plusieurs lignes, mais la hauteur du parent (.textContainer) n'augmentait pas - et le texte était affiché même en dehors du conteneur dans certains cas. Vous pouvez le voir dans l'image ci-dessous (troisième message).

 enter image description here

Le correctif consistait à envelopper tout ce composant avec une View supplémentaire, comme le code ci-dessous:

<View style={styles.wrapper}>
  <View style={styles.container}>
    <View style={styles.textContainer}>
      <Text style={styles.text}>{props.message.text}</Text>
    </View>
    <View style={styles.iconContainer}>
      <Icon name="chevron-right" size={30} color="#999" />
    </View>
  </View>
</View>

La classe .wrapper n'a que des détails sur les styles (déplacée de .container)

wrapper: {
  backgroundColor: '#FFF',
  marginBottom: Metrics.baseMargin,
  borderRadius: 3,
},
2
André Junges

Donc, je pense que la stratégie ici est que vous voulez contenir la bulle de message dans un bloc de largeur, puis avoir deux vues intérieures. Une vue interne tente d'être aussi fine que possible, c'est celle dans laquelle vous insérez du texte. L'autre tente d'être aussi large que possible, condensant ainsi l'autre bloc juste assez large pour contenir le texte.

<View style={{flex:1,backgroundColor:'blue',flexDirection:'row'}}>
  <View style={{flex:0,backgroundColor:'red'}}>
    <Text>123</Text>
  </View>
  <View style={{backgroundColor:'orange', flex: 1}}/> 
</View>
1
iLoch

Peut ne pas être utile selon le codage. Mais semble que cela ne fonctionnera pas pour le contenu dynamique, fonctionne avec du contenu statique uniquement ...

0
Sanjeev Rao