Chapitre 3.2 — View, Text, Image
Où on en est : le mental model est posé. On détaille maintenant les trois primitives qui composent 80 % de n’importe quel écran — avec leurs props réellement utiles.
⏱️ TL;DR —
View= conteneur pur (layout, fond, bordures).Text= tout le texte, avecnumberOfLines,ellipsizeModeet l’imbrication.Image=source(require local vs URI distant),resizeMode, dimensions obligatoires pour les images distantes. Pour les vraies apps : expo-image, plus performant (cache disque, placeholders, transitions).
🎯 Objectifs
- Utiliser les props utiles de View, Text et Image (la vraie liste courte).
- Comprendre
require()vs URI et pourquoi les images distantes exigent des dimensions. - Choisir entre
Imagecore etexpo-image. - Construire la première carte de thème de Drill.
View — le conteneur
View n’affiche rien par elle-même : elle organise (flexbox, Partie 4) et décore
(fond, bordure, ombre). Ses props courantes :
<View
style={styles.card} // le layout et la déco (Partie 4)
pointerEvents="none" // laisser passer les touchers (overlays)
accessibilityRole="summary" // sémantique pour lecteurs d'écran
testID="theme-card" // hook pour les tests E2E (Partie 14)
/>C’est volontairement pauvre : une View est un ViewGroup Android nu. Toute la richesse vient
du style et de la composition.
Text — plus subtil qu’il n’y paraît
Au-delà de l’imbrication (chapitre 3.1), trois props sauvent des écrans entiers :
// L'intitulé d'une question Drill, borné à 3 lignes
<Text numberOfLines={3} ellipsizeMode="tail" style={styles.prompt}>
{question.prompt}
</Text>numberOfLines: tronque au-delà de N lignes — indispensable dans les cartes et les listes, où un contenu long casserait la hauteur.ellipsizeMode: où placer le « … » (tailpar défaut,middleutile pour les chemins).onTextLayout: mesurer le texte réellement rendu (cas avancés).
Deux détails Android à connaître : includeFontPadding: false (style) retire le padding
vertical fantôme des polices — utile pour aligner des chiffres de score ; et le font scaling
système (l’utilisateur peut grossir sa police : testez avec, ne bloquez allowFontScaling
qu’en dernier recours — l’accessibilité prime).
Image — le piège des dimensions
// Locale : require, dimensions lues dans le fichier — rien à préciser
<Image source={require('../assets/images/logo.png')} style={{ width: 96, height: 96 }} />
// Distante : URI… et dimensions OBLIGATOIRES, sinon 0×0 !
<Image
source={{ uri: 'https://raw.githubusercontent.com/alex/drill-questions/main/badges/php.png' }}
style={{ width: 48, height: 48 }}
resizeMode="contain"
/>Pourquoi cette asymétrie ? Une image require() est connue au build : Metro lit ses
dimensions. Une URI ne l’est qu’au chargement — RN ne réserve donc aucun espace par défaut.
L’oubli classique : « mon image distante ne s’affiche pas » = pas de width/height.
resizeMode répond à « comment remplir le cadre » : cover (remplit, rogne — défaut),
contain (tout visible, letterbox), stretch (déforme — à éviter), center.
💡 Pour un dev Next.js —
require()d’une image ≈ l’import statique denext/image: connue au build, optimisée, dimensionnée. L’URI ≈ une image distante sanswidth/height— que Next refuse aussi ! Même problème (réserver l’espace), solutions cousines.
expo-image — le choix des vraies apps
Le composant Image core est basique : cache mémoire sommaire, pas de placeholder, pas de
transition. Dès qu’une app charge des images réseau en série (avatars, badges de thèmes),
expo-image s’impose :
npx expo install expo-imageimport { Image } from 'expo-image'
<Image
source="https://…/badge-symfony.png"
style={{ width: 48, height: 48 }}
contentFit="contain" // remplace resizeMode
placeholder={{ blurhash }} // flou élégant pendant le chargement
transition={200} // fondu à l'arrivée
cachePolicy="disk" // cache persistant
/>Décision de cours : Drill utilise expo-image partout où une image vient du réseau, et
l’Image core pour les assets locaux simples. (Drill reste sobre en images — l’essentiel de
son UI est du texte et des composants stylés.)
La première carte de Drill
Assemblons les trois piliers — la carte d’un thème, qu’on retrouvera dans la liste des thèmes :
// components/ThemeCard.tsx — v1 (Pressable au chapitre suivant, style réel en Partie 4)
import { StyleSheet, Text, View } from 'react-native'
type Props = {
title: string // « Symfony »
questionCount: number
emoji: string // « 🎼 » — pas d'images à télécharger pour la v1
}
export function ThemeCard({ title, questionCount, emoji }: Props) {
return (
<View style={styles.card}>
<Text style={styles.emoji}>{emoji}</Text>
<View style={styles.body}>
<Text style={styles.title} numberOfLines={1}>{title}</Text>
<Text style={styles.meta}>{questionCount} questions</Text>
</View>
</View>
)
}
const styles = StyleSheet.create({
card: {
flexDirection: 'row',
alignItems: 'center',
gap: 12,
backgroundColor: '#101b21',
borderRadius: 14,
padding: 16,
},
emoji: { fontSize: 32 },
body: { flex: 1 },
title: { color: '#e2e8f0', fontSize: 18, fontWeight: '700' },
meta: { color: '#7a8b94', fontSize: 13 },
})📱 Essaie sur ton téléphone — Importez
ThemeCarddansapp/index.tsxet affichez-en deux ou trois (Symfony, PHP, Next.js) sous le titre. Jouez avecnumberOfLinesen donnant un titre très long. C’est la graine de l’écran Thèmes (Partie 5).
✏️ Exercices
1. Pourquoi ce code n’affiche-t-il rien, et quelles sont les DEUX corrections possibles ?
<Image source={{ uri: 'https://picsum.photos/200' }} />✅ Solution
Une image distante n’a aucune dimension intrinsèque connue avant chargement → rendu 0×0.
Corrections : (1) donner style={{ width: 200, height: 200 }} ; (2) la faire remplir un parent
dimensionné (style={{ flex: 1 }} dans une View de taille connue, ou width: '100%' +
aspectRatio: 1).
2. Ajoutez à ThemeCard un badge « NOUVEAU » qui ne s’affiche que si une prop isNew est
vraie — sans tomber dans le piège du &&.
✅ Solution
type Props = { /* … */ isNew?: boolean }
{isNew === true && (
<View style={styles.badge}>
<Text style={styles.badgeText}>NOUVEAU</Text>
</View>
)}isNew est optionnel (undefined possible) : isNew === true (ou !!isNew) garantit un
booléen à gauche du &&. Le texte du badge vit dans un Text, jamais nu.
3. Un titre de thème « Programmation orientée objet avancée en PHP 8.5 » casse la mise en page de la carte. Réparez sans changer la taille de police.
✅ Solution
Déjà fait dans notre version : numberOfLines={1} sur le titre (+ ellipsizeMode par défaut
tail) → « Programmation orientée ob… ». La View body en flex: 1 est l’autre moitié de la
solution : elle borne la largeur disponible du texte au lieu de laisser la ligne pousser la carte.
🧠 Quiz
1. Pourquoi les images require() n’exigent-elles pas de dimensions ?
Réponse
Elles sont résolues au build par Metro, qui embarque leurs dimensions intrinsèques. Les URI distantes ne sont connues qu’au chargement → il faut réserver l’espace soi-même.
2. Quelle prop borne un Text à 2 lignes avec « … » ?
Réponse
numberOfLines={2} (avec ellipsizeMode, tail par défaut, pour la position des points de
suspension).
3. Citez trois avantages d’expo-image sur l’Image core.
Réponse
Cache disque persistant (cachePolicy), placeholders élégants (blurhash), transitions de
chargement, meilleures perfs de décodage — pour toute image réseau.
4. Différence entre cover et contain ?
Réponse
cover remplit tout le cadre quitte à rogner l’image ; contain montre TOUTE l’image
quitte à laisser des bandes vides. (stretch déforme — à éviter.)
5. À quoi sert testID ?
Réponse
À identifier un élément pour les tests (RN Testing Library, Maestro E2E — Partie 14), sans dépendre du texte affiché.
👉 Chapitre suivant : 3.3 — Pressable & le tactile — le doigt n’est pas une souris.