안드로이드 네이티브 UI 컴포넌트
Native Module과 Native Components는 기존 아키텍처에서 사용하던 안정적인 기술이다.
새로운 아키텍처가 안정화되면 앞으로 지원이 중단될 예정이다. 새로운 아키텍처에서는 Turbo Native Module과 Fabric Native Components를 사용해 유사한 결과를 달성한다.
최신 앱에서 사용할 수 있는 다양한 네이티브 UI 위젯이 존재한다. 일부는 플랫폼에 내장되어 있고, 다른 것들은 서드파티 라이브러리로 제공되며, 또 다른 것들은 여러분의 포트폴리오에서 사용 중일 수도 있다. React Native는 ScrollView
와 TextInput
같은 가장 중요한 플랫폼 컴포넌트를 이미 래핑해 제공하지만, 모든 컴포넌트를 포함하지는 않으며, 특히 이전 앱에서 직접 작성한 컴포넌트는 포함되지 않는다. 다행히도, 기존 컴포넌트를 래핑해 React Native 애플리케이션과 원활하게 통합할 수 있다.
네이티브 모듈 가이드와 마찬가지로, 이 가이드는 Android SDK 프로그래밍에 어느 정도 익숙한 개발자를 대상으로 한 고급 가이드이다. 이 가이드는 React Native 코어 라이브러리에 있는 ImageView
컴포넌트의 일부를 구현하는 과정을 통해 네이티브 UI 컴포넌트를 만드는 방법을 설명한다.
한 줄의 명령어로 네이티브 컴포넌트를 포함한 로컬 라이브러리를 설정할 수도 있다. 자세한 내용은 로컬 라이브러리 설정 가이드를 참고한다.
ImageView 예제
이 예제에서는 JavaScript에서 ImageView를 사용할 수 있도록 구현 요구 사항을 단계별로 살펴본다.
네이티브 뷰는 ViewManager
를 확장하거나, 더 일반적으로 SimpleViewManager
를 확장하여 생성하고 조작한다. SimpleViewManager
는 배경색, 투명도, 플렉스 박스 레이아웃과 같은 공통 속성을 적용하기 때문에 이 경우에 편리하다.
이러한 서브클래스는 기본적으로 싱글톤이다. 브리지에 의해 각각의 인스턴스가 하나만 생성된다. 이들은 네이티브 뷰를 NativeViewHierarchyManager
로 보내고, 필요한 경우 뷰의 속성을 설정하고 업데이트하기 위해 다시 위임한다. ViewManagers
는 일반적으로 뷰의 대리자 역할도 하며, 이벤트를 브리지를 통해 JavaScript로 전송한다.
뷰를 전송하려면 다음 단계를 따른다:
ViewManager
서브클래스를 생성한다.createViewInstance
메서드를 구현한다.@ReactProp
(또는@ReactPropGroup
) 어노테이션을 사용해 뷰 속성 설정자를 노출한다.- 애플리케이션 패키지의
createViewManagers
에서 매니저를 등록한다. - JavaScript 모듈을 구현한다.
1. ViewManager
서브클래스 생성하기
이 예제에서는 ReactImageView
타입의 SimpleViewManager
를 확장한 ReactImageManager
뷰 매니저 클래스를 만든다. ReactImageView
는 매니저가 관리하는 객체 타입으로, 커스텀 네이티브 뷰가 된다. getName
이 반환하는 이름은 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. createViewInstance
메서드 구현
뷰는 createViewInstance
메서드에서 생성된다. 뷰는 기본 상태로 초기화해야 하며, 모든 속성은 이후 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. @ReactProp
(또는 @ReactPropGroup
) 어노테이션을 사용해 뷰 속성 설정자 노출하기
자바스크립트에서 반영할 속성은 @ReactProp
(또는 @ReactPropGroup
) 어노테이션이 달린 설정자 메서드로 노출해야 한다. 설정자 메서드는 첫 번째 인자로 업데이트할 뷰(현재 뷰 타입)를, 두 번째 인자로 속성 값을 받는다. 설정자는 public으로 선언하고, 반환 값이 없어야 한다(즉, 자바에서는 void
, 코틀린에서는 Unit
). JS로 전송되는 속성 타입은 설정자의 두 번째 인자 타입에 따라 자동으로 결정된다. 현재 지원하는 값의 타입은 다음과 같다(자바 기준): boolean
, int
, float
, double
, String
, Boolean
, Integer
, ReadableArray
, ReadableMap
. 코틀린에서는 Boolean
, Int
, Float
, Double
, String
, ReadableArray
, ReadableMap
에 해당한다.
@ReactProp
어노테이션은 필수 인자인 name
을 포함한다. 이 name
은 설정자 메서드와 연결되며, JS 측에서 속성을 참조할 때 사용된다.
name
외에도 @ReactProp
어노테이션은 다음과 같은 선택적 인자를 가질 수 있다: defaultBoolean
, defaultInt
, defaultFloat
. 이 인자들은 해당 타입(자바에서는 boolean
, int
, float
, 코틀린에서는 Boolean
, Int
, Float
)이어야 하며, 설정자가 참조하는 속성이 컴포넌트에서 제거된 경우 이 값이 설정자 메서드에 전달된다. 기본값은 원시 타입에만 제공되며, 복잡한 타입의 설정자인 경우 속성이 제거되면 null
이 기본값으로 전달된다.
@ReactPropGroup
어노테이션을 사용한 설정자 메서드의 선언 요구사항은 @ReactProp
와 다르다. 자세한 내용은 @ReactPropGroup
어노테이션 클래스 문서를 참고한다. 중요! ReactJS에서 속성 값을 업데이트하면 설정자 메서드가 호출된다. 컴포넌트를 업데이트하는 방법 중 하나는 이전에 설정된 속성을 제거하는 것이다. 이 경우에도 설정자 메서드가 호출되어 뷰 매니저에게 속성이 변경되었음을 알린다. 이때 원시 타입의 경우 defaultBoolean
, defaultFloat
등의 @ReactProp
어노테이션 인자로 기본값을 지정할 수 있으며, 복잡한 타입의 경우 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. ViewManager
등록하기
마지막 단계는 ViewManager
를 애플리케이션에 등록하는 것이다. 이 과정은 네이티브 모듈과 유사하게 애플리케이션 패키지의 멤버 함수인 createViewManagers
를 통해 이루어진다.
- 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. JavaScript 모듈 구현
마지막 단계는 새로운 뷰를 사용하는 사용자들을 위해 Java/Kotlin과 JavaScript 간의 인터페이스 계층을 정의하는 JavaScript 모듈을 만드는 것이다. 이 모듈에서 컴포넌트 인터페이스를 문서화하는 것을 권장한다(예: TypeScript, Flow, 또는 일반 주석 사용).
import {requireNativeComponent} from 'react-native';
/**
* `View`를 합성한다.
*
* - src: Array<{url: string}>
* - borderRadius: number
* - resizeMode: 'cover' | 'contain' | 'stretch'
*/
export default requireNativeComponent('RCTImageView');
requireNativeComponent
함수는 네이티브 뷰의 이름을 인자로 받는다. 컴포넌트가 더 복잡한 작업을 수행해야 하는 경우(예: 커스텀 이벤트 핸들링), 네이티브 컴포넌트를 다른 React 컴포넌트로 감싸야 한다. 아래 MyCustomView
예제에서 이를 확인할 수 있다.
이벤트 처리
이제 JS에서 자유롭게 제어할 수 있는 네이티브 뷰 컴포넌트를 노출하는 방법을 알았다. 그렇다면 사용자의 핀치 줌이나 패닝과 같은 이벤트는 어떻게 처리할까? 네이티브 이벤트가 발생하면 네이티브 코드는 뷰의 JS 표현에 이벤트를 전달해야 한다. 이때 두 뷰는 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);
}
}
topChange
이벤트 이름을 JS의 onChange
콜백 prop에 매핑하려면, ViewManager
에서 getExportedCustomBubblingEventTypeConstants
메서드를 오버라이드하여 등록한다.
- 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();
}
}
이 콜백은 raw 이벤트와 함께 호출된다. 일반적으로 이 이벤트는 래퍼 컴포넌트에서 처리하여 더 간단한 API를 만든다.
import {useCallback} from 'react';
import {requireNativeComponent} from 'react-native';
const RCTMyCustomView = requireNativeComponent('RCTMyCustomView');
export default function MyCustomView(props: {
// ...
/**
* 사용자가 지도를 드래그할 때 연속적으로 호출되는 콜백.
*/
onChangeMessage: (message: string) => unknown;
}) {
const onChange = useCallback(
event => {
props.onChangeMessage?.(event.nativeEvent.message);
},
[props.onChangeMessage],
);
return <RCTMyCustomView {...props} onChange={props.onChange} />;
}
안드로이드 프래그먼트와의 통합 예제
기존 네이티브 UI 엘리먼트를 React Native 앱에 통합하려면, ViewManager
에서 View
를 반환하는 것보다 더 세밀한 제어가 필요할 수 있다. 이때 안드로이드 프래그먼트를 사용하면 된다. 특히 onViewCreated
, onPause
, onResume
같은 라이프사이클 메서드를 활용해 뷰와 연결된 커스텀 로직을 추가하고자 할 때 유용하다. 아래 단계를 따라 구체적인 방법을 알아보자.
1. 커스텀 뷰 예제 만들기
먼저 FrameLayout
을 상속받는 CustomView
클래스를 만든다. 이 뷰의 내용은 여러분이 렌더링하고 싶은 어떤 뷰든 가능하다.
- Java
- Kotlin
// 패키지를 여러분의 것으로 변경
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 {
// 패딩과 배경색 설정
setPadding(16,16,16,16)
setBackgroundColor(Color.parseColor("#5FD3F3"))
// 기본 텍스트 뷰 추가
addView(TextView(context).apply {
text = "Welcome to Android Fragments with React Native."
})
}
}
// 패키지를 여러분의 것으로 변경
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);
// 패딩과 배경색 설정
this.setPadding(16,16,16,16);
this.setBackgroundColor(Color.parseColor("#5FD3F3"));
// 기본 텍스트 뷰 추가
TextView text = new TextView(context);
text.setText("Welcome to Android Fragments with React Native.");
this.addView(text);
}
}
2. 프래그먼트 생성하기
- Java
- Kotlin
// 패키지를 자신의 것으로 교체
package com.mypackage
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
// 뷰의 임포트를 자신의 것으로 교체
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 // 이 CustomView는 렌더링하려는 어떤 뷰든 가능
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// `onCreate` 메서드에서 처리해야 할 로직을 여기에 추가
// 예: customView.onCreate(savedInstanceState);
}
override fun onPause() {
super.onPause()
// `onPause` 메서드에서 처리해야 할 로직을 여기에 추가
// 예: customView.onPause();
}
override fun onResume() {
super.onResume()
// `onResume` 메서드에서 처리해야 할 로직을 여기에 추가
// 예: customView.onResume();
}
override fun onDestroy() {
super.onDestroy()
// `onDestroy` 메서드에서 처리해야 할 로직을 여기에 추가
// 예: customView.onDestroy();
}
}
// 패키지를 자신의 것으로 교체
package com.mypackage;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.fragment.app.Fragment;
// 뷰의 임포트를 자신의 것으로 교체
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; // 이 CustomView는 렌더링하려는 어떤 뷰든 가능
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// `onCreate` 메서드에서 처리해야 할 로직을 여기에 추가
// 예: customView.onCreate(savedInstanceState);
}
@Override
public void onPause() {
super.onPause();
// `onPause` 메서드에서 처리해야 할 로직을 여기에 추가
// 예: customView.onPause();
}
@Override
public void onResume() {
super.onResume();
// `onResume` 메서드에서 처리해야 할 로직을 여기에 추가
// 예: customView.onResume();
}
@Override
public void onDestroy() {
super.onDestroy();
// `onDestroy` 메서드에서 처리해야 할 로직을 여기에 추가
// 예: customView.onDestroy();
}
}
3. ViewManager
서브클래스 생성
- Java
- Kotlin
// 패키지를 자신의 것으로 변경
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
/**
* 프래그먼트를 담을 FrameLayout 반환
*/
override fun createViewInstance(reactContext: ThemedReactContext) =
FrameLayout(reactContext)
/**
* "create" 커맨드를 정수로 매핑
*/
override fun getCommandsMap() = mapOf("create" to COMMAND_CREATE)
/**
* "create" 커맨드 처리 (JS에서 호출됨) 및 createFragment 메서드 호출
*/
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
}
/**
* React Native 뷰를 커스텀 프래그먼트로 교체
*/
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)
}
})
}
/**
* 모든 자식 뷰를 적절히 배치
*/
private fun manuallyLayoutChildren(view: View) {
// propWidth와 propHeight는 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
}
}
// 패키지를 자신의 것으로 변경
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;
}
/**
* 프래그먼트를 담을 FrameLayout 반환
*/
@Override
public FrameLayout createViewInstance(ThemedReactContext reactContext) {
return new FrameLayout(reactContext);
}
/**
* "create" 커맨드를 정수로 매핑
*/
@Nullable
@Override
public Map<String, Integer> getCommandsMap() {
return MapBuilder.of("create", COMMAND_CREATE);
}
/**
* "create" 커맨드 처리 (JS에서 호출됨) 및 createFragment 메서드 호출
*/
@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;
}
}
/**
* React Native 뷰를 커스텀 프래그먼트로 교체
*/
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);
}
});
}
/**
* 모든 자식 뷰를 적절히 배치
*/
public void manuallyLayoutChildren(View view) {
// propWidth와 propHeight는 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. ViewManager
등록하기
- Java
- Kotlin
// 패키지를 여러분의 것으로 변경하세요
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))
}
// 패키지를 여러분의 것으로 변경하세요
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. Package
등록하기
- Java
- Kotlin
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// 자동 연결이 되지 않는 패키지는 여기에 수동으로 추가할 수 있다. 예를 들어:
// add(MyReactNativePackage())
add(MyAppPackage())
}
@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// 자동 연결이 되지 않는 패키지는 여기에 수동으로 추가할 수 있다. 예를 들어:
// packages.add(new MyReactNativePackage());
packages.add(new MyAppPackage());
return packages;
}
6. 자바스크립트 모듈 구현
I. 커스텀 뷰 매니저부터 시작:
import {requireNativeComponent} from 'react-native';
export const MyViewManager =
requireNativeComponent('MyViewManager');
II. 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,
// 'create' 커맨드를 호출
UIManager.MyViewManager.Commands.create.toString(),
[viewId],
);
export const MyView = () => {
const ref = useRef(null);
useEffect(() => {
const viewId = findNodeHandle(ref.current);
createFragment(viewId);
}, []);
return (
<MyViewManager
style={{
// dpi를 px로 변환, 원하는 높이 지정
height: PixelRatio.getPixelSizeForLayoutSize(200),
// dpi를 px로 변환, 원하는 너비 지정
width: PixelRatio.getPixelSizeForLayoutSize(200),
}}
ref={ref}
/>
);
};
@ReactProp
(또는 @ReactPropGroup
) 어노테이션을 사용해 프로퍼티 설정자를 노출하려면 위의 ImageView 예제를 참고한다.