안드로이드 네이티브 UI 컴포넌트
네이티브 모듈(Native Module)과 네이티브 컴포넌트(Native Components)는 기존 아키텍처에서 사용하던 안정적인 기술이다. 새로운 아키텍처가 안정화되면 이 기술들은 점차 사용되지 않을 예정이다. 새로운 아키텍처는 터보 네이티브 모듈과 패브릭 네이티브 컴포넌트를 사용해 유사한 결과를 달성한다.
최신 앱에서 사용할 수 있는 다양한 네이티브 UI 위젯이 존재한다. 일부는 플랫폼에 내장되어 있고, 다른 것들은 서드파티 라이브러리로 제공되며, 또 다른 것들은 여러분의 포트폴리오에서 사용 중일 수도 있다. React Native는 ScrollView
와 TextInput
과 같은 가장 중요한 플랫폼 컴포넌트를 이미 래핑해 제공하지만, 모든 컴포넌트를 포함하지는 않는다. 특히 이전 앱에서 직접 작성한 컴포넌트는 포함되지 않는다. 다행히 기존 컴포넌트를 래핑해 React Native 애플리케이션과 원활하게 통합할 수 있다.
네이티브 모듈 가이드와 마찬가지로, 이 가이드도 Android SDK 프로그래밍에 어느 정도 익숙하다는 전제하에 작성된 고급 가이드이다. 이 가이드는 네이티브 UI 컴포넌트를 구축하는 방법을 보여주며, React Native 코어 라이브러리에 포함된 ImageView
컴포넌트의 일부를 구현하는 과정을 단계별로 설명한다.
네이티브 컴포넌트를 포함한 로컬 라이브러리를 한 번의 명령으로 설정할 수도 있다. 자세한 내용은 로컬 라이브러리 설정 가이드를 참고한다.
ImageView 예제
이 예제에서는 JavaScript에서 ImageView를 사용할 수 있도록 구현 요구 사항을 살펴본다.
네이티브 뷰는 ViewManager
를 확장하거나 더 일반적으로 SimpleViewManager
를 확장하여 생성하고 조작한다. 이 경우 SimpleViewManager
가 편리한데, 배경색, 투명도, 플렉스 박스 레이아웃과 같은 공통 속성을 적용하기 때문이다.
이 서브클래스들은 기본적으로 싱글톤이다. 브릿지에 의해 각각의 인스턴스가 하나만 생성된다. 이들은 네이티브 뷰를 NativeViewHierarchyManager
로 보내며, 이 매니저는 필요에 따라 뷰의 속성을 설정하고 업데이트하기 위해 다시 이들에게 위임한다. ViewManagers
는 일반적으로 뷰의 대리자 역할도 하며, 이벤트를 브릿지를 통해 JavaScript로 다시 보낸다.
뷰를 보내는 단계는 다음과 같다:
ViewManager
서브클래스를 생성한다.createViewInstance
메서드를 구현한다.@ReactProp
(또는@ReactPropGroup
) 어노테이션을 사용해 뷰 속성 설정자를 노출한다.- 애플리케이션 패키지의
createViewManagers
에서 매니저를 등록한다. - JavaScript 모듈을 구현한다.
1. ViewManager
서브클래스 생성
이 예제에서는 ReactImageView
타입의 SimpleViewManager
를 확장한 ReactImageManager
뷰 매니저 클래스를 만든다. ReactImageView
는 매니저가 관리하는 객체 타입으로, 커스텀 네이티브 뷰가 된다. getName
에서 반환하는 이름은 자바스크립트에서 네이티브 뷰 타입을 참조할 때 사용한다.
- 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으로 선언하고 반환 값이 없어야 한다(즉, Java에서는 void
, Kotlin에서는 Unit
을 반환 타입으로 사용). JS로 전송되는 프로퍼티 타입은 설정자 메서드의 값 인자 타입에 따라 자동으로 결정된다. 현재 지원되는 값 타입은 다음과 같다(Java 기준): boolean
, int
, float
, double
, String
, Boolean
, Integer
, ReadableArray
, ReadableMap
. Kotlin에서는 Boolean
, Int
, Float
, Double
, String
, ReadableArray
, ReadableMap
에 해당한다.
@ReactProp
어노테이션은 필수 인자로 name
을 받는다. 이 name
은 설정자 메서드와 연결되며, JS 측에서 프로퍼티를 참조할 때 사용된다.
name
외에도 @ReactProp
어노테이션은 다음과 같은 선택적 인자를 받을 수 있다: defaultBoolean
, defaultInt
, defaultFloat
. 이 인자들은 해당 타입(Java에서는 boolean
, int
, float
, Kotlin에서는 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에서 자유롭게 제어할 수 있는 네이티브 뷰 컴포넌트를 노출하는 방법을 알게 되었다. 그렇다면 사용자의 핀치 줌이나 패닝 같은 이벤트는 어떻게 처리할까? 네이티브 이벤트가 발생하면 네이티브 코드는 뷰의 JavaScript 표현에 이벤트를 전달해야 한다. 두 뷰는 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);
}
}
JavaScript에서 topChange
이벤트 이름을 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();
}
}
이 콜백은 원시 이벤트와 함께 호출되며, 일반적으로 래퍼 컴포넌트에서 처리해 더 간단한 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. Fragment
생성하기
- 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 예제를 참고한다.