Dia 2 — Tamanho, espaçamento e borda

Informatica · Conteudo · publicado em 05/10/2026
Dia 2 de 16

Tamanho, espaçamento e borda

Aula 1

width, height, flex e o limite

width, height, flex e o limite

width e height são número de pixel: width: 120 ocupa 120 pixels, sempre,

em qualquer aparelho. No celular isso quase nunca é o que se quer, porque a

largura útil muda de um modelo para outro. A resposta do sistema de layout é

flex, e flex: 1 significa "cresça para ocupar o que sobrar". Um View com

flex: 1 dentro de uma coluna de altura definida cresce até encostar no

próximo irmão; dois irmãos com flex: 1 dividem o espaço ao meio; com

flex: 2 e flex: 1, o primeiro fica com o dobro.

O exemplo desta página começa pelo atalho: flex: 1 expande para `{ flexGrow: 1,

flexShrink: 1, flexBasis: '0%' } e flex: 2 para { flexGrow: 2, flexShrink: 1,

flexBasis: '0%' }. Depois faz a conta da divisão em 200 pixels: com flex: 1` e

flex: 2 o resultado é 67 e 133; com dois flex: 1 é 100 e 100. Com

dois filhos sem flex nenhum, a mesma função devolve NaN e NaN — não há

proporção para dividir, e o erro é o sinal de que flex não foi declarado.

Porcentagem funciona nos dois: width: '100%' é a largura do pai, e

width: '50%' é metade dela — e é isso que o exemplo imprime no último console,

50%. A diferença entre porcentagem e flex é quem decide o tamanho: na

porcentagem o filho decide; no flex o pai distribui o espaço que sobrou. Isso

importa em lista com item de largura variável — onde a porcentagem é o certo,

porque cada item quer uma fração do pai.

flex é um atalho de três propriedades

flex: 1 escreve, expandido, flexGrow: 1, flexShrink: 1, flexBasis: 0%. O

flexBasis é o tamanho inicial: 0% significa que o cálculo começa do zero e

o espaço é distribuído por proporção. É por isso que dois filhos com flex: 1

ficam exatamente iguais mesmo com conteúdos de tamanhos muito diferentes — o

conteúdo não entra na conta. Quando se escreve flex: 1 e o resultado é

estranho, quase sempre o culpado é flexBasis assumindo o conteúdo, e a

solução é flexBasis: 0 explícito ou width fixa em um dos lados.

O flexGrow é o número que entra na divisão, e é ele que o exemplo usa no

cálculo: 200 pixels com flex: 1 e flex: 2 dão 67 e 133, que é a mesma

regra na proporção 1:2. Quando a função do exemplo não encontra nenhum

flexGrow — os dois filhos com estilo vazio — o total dá zero, e a divisão por

zero aparece como NaN. Esse NaN é o mesmo defeito que aparece na tela quando

se espera que o filho ocupe o espaço e ele fica com o tamanho do conteúdo.

O flexShrink é o que impede o transbordo horizontal: com nowrap e filhos

largos demais, o padrão do flex é encolher. Colocar flexShrink: 0 em um

item de largura intrínseca tira essa proteção e é a forma mais rápida de fazer

uma linha transbordar.

A função expandeFlex do exemplo mostra o outro ramo: quando o valor não é um

número, ela devolve { flexGrow: 0, flexShrink: 1, flexBasis: 'auto' }. É o

flex que não foi declarado, e flexBasis: 'auto' é a diferença entre ocupar o

espaço sobrando e medir pelo conteúdo.

min e max são limites, não tamanhos

minWidth, maxWidth, minHeight e maxHeight não definem o tamanho: eles

limitam. É a diferença entre um Text que pode encolher até 60 pixels de altura

e o mesmo Text com maxHeight: 60, que corta o excedente sem avisar. Para

texto com número controlado de linhas existe propriedade própria, tratada na

aula de tipografia; maxHeight em texto é a versão cega.

O limite convive com o flex sem brigar por quem manda, e o exemplo junta os

dois no mesmo objeto: { flex: 1, maxWidth: 420, minHeight: 40 }. A leitura é que

o filho cresce até o que sobrar, para no maxWidth, e nunca fica com menos que o

minHeight. Se o limite inferior e o superior se cruzam — minHeight: 100 e

maxHeight: 60 — o resultado é o menor deles, porque o piso vem depois do teto.

// a caixa ocupa o que sobrou, mas nunca passa de 420
<View style={{ flex: 1, maxWidth: 420, padding: 16 }}>
  <Text style={{ flexShrink: 1 }}>conteudo que precisa caber</Text>
</View>

flexShrink: 1 no Text é o que permite o conteúdo caber dentro do limite: sem

ele, um texto largo empurra a caixa para fora do maxWidth em vez de encolher

dentro dela. Os dois lados do exemplo — flex: 1 no pai, flexShrink: 1 no

filho — são o par que resolve largura variável.

O limite também é a ferramenta de responsividade mais barata que existe: um

maxWidth não pergunta o tamanho da tela, ele só impede o crescimento. Em vez

de decidir por aparelho, deixa o conteúdo ocupar o que tem e trava em um teto.

a medida que vem de fora

Número fixo serve para o que é sempre igual: a barra de rodapé, um botão de

48 de altura, a margem padrão da tela. Tudo que depende do aparelho — largura da

coluna, quantos itens cabem numa linha, altura disponível depois do teclado — é

medido em tempo de execução, e o valor medido entra no estilo como `width:

medida` em vez de um número escrito à mão. É o mesmo caminho que a aula de

ajuste por tamanho de tela percorre, com o hook que entrega a janela.

O exemplo fecha nessa distinção com as duas medidas lado a lado: o pai de altura

fixa imprime 200, um número escrito no StyleSheet, e o filho responde 50%,

uma proporção que só significa alguma coisa com a medida do pai. As duas formas

convivem no mesmo arquivo sem se misturar — o que decide é se o número pertence

à caixa ou ao aparelho.

Exemplo

// `flex` e a forma de ocupar o espaco que sobrou, e ele e um atalho de
// tres propriedades. O exemplo expande o atalho e calcula como o espaco
// e dividido entre os filhos.
const { View, Text, StyleSheet } = require('react-native');

const estilos = StyleSheet.create({
  linha: { flexDirection: 'row', height: 200 },
  coluna: { flex: 1 },
});

function expandeFlex(valor) {
  const numero = typeof valor === 'number' ? valor : parseFloat(String(valor));
  if (!numero) {
    return { flexGrow: 0, flexShrink: 1, flexBasis: 'auto' };
  }
  return { flexGrow: numero, flexShrink: 1, flexBasis: '0%' };
}

console.log('flex: 1 vira:', expandeFlex(1));
console.log('flex: 2 vira:', expandeFlex(2));

// O espaco que sobra e dividido na proporcao dos `flexGrow`.
function divide(espaco, filhos) {
  const total = filhos.reduce((soma, f) => soma + f.flex, 0);
  if (total === 0) return filhos.map(() => espaco / filhos.length);
  return filhos.map((f) => Math.round((espaco * f.flex) / total));
}

console.log('200 px para flex 1 e flex 2:', divide(200, [{ flex: 1 }, { flex: 2 }]));
console.log('200 px para dois flex 1:', divide(200, [{ flex: 1 }, { flex: 1 }]));
console.log('200 px sem flex nenhum:', divide(200, [{}, {}]));

// A arvore montada mostra que `flex` viaja dentro do estilo do filho.
function Linha() {
  return (
    <View style={estilos.linha}>
      <View style={estilos.coluna}>
        <Text>lateral</Text>
      </View>
      <View style={estilos.coluna}>
        <Text>conteudo</Text>
      </View>
    </View>
  );
}

const arvore = Linha();
console.log('altura fixa do pai:', StyleSheet.flatten(arvore.props.style).height);
console.log('estilo do primeiro filho:', StyleSheet.flatten(arvore.props.children[0].props.style));
console.log('os dois filhos tem o mesmo estilo:', arvore.props.children[0].props.style === arvore.props.children[1].props.style);

// Limites: `maxWidth` limita, nao define.
const comLimite = StyleSheet.flatten({ flex: 1, maxWidth: 420, minHeight: 40 });
console.log('com limite:', comLimite);
console.log('largura percentual e medida do pai:', StyleSheet.flatten({ width: '50%' }).width);

Saída real

flex: 1 vira: { flexGrow: 1, flexShrink: 1, flexBasis: '0%' }
flex: 2 vira: { flexGrow: 2, flexShrink: 1, flexBasis: '0%' }
200 px para flex 1 e flex 2: [ 67, 133 ]
200 px para dois flex 1: [ 100, 100 ]
200 px sem flex nenhum: [ NaN, NaN ]
altura fixa do pai: 200
estilo do primeiro filho: { flex: 1 }
os dois filhos tem o mesmo estilo: true
com limite: { flex: 1, maxWidth: 420, minHeight: 40 }
largura percentual e medida do pai: 50%
Aula 2

padding, margin e borda

padding, margin e borda

padding é o espaço dentro da caixa, entre a borda e o conteúdo, e ele

empurra o conteúdo para longe da borda sem aumentar a caixa quando a caixa tem

altura fixa. margin é o espaço de fora, entre a caixa e os irmãos, e é o

que empurra as outras caixas. A diferença aparece em dois lugares: padding

some com o fundo da caixa, margin não — o fundo atravessa a área do margin e

o que se vê é o fundo do pai. E padding aumenta o tamanho da caixa quando o

tamanho é automático; margin nunca aumenta o tamanho de ninguém.

O exemplo desta página monta um cartão com as três coisas juntas — padding: 12,

marginHorizontal: 16, marginBottom: 8, mais borda e raio — e imprime o

estilo achatado. É a prova de que padding e margin convivem no mesmo objeto

sem se misturar: são chaves diferentes, com efeitos diferentes, e nenhuma

sobrescreve a outra.

Os atalhos existem para os quatro lados: paddingVertical e

paddingHorizontal são paddingTop mais paddingBottom, e

paddingHorizontal é paddingLeft mais paddingRight. Escrever o valor curto

cabe em uma linha e evita o erro de esquecer um dos lados — que é o defeito

clássico de um card com texto colado na lateral só de um lado.

A função expandeAtalho do exemplo faz essa conta na mão e mostra o resultado:

paddingVertical: 8 e paddingHorizontal: 16 viram um objeto com as quatro

chaves por extenso, paddingTop: 8, paddingBottom: 8, paddingLeft: 16 e

paddingRight: 16. Os atalhos originais continuam no objeto ao lado das quatro

chaves — o que mostra que atalho e valor por extenso são a mesma medida, e que

escrever os dois é redundância, não soma.

// atalho lateral: mesma medida nos dois lados
<Text style={{ paddingHorizontal: 16 }}>linha do formulario</Text>
// equivalente, escrito por extenso
<Text style={{ paddingLeft: 16, paddingRight: 16 }}>linha do formulario</Text>

margin tem os mesmos atalhos, mais marginTop isolado — que é a forma de

empurrar um bloco para baixo sem mexer nos lados. Padding em marginTop não

existe: para espaçar título de conteúdo, é marginBottom no título.

a borda entra na conta do tamanho

borderWidth soma no tamanho da caixa. Uma caixa com width: 100 e

borderWidth: 2 tem 104 de largura: os 2 pixels de cada lado são borda, não

conteúdo — e é exatamente isso que a função tamanhoExterno do exemplo devolve,

{ largura: 104, altura: 4 }. A altura de 4 vem da mesma conta em cima de uma

altura que não foi declarada: 0 de conteúdo mais 2 de borda de cada lado. É

a forma mais curta de entender por que width: '100%' com borda transborda a

caixa em 4 pixels.

borderRadius só faz sentido junto com borderWidth ou com

backgroundColor — raio sem nada para arredondar arredonda nada, e é o motivo

de borderRadius: 12 sozinho não mudar nada na tela. O exemplo mostra os dois

casos lado a lado: so raio imprime { borderRadius: 12 }, e raio com borda

imprime { borderRadius: 12, borderWidth: 2 }. A chave não muda; o que muda é se

existe pixel para curvar.

O raio é medido nos cantos e o valor não passa de metade do lado menor: um

borderRadius: 40 num quadrado de 60 vira uma forma de 40, não um círculo. O

exemplo faz essa conta com Math.min e imprime 12 como raio efetivo quando o

pedido foi 40 numa caixa de 60. Pedir mais que o permitido não dá erro: o valor

simplesmente satura, e o raio vira metade do lado menor.

borderColor sem borderWidth também não aparece, pela mesma razão: o estilo

precisa da espessura para existir. E borderTopWidth: 0 com os demais lados

presentes é o separador de uma linha: é o jeito mais leve de desenhar um

divisor entre dois itens de lista.

espaçamento que não depende do estilo repetido

Repetir marginBottom: 12 em quinze itens é o caminho curto para um arquivo que

ninguém consegue revisar. A alternativa é uma constante no módulo do estilo e um

map que aplica: o número deixa de estar espalhado e passa a ter um nome. Se

depois o espaçamento mudar, muda em um lugar só. É a mesma jogada do

StyleSheet.create — trocar número escrito por valor com nome.

O exemplo usa marginBottom: 8 num lugar só — o estilo do título do cartão — e

padding: 12 no cartão inteiro. O espaçamento vertical entre o título e o texto

não vem de um número repetido em cada Text, e sim de uma chave no StyleSheet

que o map reaproveita. É por isso que o estilo da raiz impresso pelo exemplo

continua legível: cada número tem um nome que diz o que ele controla.

Precisa deUse
afastar do próprio conteúdopadding
afastar de outro elementomargin
desenhar um separadorborderBottomWidth sem borderBottomColor transparente
arredondar só um cantoborderTopLeftRadius

O separador do exemplo é a linha real dessa tabela: `{ borderBottomWidth: 1,

borderBottomColor: '#e6e8eb' } num View` sem altura nem filhos. Um elemento

vazio só com borda desenha uma linha de 1 pixel na base, e é mais leve que um

View de altura 1 com fundo — porque não cria caixa, não ocupa espaço e não

entra na conta do flex do pai.

Exemplo

// `padding` e espaco dentro da caixa, `margin` e espaco fora dela. O
// exemplo calcula o tamanho final de uma caixa e mostra por que a borda
// soma no tamanho e por que o raio sozinho nao arredonda nada.
const { View, Text, StyleSheet } = require('react-native');

const estilos = StyleSheet.create({
  cartao: {
    padding: 12,
    marginHorizontal: 16,
    marginBottom: 8,
    borderWidth: 2,
    borderColor: '#d0d7de',
    borderRadius: 10,
  },
  titulo: { marginBottom: 6, fontSize: 16 },
  separador: { borderBottomWidth: 1, borderBottomColor: '#e6e8eb' },
});

// atalho e a mesma medida nos dois lados
function expandeAtalho(estilo) {
  const saida = Object.assign({}, estilo);
  if (estilo.paddingVertical !== undefined) {
    saida.paddingTop = estilo.paddingVertical;
    saida.paddingBottom = estilo.paddingVertical;
  }
  if (estilo.paddingHorizontal !== undefined) {
    saida.paddingLeft = estilo.paddingHorizontal;
    saida.paddingRight = estilo.paddingHorizontal;
  }
  return saida;
}

const cartao = StyleSheet.flatten(estilos.cartao);
console.log('cartao:', cartao);
console.log('atalhos expandidos:', expandeAtalho({ paddingVertical: 8, paddingHorizontal: 16 }));

// A borda SOMA no tamanho. A caixa de 100 com borda de 2 tem 104.
function tamanhoExterno(estilo) {
  const largura = (estilo.width || 0) + 2 * (estilo.borderWidth || 0);
  const altura = (estilo.height || 0) + 2 * (estilo.borderWidth || 0);
  return { largura, altura };
}

console.log('width 100 com borderWidth 2:', tamanhoExterno({ width: 100, borderWidth: 2 }));

// Padding aumenta a caixa quando o tamanho e automatico; margin nunca.
function alturaDe(conteudo, estilo) {
  return conteudo + 2 * (estilo.padding || 0);
}

console.log('altura com padding 12:', alturaDe(40, { padding: 12 }), '| sem padding:', alturaDe(40, {}));

// Raio sem borda e raio sem fundo: nada aparece para arredondar.
console.log('so raio:', StyleSheet.flatten({ borderRadius: 12 }));
console.log('raio com borda:', StyleSheet.flatten({ borderRadius: 12, borderWidth: 2 }));

// O raio nunca passa de metade do lado menor.
const raioReal = Math.min(12, Math.min(60, 60) / 2);
console.log('raio pedido 40 numa caixa 60, o raio efetivo:', raioReal);

// A arvore montada leva o estilo inteiro no objeto, e o `flatten` resolve.
function Cartao(props) {
  return (
    <View style={estilos.cartao}>
      <Text style={estilos.titulo}>{props.titulo}</Text>
      <View style={estilos.separador} />
      <Text>{props.corpo}</Text>
    </View>
  );
}

const arvore = Cartao({ titulo: 'Pedido 41', corpo: 'dois itens' });
console.log('tipo da raiz:', arvore.type);
console.log('estilo da raiz:', StyleSheet.flatten(arvore.props.style));
console.log('separador:', StyleSheet.flatten(arvore.props.children[1].props.style));

Saída real

cartao: {
  padding: 12,
  marginHorizontal: 16,
  marginBottom: 8,
  borderWidth: 2,
  borderColor: '#d0d7de',
  borderRadius: 10
}
atalhos expandidos: {
  paddingVertical: 8,
  paddingHorizontal: 16,
  paddingTop: 8,
  paddingBottom: 8,
  paddingLeft: 16,
  paddingRight: 16
}
width 100 com borderWidth 2: { largura: 104, altura: 4 }
altura com padding 12: 64 | sem padding: 40
so raio: { borderRadius: 12 }
raio com borda: { borderRadius: 12, borderWidth: 2 }
raio pedido 40 numa caixa 60, o raio efetivo: 12
tipo da raiz: View
estilo da raiz: {
  padding: 12,
  marginHorizontal: 16,
  marginBottom: 8,
  borderWidth: 2,
  borderColor: '#d0d7de',
  borderRadius: 10
}
separador: { borderBottomWidth: 1, borderBottomColor: '#e6e8eb' }