Saltar al contenido principal
Versión: 0.79

Componentes de interfaz de usuario nativos de Android

Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

Traducción Beta No Oficial

Esta página fue traducida por PageTurner AI (beta). No está respaldada oficialmente por el proyecto. ¿Encontraste un error? Reportar problema →

información

Native Module y Native Components son nuestras tecnologías estables utilizadas por la arquitectura heredada. Serán desaprobadas en el futuro cuando la Nueva Arquitectura sea estable. La Nueva Arquitectura utiliza Turbo Native Module y Fabric Native Components para lograr resultados similares.

Existen una gran cantidad de componentes de interfaz de usuario nativos listos para usarse en las aplicaciones más recientes: algunos forman parte de la plataforma, otros están disponibles como bibliotecas de terceros, y otros más podrían estar en uso en tu propio portafolio. React Native ya incluye varios de los componentes más críticos de la plataforma, como ScrollView y TextInput, pero no todos, y ciertamente no aquellos que podrías haber creado para una aplicación anterior. Afortunadamente, podemos encapsular estos componentes existentes para integrarlos perfectamente con tu aplicación de React Native.

Al igual que la guía de módulos nativos, este también es un documento avanzado que asume cierto conocimiento de programación con el SDK de Android. Esta guía te mostrará cómo crear un componente de UI nativo, guiándote a través de la implementación de un subconjunto del componente ImageView disponible en la biblioteca principal de React Native.

información

También puedes configurar una biblioteca local que contenga componentes nativos con un solo comando. Consulta la guía de Configuración de bibliotecas locales para más detalles.

Ejemplo de ImageView

Para este ejemplo, repasaremos los requisitos de implementación para permitir el uso de ImageViews en JavaScript.

Las vistas nativas se crean y manipulan extendiendo ViewManager o más comúnmente SimpleViewManager. Un SimpleViewManager es conveniente en este caso porque aplica propiedades comunes como color de fondo, opacidad y diseño Flexbox.

Estas subclases son esencialmente singletons: solo se crea una instancia de cada una mediante el bridge. Envían vistas nativas al NativeViewHierarchyManager, que les delega establecer y actualizar las propiedades de las vistas según sea necesario. Los ViewManagers también suelen actuar como delegados para las vistas, enviando eventos de vuelta a JavaScript a través del bridge.

Para enviar una vista:

  1. Crea la subclase de ViewManager.

  2. Implementa el método createViewInstance

  3. Expón los setters de propiedades de la vista usando la anotación @ReactProp (o @ReactPropGroup)

  4. Registra el administrador en createViewManagers del paquete de la aplicación.

  5. Implementa el módulo JavaScript

1. Crea la subclase ViewManager

En este ejemplo creamos la clase administradora de vistas ReactImageManager que extiende SimpleViewManager del tipo ReactImageView. ReactImageView es el tipo de objeto gestionado por el administrador, que será la vista nativa personalizada. El nombre devuelto por getName se usa para referenciar el tipo de vista nativa desde JavaScript.

java
public class ReactImageManager extends SimpleViewManager<ReactImageView> {

public static final String REACT_CLASS = "RCTImageView";
ReactApplicationContext mCallerContext;

public ReactImageManager(ReactApplicationContext reactContext) {
mCallerContext = reactContext;
}

@Override
public String getName() {
return REACT_CLASS;
}
}

2. Implementa el método createViewInstance

Las vistas se crean en el método createViewInstance. La vista debe inicializarse en su estado predeterminado; cualquier propiedad se establecerá mediante una llamada posterior a updateView.

java
  @Override
public ReactImageView createViewInstance(ThemedReactContext context) {
return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), null, mCallerContext);
}

3. Expón setters de propiedades de la vista usando la anotación @ReactProp (o @ReactPropGroup)

Las propiedades que deben reflejarse en JavaScript deben exponerse como métodos setter anotados con @ReactProp (o @ReactPropGroup). El método setter debe tomar como primer argumento la vista a actualizar (del tipo de vista actual) y como segundo argumento el valor de la propiedad. El setter debe ser público y no devolver ningún valor (es decir, el tipo de retorno debe ser void en Java o Unit en Kotlin). El tipo de propiedad enviado a JS se determina automáticamente según el tipo del argumento de valor del setter. Actualmente se admiten los siguientes tipos de valores (en Java): boolean, int, float, double, String, Boolean, Integer, ReadableArray, ReadableMap. Los tipos correspondientes en Kotlin son Boolean, Int, Float, Double, String, ReadableArray, ReadableMap.

La anotación @ReactProp tiene un argumento obligatorio name de tipo String. El nombre asignado a la anotación @ReactProp vinculada al método setter se utiliza para referenciar la propiedad en el lado de JS.

Además de name, la anotación @ReactProp puede tomar los siguientes argumentos opcionales: defaultBoolean, defaultInt, defaultFloat. Estos argumentos deben ser del tipo correspondiente (boolean, int, float en Java y Boolean, Int, Float en Kotlin). El valor proporcionado se pasará al método setter cuando la propiedad referenciada haya sido eliminada del componente. Nota: los valores "default" solo se proporcionan para tipos primitivos. Para setters de tipos complejos, se pasará null como valor predeterminado si la propiedad correspondiente se elimina.

Los requisitos de declaración para métodos anotados con @ReactPropGroup difieren de @ReactProp. Consulta la documentación de la clase @ReactPropGroup para más detalles. IMPORTANTE: En ReactJS, actualizar el valor de una propiedad ejecutará su método setter. Ten en cuenta que al eliminar propiedades previamente establecidas también se invocará el setter para notificar al administrador de vistas sobre el cambio. En este caso, se proporcionará el valor "default" (para tipos primitivos, este valor puede especificarse mediante los argumentos defaultBoolean, defaultFloat, etc. de la anotación @ReactProp; para tipos complejos el setter recibirá null).

java
  @ReactProp(name = "src")
public void setSrc(ReactImageView view, @Nullable ReadableArray sources) {
view.setSource(sources);
}

@ReactProp(name = "borderRadius", defaultFloat = 0f)
public void setBorderRadius(ReactImageView view, float borderRadius) {
view.setBorderRadius(borderRadius);
}

@ReactProp(name = ViewProps.RESIZE_MODE)
public void setResizeMode(ReactImageView view, @Nullable String resizeMode) {
view.setScaleType(ImageResizeMode.toScaleType(resizeMode));
}

4. Registrar el ViewManager

El paso final es registrar el ViewManager en la aplicación, similar a cómo se registran los Módulos Nativos, mediante la función createViewManagers del paquete de la aplicación.

java
  @Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new ReactImageManager(reactContext)
);
}

5. Implementar el módulo JavaScript

El último paso es crear el módulo JavaScript que define la capa de interfaz entre Java/Kotlin y JavaScript para los usuarios de tu nueva vista. Se recomienda documentar la interfaz del componente en este módulo (usando TypeScript, Flow o comentarios simples).

ImageView.tsx
import {requireNativeComponent} from 'react-native';

/**
* Composes `View`.
*
* - src: Array<{url: string}>
* - borderRadius: number
* - resizeMode: 'cover' | 'contain' | 'stretch'
*/
export default requireNativeComponent('RCTImageView');

La función requireNativeComponent toma el nombre de la vista nativa. Nota: si necesitas funcionalidades avanzadas (como manejo personalizado de eventos), envuelve el componente nativo en otro componente React. Esto se ilustra en el ejemplo MyCustomView a continuación.

Eventos

Ahora sabemos cómo exponer componentes nativos controlables desde JS, pero ¿cómo manejamos eventos del usuario como zoom o desplazamiento? Cuando ocurre un evento nativo, el código debe emitirlo a la representación JavaScript de la Vista. Ambos componentes se vinculan mediante el valor devuelto por getId().

java
class MyCustomView extends View {
...
public void onReceiveNativeEvent() {
WritableMap event = Arguments.createMap();
event.putString("message", "MyMessage");
ReactContext reactContext = (ReactContext)getContext();
reactContext
.getJSModule(RCTEventEmitter.class)
.receiveEvent(getId(), "topChange", event);
}
}

Para mapear el nombre de evento topChange al prop onChange en JavaScript, regístralo sobrescribiendo getExportedCustomBubblingEventTypeConstants en tu ViewManager:

java
public class ReactImageManager extends SimpleViewManager<MyCustomView> {
...
public Map getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.builder().put(
"topChange",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onChange")
)
).build();
}
}

Esta callback recibe el evento en bruto, que normalmente procesamos en el componente wrapper para simplificar la API:

MyCustomView.tsx
import {useCallback} from 'react';
import {requireNativeComponent} from 'react-native';

const RCTMyCustomView = requireNativeComponent('RCTMyCustomView');

export default function MyCustomView(props: {
// ...
/**
* Callback that is called continuously when the user is dragging the map.
*/
onChangeMessage: (message: string) => unknown;
}) {
const onChange = useCallback(
event => {
props.onChangeMessage?.(event.nativeEvent.message);
},
[props.onChangeMessage],
);

return <RCTMyCustomView {...props} onChange={onChange} />;
}

Ejemplo de integración con un Fragmento de Android

Para integrar elementos nativos existentes en tu app de React Native, quizá necesites usar Fragmentos de Android. Esto ofrece mayor control granular que devolver una View desde tu ViewManager, especialmente si requieres lógica personalizada vinculada a métodos de ciclo de vida como onViewCreated, onPause o onResume. Sigue estos pasos:

1. Crear una vista personalizada de ejemplo

Primero, crea una clase CustomView que extienda FrameLayout (su contenido puede ser cualquier vista que desees renderizar).

CustomView.java
// replace with your package
package com.mypackage;

import android.content.Context;
import android.graphics.Color;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;

public class CustomView extends FrameLayout {
public CustomView(@NonNull Context context) {
super(context);
// set padding and background color
this.setPadding(16,16,16,16);
this.setBackgroundColor(Color.parseColor("#5FD3F3"));

// add default text view
TextView text = new TextView(context);
text.setText("Welcome to Android Fragments with React Native.");
this.addView(text);
}
}

2. Crear un Fragment

MyFragment.java
// replace with your package
package com.mypackage;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;

// replace with your view's import
import com.mypackage.CustomView;

public class MyFragment extends Fragment {
CustomView customView;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
super.onCreateView(inflater, parent, savedInstanceState);
customView = new CustomView(this.getContext());
return customView; // this CustomView could be any view that you want to render
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// do any logic that should happen in an `onCreate` method, e.g:
// customView.onCreate(savedInstanceState);
}

@Override
public void onPause() {
super.onPause();
// do any logic that should happen in an `onPause` method
// e.g.: customView.onPause();
}

@Override
public void onResume() {
super.onResume();
// do any logic that should happen in an `onResume` method
// e.g.: customView.onResume();
}

@Override
public void onDestroy() {
super.onDestroy();
// do any logic that should happen in an `onDestroy` method
// e.g.: customView.onDestroy();
}
}

3. Crear la subclase de ViewManager

MyViewManager.java
// replace with your package
package com.mypackage;

import android.view.Choreographer;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.FragmentActivity;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.ThemedReactContext;

import java.util.Map;

public class MyViewManager extends ViewGroupManager<FrameLayout> {

public static final String REACT_CLASS = "MyViewManager";
public final int COMMAND_CREATE = 1;
private int propWidth;
private int propHeight;

ReactApplicationContext reactContext;

public MyViewManager(ReactApplicationContext reactContext) {
this.reactContext = reactContext;
}

@Override
public String getName() {
return REACT_CLASS;
}

/**
* Return a FrameLayout which will later hold the Fragment
*/
@Override
public FrameLayout createViewInstance(ThemedReactContext reactContext) {
return new FrameLayout(reactContext);
}

/**
* Map the "create" command to an integer
*/
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of("create", COMMAND_CREATE);
}

/**
* Handle "create" command (called from JS) and call createFragment method
*/
@Override
public void receiveCommand(
@NonNull FrameLayout root,
String commandId,
@Nullable ReadableArray args
) {
super.receiveCommand(root, commandId, args);
int reactNativeViewId = args.getInt(0);
int commandIdInt = Integer.parseInt(commandId);

switch (commandIdInt) {
case COMMAND_CREATE:
createFragment(root, reactNativeViewId);
break;
default: {}
}
}

@ReactPropGroup(names = {"width", "height"}, customType = "Style")
public void setStyle(FrameLayout view, int index, Integer value) {
if (index == 0) {
propWidth = value;
}

if (index == 1) {
propHeight = value;
}
}

/**
* Replace your React Native view with a custom fragment
*/
public void createFragment(FrameLayout root, int reactNativeViewId) {
ViewGroup parentView = (ViewGroup) root.findViewById(reactNativeViewId);
setupLayout(parentView);

final MyFragment myFragment = new MyFragment();
FragmentActivity activity = (FragmentActivity) reactContext.getCurrentActivity();
activity.getSupportFragmentManager()
.beginTransaction()
.replace(reactNativeViewId, myFragment, String.valueOf(reactNativeViewId))
.commit();
}

public void setupLayout(View view) {
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
manuallyLayoutChildren(view);
view.getViewTreeObserver().dispatchOnGlobalLayout();
Choreographer.getInstance().postFrameCallback(this);
}
});
}

/**
* Layout all children properly
*/
public void manuallyLayoutChildren(View view) {
// propWidth and propHeight coming from react-native props
int width = propWidth;
int height = propHeight;

view.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY));

view.layout(0, 0, width, height);
}
}

4. Registrar el ViewManager

MyPackage.java
// replace with your package
package com.mypackage;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.Arrays;
import java.util.List;

public class MyPackage implements ReactPackage {

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new MyViewManager(reactContext)
);
}

}

5. Registrar el Package

MainApplication.java
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new MyAppPackage());
return packages;
}

6. Implementar el módulo JavaScript

I. Comienza con el administrador de vistas personalizado:

MyViewManager.tsx
import {requireNativeComponent} from 'react-native';

export const MyViewManager =
requireNativeComponent('MyViewManager');

II. Luego implementa la Vista personalizada llamando al método create:

MyView.tsx
import React, {useEffect, useRef} from 'react';
import {
PixelRatio,
UIManager,
findNodeHandle,
} from 'react-native';

import {MyViewManager} from './my-view-manager';

const createFragment = viewId =>
UIManager.dispatchViewManagerCommand(
viewId,
// we are calling the 'create' command
UIManager.MyViewManager.Commands.create.toString(),
[viewId],
);

export const MyView = () => {
const ref = useRef(null);

useEffect(() => {
const viewId = findNodeHandle(ref.current);
createFragment(viewId);
}, []);

return (
<MyViewManager
style={{
// converts dpi to px, provide desired height
height: PixelRatio.getPixelSizeForLayoutSize(200),
// converts dpi to px, provide desired width
width: PixelRatio.getPixelSizeForLayoutSize(200),
}}
ref={ref}
/>
);
};

Si deseas exponer establecedores de propiedades usando la anotación @ReactProp (o @ReactPropGroup), consulta el ejemplo de ImageView anterior.