2020. 10. 12. 17:59ㆍProgramming/Flutter
Flutter에서 Native(Android/iOS)로 작성한 UI Component 사용하기
Flutter를 사용해서 하이브리드 앱을 만들 때, 성능상 한계로 인해 네이티브를 사용해 UI를 작성해야 할 때가 있다. 혹은 이미 Kotlin/Swift를 사용해서 만들어진 컴포넌트 UI가 있어서, Flutter로 코드를 재작성하지 않고 네이티브로 작성된 UI를 불러와야 할 때가 있다. 이러한 경우 어떻게 하면 되는지 살펴보도록 하자.
현재 테스트는 안드로이드만 해봤기 때문에, 이 글은 안드로이드 기준으로 작성한다. 안드로이드는 v2로 업데이트 되면서 사용법이 좀 달라졌기 때문에 삽질을 했지만, iOS는 아마 별 문제 없으리라 생각한다.
사실 공식 문서 Hosting native Android and iOS views in your Flutter app with Platform Views 항목을 보면, v2 기준으로 잘 기술되어있다. 2020년 10월 기준으로 구글링하면 가뭄에 콩나듯 v1 기준으로 작성된 예제가 검색되는데,
io.flutter.app.FlutterActivity
는 당장은 아니더라도 Deprecated될 예정이기 때문에 공식 문서를 참조하여 v2 기준으로 작성하도록 하자.
Hybrid Composition과 Virtual Display
Native로 작성한 코드를 불러오기 위해서는, 먼저 Dart파일을 사용하여 위젯을 생성해야한다. Dart로 코드를 작성할 때 Hybrid Composition를 사용하는 방법과 Virtual Display 를 사용하는 방법으로 나뉘게되는데, 두 가지 방법의 차이는 아래와 같다. 적절히 골라서 사용하도록하자.
Hybrid Composition
Flutter 1.22 버전 이상에서 사용 가능하며, 네이티브로 작성된 android.view.View
를 View 계층(View Hierachy)에 추가한다. 따라서 키보드와 같은 동작이 UI 범위 박스 바깥쪽(UI 바깥쪽)에서 동작한다. 안드로이드10 이전 버전에서는 프레임(FPS)이 조금 떨어질 수 있다. 성능은 링크에서 설명한다.
Virtual Display
android.view.View
를 텍스쳐로 렌더링하기 때문에, 안드로이드 View 계층(View Hierachy)에 포함되지 않는다. 따라서 키보드나 접근성 관련 조작이 동작하지 않을 수 있다.
Dart로 코드 작성
Hybrid Composition
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
위의 패키지를 import하도록 하자. 사실 아래의 코드를 첨부한 뒤, 에러가 나는 부분에서 퀵 픽스(cmd+.)를 누르면 자동으로 import되는 항목들이다.
적당히 NativeView
라는 이름의 StatelessWidget
을 만들어준 뒤, build
함수의 내용을 다음과 같이 수정해주자.
Widget build(BuildContext context) {
// viewType은 네이티브 코드에서 뷰를 등록할 때 사용한다.
final String viewType = '<platform-view-type>';
// 파라메터를 네이티브로 전달할 때 사용한다.
final Map<String, dynamic> creationParams = <String, dynamic>{};
return PlatformViewLink(
viewType: viewType,
surfaceFactory:
(BuildContext context, PlatformViewController controller) {
return AndroidViewSurface(
controller: controller,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
);
},
onCreatePlatformView: (PlatformViewCreationParams params) {
return PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: StandardMessageCodec(),
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
);
}
이걸로 Hybrid Composition를 위한 Dart 코드는 끝이다. 자세한 사항은 PlatformViewLink, AndroidViewService, PlatformViewsService API 문서를 참조하도록 하자.
Virtual Device
import 'package:flutter/widget.dart';
위의 패키지를 import하도록 하자.
Widget build(BuildContext context) {
// viewType은 네이티브 코드에서 뷰를 등록할 때 사용한다.
final String viewType = 'hybrid-view-type';
// 파라메터를 네이티브로 전달할 때 사용한다.
final Map<String, dynamic> creationParams = <String, dynamic>{};
return AndroidView(
viewType: viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
);
}
Hybrid Composition에 비해 코드가 간단한 편이다. 자세한 사항은 AndroidView API 문서를 참조하도록 하자.
Native로 코드 작성 (Android)
이제 Native로 코드를 작성해보자.PlatformView
를 상속받아서 실제 View를 저장하는 함수와, PlatformViewFactory
를 상속받아서 View를 생성하는 함수를 만든 뒤 FlutterEngine
에 등록해주기만 하면 된다.
PlatformView
TextView를 생성해서 저장하고 있다가, 요청이 오면 저장하고 있던 TextView를 반환하는 NativeView.kt
를 생성해보자.
import android.content.Context
import android.graphics.Color
import android.view.View
import android.widget.TextView
import io.flutter.plugin.platform.PlatformView
internal class NativeView(context: Context, id: Int, creationParams: Map<String?, Any?>?) : PlatformView {
private val textView: TextView
override fun getView(): View {
return textView
}
override fun dispose() {}
init { // textview을 생성한 후 속성을 변경해준다.
textView = TextView(context)
textView.textSize = 72f
textView.setBackgroundColor(Color.rgb(255, 255, 255))
textView.text = "Rendered on a native Android view (id: $id)"
}
}
NativeView
클래스는 TextView
의 객체를 생성해서 저장하고 있다가, getView()
가 호출되면 저장하고 있던 TextView
의 레퍼런스를 반환해주는 역할을 한다.
PlatformViewFactory
이제 PlatformViewFactory
를 상속받는 NativeViewFactory
를 생성하도록하자. NativeViewFactory
는 위에서 작성한 NativeView
의 인스턴스를 생성하는 역할을 한다.
import android.content.Context
import android.view.View
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory
internal class NativeViewFactory(private val messenger: BinaryMessenger, private val containerView: View) : PlatformViewFactory(StandardMessageCodec.INSTANCE) {
override fun create(context: Context, id: Int, args: Any?): PlatformView {
val creationParams = args as Map<String?, Any?>?
return NativeView(context, id, creationParams)
}
}
FlutterEngine에 등록
이제 위에서 만든 NativeViewFactory
를 MainActivity
에서,configureFlutterEngine
를 호출하여 FlutterEngine
에 등록해주기만 하면 된다.
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
flutterEngine
.platformViewsController
.registry
.registerViewFactory("<platform-view-type>", NativeViewFactory())
}
}
플러그인을 생성하는 중이라면, 플러그인의 메인 클래스를 다음과 같이 수정하여 등록해주도록 하자.
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.FlutterPlugin.FlutterPluginBinding
class PlatformViewPlugin : FlutterPlugin {
override fun onAttachedToEngine(binding: FlutterPluginBinding) {
binding
.platformViewRegistry
.registerViewFactory("<platform-view-type>", NativeViewFactory())
}
override fun onDetachedFromEngine(binding: FlutterPluginBinding) {}
}
여기까지 작성한 후에 코드를 실행해보면 화면에 Rendered on a native Android view (id: 0)
라는 문구가 출력되는 것을 확인할 수 있다. 위의 코드는 안드로이드에만 해당되는 코드이므로, 다른 플랫폼에 대해서 예외처리가 필요하다. 이러한 플랫폼 별 예외처리에 대해서는 공식문서의 Putting it together를 참조하도록 하자.
'Programming > Flutter' 카테고리의 다른 글
플러터(Flutter) 사용시 참고할만한 사이트 (0) | 2021.01.04 |
---|---|
UDP 라이브러리를 통해 Flutter(Dart)에서 브로드캐스팅을 해보자 (0) | 2020.11.30 |
Provider를 사용한 페이지간의 데이터 공유시, Error: Could not find the correct Provider<ProviderName> above this <WidgetName> Widget이 발생하는 원인과 해결법 (2) | 2020.11.01 |
flutter upgrade를 실행한 뒤, flutter로 뭘 해도 building flutter tools...와 함께 에러가 발생한다면...? (0) | 2020.10.18 |
Flutter에서 C/C++로 Pointer를 전달하여 연산하기 (5) | 2020.09.25 |