Dia 4 — Imagem, toque e evento

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

Imagem, toque e evento

Aula 1

Image, require e a pasta de imagens

Image, require e a pasta de imagens

Image é o componente de imagem, e a propriedade source recebe a origem em

duas formas que não se misturam. A local é um require de um caminho, e o

require é uma função do JavaScript que o ambiente de build entende: ele

transforma o caminho em um número de recurso dentro do aplicativo, e é por isso

que o caminho tem de ser literal — require('./logo.png') funciona e

require(caminhoVariavel) não, porque o build precisa ler o arquivo antes de o

código rodar e não consegue adivinhar o valor de uma variável.

const logo = require('./assets/logo.png');

<Image source={logo} style={{ width: 120, height: 40 }} />

O motivo do caminho literal fica claro pelo lado do require: ele não é um

import comum, é uma chamada que o build instrumenta. No exemplo, a função

carregarImagem registra o caminho num array e devolve um objeto com o

recurso, e o array final sai como [ './assets/logo.png' ]. Um único caminho

entrou. Se o caminho viesse de uma variável, essa linha do array seria undefined

— e o Image receberia um recurso que não existe.

A remota é uma uri, uma string com o endereço completo começando por

https://. source={{ uri: endereco }} é o objeto, e a string sozinha não

serve. A consequência prática é que uma imagem remota ocupa memória e tempo de

rede: o Image mostra o retângulo vazio até o download terminar, e por isso a

placeholder e a imagem de carregamento são parte do componente, não um detalhe

de estilo.

As duas formas chegam ao componente com a mesma chave source e formatos

diferentes: o exemplo imprime { uri: 'https://exemplo.test/capa/41.jpg' } para

a remota e { recursoDoApp: './assets/logo.png' } para a local. O

uri é o endereço que o aparelho busca em tempo de execução; o recurso do app

já está no pacote instalado. A mesma chave, dois lugares onde a imagem está.

resizeMode decide o que sobra

contain mostra a imagem inteira e deixa espaço vazio ao redor; cover preenche

o retângulo inteiro e corta o que sobra; stretch distorce até caber, e é a

única das três que muda a proporção — usar stretch em foto é o defeito mais

visível de uma tela mal acabada. O padrão é cover.

A caixa e a foto são coisas separadas, e é essa separação que costuma

confundir: width e height no Image definem o retângulo que a imagem

ocupa na tela, e resizeMode decide o que a foto faz dentro desse retângulo.

cover com width: 100, height: 40 corta bastante foto vertical; contain com

as mesmas medidas deixa a foto pequena e centralizada. Escolher resizeMode

antes de escolher a medida evita a metade dos retrabalho.

O exemplo separa as duas coisas em duas funções. A capa declara

resizeMode: 'cover' e mede { largura: '100%', altura: 180 }; a marca declara

resizeMode: 'contain' e mede { largura: 120, altura: 40 }. As duas caixas

têm formatos opostos — uma ocupa tudo e corta, a outra é pequena e cabe — e é a

mesma propriedade decidindo os dois casos. O resizeMode não diz o tamanho da

foto: diz o que acontece com o que não cabe na caixa.

// retrato: a foto preenche e o que sobra e cortado
<Image source={{ uri }} style={{ width: '100%', height: 180 }} resizeMode="cover" />
// logo: a imagem inteira aparece, com o espaco sobrando
<Image source={logo} style={{ width: 120, height: 40 }} resizeMode="contain" />

O último console do exemplo é o teste que separa as três coisas: um Image

sem resizeMode declarado responde undefined na propriedade, e o valor que

aparece na tela é o padrão da plataforma. undefined no objeto e cover na

tela são o mesmo comportamento — é só que a segunda não está escrita no código

da aula. É por isso que declarar resizeMode explicitamente é documentação, e

não redundância.

A proporção também é a medida. stretch é o único dos três que muda a

proporção, e por isso é o único que produz uma foto que parece estragada; os

outros dois preservam a proporção e decidem o que fazer com a sobra — cortar

(cover) ou sobrar (contain).

ImageBackground e o resto

ImageBackground é um Image que aceita filhos: o conteúdo vai por cima da

foto, e é o componente de tela de abertura. Ele não tem resizeMode próprio —

usa style, com flex: 1 para cobrir tudo. Um aviso prático: a imagem de

fundo é carregada junto com o componente e, em celular, imagem de fundo pesada

é a primeira causa de tela branca na abertura.

A diferença em relação ao Image é estrutural, e está em quem recebe os filhos:

o Image não aceita filho nenhum, o ImageBackground aceita e os desenha por

cima. Por isso o flex: 1 é obrigatório no ImageBackground — sem ele a

imagem não tem altura para cobrir, e o conteúdo aparece sobre um retângulo do

tamanho da foto.

O require tem uma segunda utilidade fora da imagem: qualquer arquivo de

módulo importado com ele vira recurso do aplicativo — um JSON de configuração

ou um .txt de termos. O caminho é relativo ao arquivo que chamou, então o

mesmo nome usado de duas pastas diferentes aponta para arquivos diferentes.

Exemplo

// `source` aceita um `require` (imagem local, resolvido no build) ou um
// objeto com `uri` (imagem remota). O exemplo mostra as duas formas e o
// que o `Image` recebeu em cada caso.
const { View, Image, StyleSheet } = require('react-native');

// No app, `require` de um arquivo de imagem e resolvido pelo Metro e vira
// um numero de recurso. Aqui nao ha build, entao o exemplo registra a
// chamada para mostrar o que o componente receberia.
const requisicoes = [];
function carregarImagem(caminho) {
  requisicoes.push(caminho);
  return { recursoDoApp: caminho };
}

const logo = carregarImagem('./assets/logo.png');
const foto = { uri: 'https://exemplo.test/capa/41.jpg' };

const estilos = StyleSheet.create({
  capa: { width: '100%', height: 180 },
  marca: { width: 120, height: 40 },
  cartao: { width: 200, height: 100 },
});

function Capa() {
  return <Image source={foto} style={estilos.capa} resizeMode="cover" />;
}

function Marca() {
  return <Image source={logo} style={estilos.marca} resizeMode="contain" />;
}

const capa = Capa();
const marca = Marca();

console.log('caminhos pedidos no build:', requisicoes);
console.log('tipo do componente:', capa.type, marca.type);
console.log('source remoto:', capa.props.source);
console.log('source local:', marca.props.source);
console.log('resizeMode da capa:', capa.props.resizeMode);
console.log('resizeMode da marca:', marca.props.resizeMode);

// A caixa e a foto sao coisas separadas: medida e retangulo, nao a foto.
function retangulo(estilo) {
  return { largura: estilo.width, altura: estilo.height };
}

console.log('a caixa da capa e:', retangulo(StyleSheet.flatten(capa.props.style)));
console.log('a caixa da marca e:', retangulo(StyleSheet.flatten(marca.props.style)));
console.log('`cover` preenche a caixa e corta a foto');
console.log('`contain` mostra a foto inteira e sobra espaco');
console.log('`stretch` distorce ate caber: e o unico que muda a proporcao');

// O padrao de `resizeMode` e `cover`; declarado aqui so para o exemplo.
console.log('sem resizeMode declarado:', Image({ source: foto, style: {} }).props.resizeMode);

Saída real

caminhos pedidos no build: [ './assets/logo.png' ]
tipo do componente: Image Image
source remoto: { uri: 'https://exemplo.test/capa/41.jpg' }
source local: { recursoDoApp: './assets/logo.png' }
resizeMode da capa: cover
resizeMode da marca: contain
a caixa da capa e: { largura: '100%', altura: 180 }
a caixa da marca e: { largura: 120, altura: 40 }
`cover` preenche a caixa e corta a foto
`contain` mostra a foto inteira e sobra espaco
`stretch` distorce ate caber: e o unico que muda a proporcao
sem resizeMode declarado: undefined
Aula 2

Pressable, TouchableOpacity e onPress

Pressable, TouchableOpacity e onPress

Pressable é o botão do React Native atual e o que resolve o problema que os

componentes antigos tinham: ele não embrulha o conteúdo em outro

componente, o que significa que onPress, onLongPress, onPressIn e

onPressOut chegam direto ao elemento que você declarou. O conteúdo é filho

dele, e pode ser um Text, um Image ou um View inteiro.

TouchableOpacity e TouchableHighlight são a geração anterior e continuam

existindo. A diferença é visual e automática: o TouchableOpacity reduz a

opacidade do filho ao toque, e o TouchableHighlight acende o fundo. O

Pressable não faz nada sozinho — o feedback depende do style, que pode ser

uma função que recebe { pressed } e devolve o estilo conforme o estado:

<Pressable
  style={({ pressed }) => [
    estilos.botao,
    pressed && estilos.apertado,
  ]}
  onPress={salvar}
>
  <Text style={estilos.rotulo}>Salvar</Text>
</Pressable>

O exemplo desta página mostra por que o Pressable não embrulha: ele imprime o

tipo do filho direto e a resposta é Text. No TouchableOpacity a mesma

informação viria de um nível abaixo, dentro de um componente intermediário que o

aluno não declarou. É a razão de Pressable ter substituído os outros: menos

uma camada entre o que se escreve e o que se vê.

O estilo como função é o segundoitem do exemplo. Chamar a função com pressed

igual a false devolve o botão em estado normal, com backgroundColor,

paddingVertical, paddingHorizontal e borderRadius. Chamar com pressed

igual a true devolve exatamente o mesmo objeto mais opacity: 0.7 — e é essa

a forma do feedback: um estilo que se comporta como função do estado do toque, e

não um efeito visual que o componente aplica sozinho.

os quatro eventos de toque

onPress dispara quando o toque termina dentro do elemento. onPressIn dispara

na entrada, onPressOut na saída, e onLongPress quando o dedo fica parado por

mais tempo que o sistema considera pressionado. A combinação útil é

onPressIn mudando o estado para mostrar o botão apertado e onPressOut

voltando: é a forma de ter um botão com a cor certa no dedo e uma cor de

desabilitado quando não há nada a fazer.

A diferença entre os eventos aparece no exemplo de um jeito seco: onPress está

declarado e responde function, e onLongPress responde undefined porque não

foi declarado. Não existe evento "ligado por padrão" esperando ser usado — o que

não está no objeto não dispara. Disparar onPress duas vezes no objeto

registrou 2 toques, que é a prova de que a propriedade guardada é a mesma

função que o dedo chamaria.

A ordem importa nos que dependem do estado: onPressIn antes de onPress na

entrada, onPressOut depois de onPress na saída. Usar só onPress e deixar o

feedback para o style é a forma mais simples e cobre quase tudo; usar os quatro

é para o botão que precisa reagir ao dedo antes de soltar — botão que

cresce, que mostra uma prévia, ou que desmarca ao arrastar para fora.

onPress é assíncrono quando a função é async, e o Pressable não espera:

uma requisição disparada em onPress que demora três segundos produz uma tela

que parece travada. O botão precisa de um estado de carregamento próprio, e é

por isso que a aula de API traz a parte de desabilitar e mostrar o

carregando.

disabled e o que ele não faz

A propriedade se chama disabled, com um d: disable é a que erra todo

mundo. Ela desabilita o toque e a semitransparência do TouchableOpacity,

mas em Pressable só desabilita o toque — a aparência continua a mesma. Por

isso o padrão é combinar a propriedade com o estilo:

<Pressable disabled={carregando} style={({ pressed }) => [estilos.botao, pressed && estilos.apertado, carregando && estilos.desabilitado]}>

O exemplo separa o que a propriedade trava do que ela não trava, e a segunda

linha é a que surpreende o aluno: com carregando verdadeiro, disabled

responde true e o onPress continua sendo function. O evento existe no

objeto; o que o componente faz é não chamá-lo. O aviso é sobre aparência e não

sobre funcionamento — desabilitar o toque não deixa o botão cinza.

A aparência vem do terceiro item da função de estilo, e o exemplo resolve isso

no console: o estado de carregamento sai com backgroundColor trocado para

'#a8b3bf', com todo o resto igual. É o estado que o componente não produziu —

foi a função de estilo que devolveu, porque carregando entrou no array.

O detalhe do flexDirection volta aqui e tem nome: um Pressable com mais de

um filho precisa de flexDirection: 'row' no próprio estilo. O exemplo imprime

row para o Pressable que recebeu o estilo de linha — e a razão é a mesma do

View: o Pressable se comporta como View, e column é o padrão dele.

O que disabled não faz é esconder o elemento, impedir que ele receba o foco

do teclado ou impedir a animação — só o toque. E a regra de acessibilidade vale

aqui: um botão que só tem ícone precisa de accessibilityLabel, porque o leitor

de tela anuncia o rótulo, e sem rótulo ele anuncia o nome do componente. O

exemplo declara os dois no mesmo Pressable — accessibilityRole como

'button' e accessibilityLabel como 'salvar pedido' — e imprime os dois

valores ao lado do tipo Pressable.

Exemplo

// `Pressable` recebe `onPress` direto e o estilo pode ser uma funcao que
// recebe `{ pressed }`. O exemplo chama o evento, le o estilo nos dois
// estados e mostra o que `disabled` trava.
const { View, Text, Pressable, TouchableOpacity, StyleSheet } = require('react-native');

const estilos = StyleSheet.create({
  botao: { backgroundColor: '#1f6feb', paddingVertical: 12, paddingHorizontal: 20, borderRadius: 8 },
  apertado: { opacity: 0.7 },
  desabilitado: { backgroundColor: '#a8b3bf' },
  linha: { flexDirection: 'row', alignItems: 'center' },
});

let toques = 0;
function salvar() {
  toques += 1;
  return 'salvou';
}

function Botao(props) {
  return (
    <Pressable
      style={({ pressed }) => [
        estilos.botao,
        pressed && estilos.apertado,
        props.carregando && estilos.desabilitado,
      ]}
      onPress={salvar}
      disabled={props.carregando}
      accessibilityRole="button"
      accessibilityLabel="salvar pedido"
    >
      <Text>{props.rotulo}</Text>
    </Pressable>
  );
}

const arvore = Botao({ rotulo: 'Salvar', carregando: false });
console.log('tipo:', arvore.type);
console.log('papel declarado:', arvore.props.accessibilityRole);
console.log('rotulo para o leitor de tela:', arvore.props.accessibilityLabel);
console.log('tipo de onPress:', typeof arvore.props.onPress);
console.log('tipo de onLongPress:', typeof arvore.props.onLongPress);

// O estilo e uma funcao: o exemplo chama com `pressed` nos dois estados.
const funcaoDeEstilo = arvore.props.style;
console.log('estado normal:', StyleSheet.flatten(funcaoDeEstilo({ pressed: false })));
console.log('estado apertado:', StyleSheet.flatten(funcaoDeEstilo({ pressed: true })));
console.log('estado carregando:', StyleSheet.flatten(
  funcaoDeEstilo({ pressed: false }).map((e) => e).concat([estilos.desabilitado])
));

// Disparar o evento como o runtime faria.
arvore.props.onPress();
arvore.props.onPress();
console.log('toques registrados:', toques);
console.log('o retorno de onPress:', salvar());

// Com mais de um filho, o `Pressable` precisa da linha declarada.
const comLinha = Pressable({ style: estilos.linha, children: 'um' });
console.log('Pressable com um filho:', StyleSheet.flatten(comLinha.props.style).flexDirection);

// `disabled` trava o toque; a aparencia e do estilo.
const travado = Botao({ rotulo: 'Salvar', carregando: true });
console.log('disabled quando carregando:', travado.props.disabled);
console.log('o evento ainda existe no objeto:', typeof travado.props.onPress === 'function');

// `TouchableOpacity` embrulha o filho e cuida da opacidade sozinho.
const antigo = TouchableOpacity({ onPress: salvar, activeOpacity: 0.6, children: 'ok' });
console.log('TouchableOpacity:', antigo.type, '| activeOpacity:', antigo.props.activeOpacity);
console.log('Pressable nao embrulha:', arvore.props.children.type);

Saída real

tipo: Pressable
papel declarado: button
rotulo para o leitor de tela: salvar pedido
tipo de onPress: function
tipo de onLongPress: undefined
estado normal: {
  backgroundColor: '#1f6feb',
  paddingVertical: 12,
  paddingHorizontal: 20,
  borderRadius: 8
}
estado apertado: {
  backgroundColor: '#1f6feb',
  paddingVertical: 12,
  paddingHorizontal: 20,
  borderRadius: 8,
  opacity: 0.7
}
estado carregando: {
  backgroundColor: '#a8b3bf',
  paddingVertical: 12,
  paddingHorizontal: 20,
  borderRadius: 8
}
toques registrados: 2
o retorno de onPress: salvou
Pressable com um filho: row
disabled quando carregando: true
o evento ainda existe no objeto: true
TouchableOpacity: TouchableOpacity | activeOpacity: 0.6
Pressable nao embrulha: Text