Natywne komponenty interfejsu użytkownika w systemie Android
Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →
Ta strona została przetłumaczona przez PageTurner AI (beta). Nie jest oficjalnie zatwierdzona przez projekt. Znalazłeś błąd? Zgłoś problem →
Native Module i Native Components to nasze stabilne technologie używane w starszej architekturze. Zostaną one wycofane w przyszłości, gdy Nowa Architektura stanie się stabilna. Nowa Architektura wykorzystuje Turbo Native Module i Fabric Native Components, aby osiągnąć podobne rezultaty.
Istnieje mnóstwo natywnych widżetów UI gotowych do użycia w nowoczesnych aplikacjach - część z nich jest elementem platformy, inne dostępne są jako biblioteki stron trzecich, a jeszcze inne mogą być używane w twoim własnym portfolio. React Native zawiera opakowane wersje kilku kluczowych komponentów platformy, takich jak ScrollView czy TextInput, ale nie wszystkich i z pewnością nie tych, które mogłeś stworzyć samodzielnie do poprzedniej aplikacji. Na szczęście możemy opakować te istniejące komponenty dla płynnej integracji z twoją aplikacją React Native.
Podobnie jak przewodnik po modułach natywnych, jest to bardziej zaawansowany przewodnik zakładający, że masz pewną znajomość programowania w Android SDK. Ten przewodnik pokaże ci, jak zbudować natywny komponent UI, przeprowadzając cię przez implementację podzbioru istniejącego komponentu ImageView dostępnego w głównej bibliotece React Native.
Możesz również skonfigurować lokalną bibliotekę zawierającą natywny komponent za pomocą jednego polecenia. Więcej szczegółów znajdziesz w przewodniku Konfiguracja bibliotek lokalnych.
Przykład ImageView
W tym przykładzie prześledzimy wymagania implementacyjne umożliwiające używanie komponentów ImageView w JavaScript.
Natywne widoki są tworzone i zarządzane przez rozszerzenie ViewManager lub częściej SimpleViewManager. SimpleViewManager jest w tym przypadku wygodny, ponieważ stosuje wspólne właściwości jak kolor tła, przezroczystość i układ Flexbox.
Te podklasy są w zasadzie singletonami - mostek tworzy tylko jedną instancję każdej z nich. Wysyłają natywne widoki do NativeViewHierarchyManager, który deleguje do nich ustawianie i aktualizację właściwości widoków w razie potrzeby. ViewManagers są również typowo delegatami dla widoków, wysyłając zdarzenia z powrotem do JavaScript poprzez mostek.
Aby wysłać widok:
-
Utwórz podklasę ViewManager.
-
Zaimplementuj metodę
createViewInstance -
Udostępnij settery właściwości widoku przy użyciu adnotacji
@ReactProp(lub@ReactPropGroup) -
Zarejestruj menedżer w
createViewManagerspakietu aplikacji -
Zaimplementuj moduł JavaScript
1. Utwórz podklasę ViewManager
W tym przykładzie tworzymy klasę menedżera widoków ReactImageManager, która rozszerza SimpleViewManager typu ReactImageView. ReactImageView to typ obiektu zarządzanego przez menedżer, który będzie niestandardowym natywnym widokiem. Nazwa zwracana przez getName służy do odwoływania się do typu natywnego widoku z poziomu JavaScript.
- Java
- Kotlin
class ReactImageManager(
private val callerContext: ReactApplicationContext
) : SimpleViewManager<ReactImageView>() {
override fun getName() = REACT_CLASS
companion object {
const val REACT_CLASS = "RCTImageView"
}
}
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. Zaimplementuj metodę createViewInstance
Widoki są tworzone w metodzie createViewInstance. Widok powinien zainicjować się w stanie domyślnym, a wszelkie właściwości zostaną ustawione przez kolejne wywołanie updateView.
- Java
- Kotlin
override fun createViewInstance(context: ThemedReactContext) =
ReactImageView(context, Fresco.newDraweeControllerBuilder(), null, callerContext)
@Override
public ReactImageView createViewInstance(ThemedReactContext context) {
return new ReactImageView(context, Fresco.newDraweeControllerBuilder(), null, mCallerContext);
}
3. Udostępnij settery właściwości widoku przy użyciu adnotacji @ReactProp (lub @ReactPropGroup)
Właściwości, które mają być odzwierciedlone w JavaScript, muszą być udostępnione jako metody setter opatrzone adnotacją @ReactProp (lub @ReactPropGroup). Metoda setter powinna przyjmować jako pierwszy argument widok do aktualizacji (bieżącego typu widoku), a jako drugi argument wartość właściwości. Setter powinien być publiczny i nie zwracać wartości (tj. typ zwracany powinien być void w Javie lub Unit w Kotlinie). Typ właściwości wysyłany do JS jest określany automatycznie na podstawie typu argumentu wartości settera. Obecnie obsługiwane są następujące typy wartości (w Javie): boolean, int, float, double, String, Boolean, Integer, ReadableArray, ReadableMap. Odpowiednie typy w Kotlinie to: Boolean, Int, Float, Double, String, ReadableArray, ReadableMap.
Adnotacja @ReactProp ma jeden obowiązkowy argument name typu String. Nazwa przypisana do adnotacji @ReactProp powiązanej z metodą setter jest używana do odwoływania się do właściwości po stronie JS.
Oprócz name, adnotacja @ReactProp może przyjmować następujące opcjonalne argumenty: defaultBoolean, defaultInt, defaultFloat. Argumenty te powinny być odpowiedniego typu (odpowiednio boolean, int, float w Javie oraz Boolean, Int, Float w Kotlinie), a podana wartość zostanie przekazana do metody settera w przypadku usunięcia właściwości, do której odnosi się setter. Wartości domyślne są dostępne tylko dla typów prostych – dla setterów obsługujących typy złożone, w przypadku usunięcia właściwości zostanie przekazana wartość null.
Wymagania dotyczące deklaracji setterów dla metod oznaczonych @ReactPropGroup różnią się od tych dla @ReactProp – szczegółowe informacje znajdują się w dokumentacji klasy adnotacji @ReactPropGroup. WAŻNE! W ReactJS aktualizacja wartości właściwości wywołuje metodę settera. Jedną z metod aktualizacji komponentu jest usuwanie wcześniej ustawionych właściwości. W takim przypadku setter również zostanie wywołany, aby powiadomić menedżera widoków o zmianie. Dla typów prostych zostanie przekazana wartość domyślna (określona przez argumenty defaultBoolean, defaultFloat itp. w adnotacji @ReactProp), natomiast dla typów złożonych setter otrzyma wartość null.
- Java
- Kotlin
@ReactProp(name = "src")
fun setSrc(view: ReactImageView, sources: ReadableArray?) {
view.setSource(sources)
}
@ReactProp(name = "borderRadius", defaultFloat = 0f)
override fun setBorderRadius(view: ReactImageView, borderRadius: Float) {
view.setBorderRadius(borderRadius)
}
@ReactProp(name = ViewProps.RESIZE_MODE)
fun setResizeMode(view: ReactImageView, resizeMode: String?) {
view.setScaleType(ImageResizeMode.toScaleType(resizeMode))
}
@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. Rejestracja ViewManager
Ostatnim krokiem jest zarejestrowanie ViewManager w aplikacji, analogicznie do modułów natywnych, poprzez funkcję createViewManagers w pakiecie aplikacji.
- Java
- Kotlin
override fun createViewManagers(
reactContext: ReactApplicationContext
) = listOf(ReactImageManager(reactContext))
@Override
public List<ViewManager> createViewManagers(
ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(
new ReactImageManager(reactContext)
);
}
5. Implementacja modułu JavaScript
Końcowym etapem jest utworzenie modułu JavaScript definiującego warstwę interfejsu między Javą/Kotlin a JavaScript dla użytkowników nowego widoku. Zaleca się udokumentowanie interfejsu komponentu w tym module (np. przy użyciu TypeScript, Flow lub standardowych komentarzy).
import {requireNativeComponent} from 'react-native';
/**
* Composes `View`.
*
* - src: Array<{url: string}>
* - borderRadius: number
* - resizeMode: 'cover' | 'contain' | 'stretch'
*/
export default requireNativeComponent('RCTImageView');
Funkcja requireNativeComponent przyjmuje nazwę widoku natywnego. Jeśli komponent wymaga zaawansowanej funkcjonalności (np. niestandardowej obsługi zdarzeń), należy opakować go w dodatkowy komponent Reacta, jak pokazano w przykładzie MyCustomView poniżej.
Zdarzenia
Wiemy już, jak udostępniać natywne komponenty widoków sterowane z poziomu JS, ale jak obsługiwać zdarzenia użytkownika takie jak powiększanie czy przesuwanie? Gdy wystąpi zdarzenie natywne, kod powinien wyemitować je do reprezentacji JavaScript widoku, powiązanej poprzez wartość zwracaną przez getId().
- Java
- Kotlin
class MyCustomView(context: Context) : View(context) {
...
fun onReceiveNativeEvent() {
val event = Arguments.createMap().apply {
putString("message", "MyMessage")
}
val reactContext = context as ReactContext
reactContext
.getJSModule(RCTEventEmitter::class.java)
.receiveEvent(id, "topChange", event)
}
}
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);
}
}
Aby zmapować zdarzenie topChange na właściwość callback onChange w JavaScripcie, zarejestruj je poprzez nadpisanie metody getExportedCustomBubblingEventTypeConstants w ViewManager:
- Java
- Kotlin
class ReactImageManager : SimpleViewManager<MyCustomView>() {
...
override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
return mapOf(
"topChange" to mapOf(
"phasedRegistrationNames" to mapOf(
"bubbled" to "onChange"
)
)
)
}
}
public class ReactImageManager extends SimpleViewManager<MyCustomView> {
...
public Map getExportedCustomBubblingEventTypeConstants() {
return MapBuilder.builder().put(
"topChange",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onChange")
)
).build();
}
}
Callback otrzymuje surowe zdarzenie, które zwykle przetwarzamy w komponencie opakowującym dla uproszczenia API:
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} />;
}
Przykład integracji z fragmentem Androida
Aby zintegrować istniejące natywne elementy UI z aplikacją React Native, czasami potrzebna jest większa kontrola niż zwykłe zwracanie View z ViewManager. Fragmenty Androida umożliwiają wykorzystanie metod cyklu życia takich jak onViewCreated, onPause czy onResume. Poniższe kroki demonstrują to podejście:
1. Tworzenie przykładowego widoku niestandardowego
Najpierw utwórz klasę CustomView dziedziczącą po FrameLayout (zawartość może być dowolnym widokiem do renderowania).
- Java
- Kotlin
// replace with your package
package com.mypackage
import android.content.Context
import android.graphics.Color
import android.widget.FrameLayout
import android.widget.TextView
class CustomView(context: Context) : FrameLayout(context) {
init {
// set padding and background color
setPadding(16,16,16,16)
setBackgroundColor(Color.parseColor("#5FD3F3"))
// add default text view
addView(TextView(context).apply {
text = "Welcome to Android Fragments with React Native."
})
}
}
// 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. Tworzenie Fragment
- Java
- Kotlin
// 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
class MyFragment : Fragment() {
private lateinit var customView: CustomView
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
customView = CustomView(requireNotNull(context))
return customView // this CustomView could be any view that you want to render
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// do any logic that should happen in an `onCreate` method, e.g:
// customView.onCreate(savedInstanceState);
}
override fun onPause() {
super.onPause()
// do any logic that should happen in an `onPause` method
// e.g.: customView.onPause();
}
override fun onResume() {
super.onResume()
// do any logic that should happen in an `onResume` method
// e.g.: customView.onResume();
}
override fun onDestroy() {
super.onDestroy()
// do any logic that should happen in an `onDestroy` method
// e.g.: customView.onDestroy();
}
}
// 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. Tworzenie podklasy ViewManager
- Java
- Kotlin
// 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.fragment.app.FragmentActivity
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewGroupManager
import com.facebook.react.uimanager.annotations.ReactPropGroup
class MyViewManager(
private val reactContext: ReactApplicationContext
) : ViewGroupManager<FrameLayout>() {
private var propWidth: Int? = null
private var propHeight: Int? = null
override fun getName() = REACT_CLASS
/**
* Return a FrameLayout which will later hold the Fragment
*/
override fun createViewInstance(reactContext: ThemedReactContext) =
FrameLayout(reactContext)
/**
* Map the "create" command to an integer
*/
override fun getCommandsMap() = mapOf("create" to COMMAND_CREATE)
/**
* Handle "create" command (called from JS) and call createFragment method
*/
override fun receiveCommand(
root: FrameLayout,
commandId: String,
args: ReadableArray?
) {
super.receiveCommand(root, commandId, args)
val reactNativeViewId = requireNotNull(args).getInt(0)
when (commandId.toInt()) {
COMMAND_CREATE -> createFragment(root, reactNativeViewId)
}
}
@ReactPropGroup(names = ["width", "height"], customType = "Style")
fun setStyle(view: FrameLayout, index: Int, value: Int) {
if (index == 0) propWidth = value
if (index == 1) propHeight = value
}
/**
* Replace your React Native view with a custom fragment
*/
fun createFragment(root: FrameLayout, reactNativeViewId: Int) {
val parentView = root.findViewById<ViewGroup>(reactNativeViewId)
setupLayout(parentView)
val myFragment = MyFragment()
val activity = reactContext.currentActivity as FragmentActivity
activity.supportFragmentManager
.beginTransaction()
.replace(reactNativeViewId, myFragment, reactNativeViewId.toString())
.commit()
}
fun setupLayout(view: View) {
Choreographer.getInstance().postFrameCallback(object: Choreographer.FrameCallback {
override fun doFrame(frameTimeNanos: Long) {
manuallyLayoutChildren(view)
view.viewTreeObserver.dispatchOnGlobalLayout()
Choreographer.getInstance().postFrameCallback(this)
}
})
}
/**
* Layout all children properly
*/
private fun manuallyLayoutChildren(view: View) {
// propWidth and propHeight coming from react-native props
val width = requireNotNull(propWidth)
val height = requireNotNull(propHeight)
view.measure(
View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY))
view.layout(0, 0, width, height)
}
companion object {
private const val REACT_CLASS = "MyViewManager"
private const val COMMAND_CREATE = 1
}
}
// 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. Rejestracja ViewManager
- Java
- Kotlin
// replace with your package
package com.mypackage
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class MyPackage : ReactPackage {
...
override fun createViewManagers(
reactContext: ReactApplicationContext
) = listOf(MyViewManager(reactContext))
}
// 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. Rejestracja Package
- Java
- Kotlin
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
add(MyAppPackage())
}
@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. Implementacja modułu JavaScript
I. Zacznij od niestandardowego menedżera widoków:
import {requireNativeComponent} from 'react-native';
export const MyViewManager =
requireNativeComponent('MyViewManager');
II. Następnie zaimplementuj własny widok, wywołując metodę create:
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}
/>
);
};
Jeśli chcesz udostępnić settery właściwości za pomocą adnotacji @ReactProp (lub @ReactPropGroup), zobacz przykład ImageView powyżej.