QtAbstractListModel을 QML에 노출하기
개요
이 예제는 두 개의 개별 프로젝트로 구성됩니다: QML 프로젝트와 QML 콘텐츠를 호스팅하고 표시할 Kotlin 기반 안드로이드 프로젝트입니다. 이 예제에서는 QtAbstractListModel을 사용하여 Android 측에서 ListView 을 사용하여 데이터를 표시하는 QML 뷰로 데이터를 공유하는 방법을 보여줍니다.
예제 실행하기
이 예제를 실행하려면 안드로이드 스튜디오와 안드로이드용 Qt 설치가 필요합니다.
안드로이드 프로젝트 빌드 과정에서 Qt Gradle 플러그인을 사용하여 QML 프로젝트를 빌드할 것입니다. 이를 위해 이 예제에는 앱 수준의 build.gradle.kts 파일에 몇 가지 플러그인 구성이 있는데, 예를 들어 플러그인이 Qt 키트 디렉터리를 찾을 수 없는 경우 수정해야 할 수 있습니다.
QtBuild { // Relative for Qt (Installer or MaintenanceTool) installations. qtPath = file("../../../../../../../6.8.2") projectPath = file("../../qtabstractlistmodel") }
플러그인의 추가 구성은 Qt Gradle 플러그인 설명서를 참조하세요.
QML 프로젝트
QML 프로젝트는 루트 객체의 프로퍼티로 데이터 모델을 정의하고 해당 모델의 데이터를 표시하는 몇 가지 UI 요소를 정의하는 매우 간단한 프로젝트입니다.
Rectangle { id: mainRectangle property AbstractItemModel dataModel
모델의 데이터를 표시하기 위해 ListView 을 만듭니다. 그런 다음 model
속성을 앞서 선언한 데이터 모델로 설정합니다.
ListView { id: listView model: mainRectangle.dataModel
데이터 모델을 표시하려면 ListView 에 데이터 모델의 각 항목에 대해 인스턴스화할 델리게이트가 필요합니다. 이 경우 델리게이트는 Column 에 두 개의 Text 요소를 보유하는 Rectangle 으로 데이터 모델의 각 요소에서 데이터를 표시합니다.
delegate: Rectangle { required property var model width: listView.width height: textColumn.height + (2 * textColumn.spacing) color: "#2CDE85" radius: 25 Column { id: textColumn height: idText.height + rowText.height + spacing spacing: 15 anchors { verticalCenter: parent.verticalCenter left: parent.left right: parent.right leftMargin: 20 rightMargin: 20 } Text { id: idText color: "#00414A" text: model.id font.pixelSize: 36 font.bold: true } Text { id: rowText color: "#00414A" text: model.row font.pixelSize: 36 font.bold: true } } }
Kotlin 프로젝트
Android 쪽은 단일 활동과 앞서 QML 보기에서 사용한 데이터 모델에 대한 정의로 구성됩니다.
데이터 모델
데이터 모델 MyListModel
은 데이터의 내부 저장소 시스템으로 ArrayList<String>
을 사용하는 QtAbstractListModel의 자식 클래스입니다. MyListModel
의 이니셜라이저 블록에서 목록에 대한 임의의 데이터를 생성합니다.
class MyListModel : QtAbstractListModel() { private val m_dataList = ArrayList<String>() init { synchronized(this) { for (row in 0..4) { m_dataList.add(UUID.randomUUID().toString()) } } }
모델의 각 항목에는 각각 고유한 역할을 가진 데이터 요소 집합이 연관되어 있습니다. 각 데이터 요소에 대한 사용자 정의 역할을 정의하려면 QtAbstractItemModel의 사용자 정의 구현이 필요합니다. 각 역할에는 데이터를 검색할 때 사용되는 연관된 Int 값과 QML에서 사용할 때 데이터 요소의 이름을 지정하는 String 값이 있습니다.
@Synchronized override fun roleNames(): HashMap<Int, String> { val m_roles = HashMap<Int, String>() m_roles[DataRole.UUID.value()] = "id" m_roles[DataRole.Row.value()] = "row" return m_roles }
"roleNames()"
메서드의 Int
값은 하드 코딩될 수 있지만, 이 예에서는 이러한 값을 참조할 때 사용되는 MyListModel
내에 사용자 지정 열거형 클래스 DataRole
를 지정합니다. 이 예에서는 두 가지 역할을 정의합니다: UUID와 Row.
private enum class DataRole(val m_value: Int) { UUID(0), Row(1); fun value(): Int { return m_value } companion object { fun valueOf(value: Int): DataRole? { val values = entries.toTypedArray() if (0 <= value && value < values.size) return values[value] return null } } }
데이터 모델에서 데이터를 반환할 때 이 클래스는 "QtAbstractListModel::data()"
메서드를 재정의해야 합니다. 이 메서드에는 두 개의 매개변수가 필요합니다: 각각 데이터 요소의 인덱스와 역할을 나타내는 QtModelIndex와 Int
입니다.
"MyDataModel::data()"
에서 UUID
역할은 내부 데이터의 지정된 인덱스에서 데이터를 반환하고 Row
역할은 요청된 요소의 행을 반환합니다.
참고: 이 메서드는 다른 메서드와 함께 @Synchronized 태그로 주석을 달았습니다. 이는 이러한 메서드 호출이 Qt 스레드에서 시작되고 "addRow()"
및 "removeRow()"
메서드를 통해 Android 스레드의 요청과 동시에 기본 데이터에 액세스하기 때문입니다.
@Synchronized override fun data(qtModelIndex: QtModelIndex, role: Int): Any { return when (DataRole.valueOf(role)) { DataRole.UUID -> "UUID: " + m_dataList[qtModelIndex.row()] DataRole.Row -> "Row: " + qtModelIndex.row() else -> "" } }
이 예제에서는 외부 액터가 QtAbstractItemModel을 조작할 수 있도록 MyDataModel
에 두 개의 메서드를 추가합니다. 행에 데이터를 추가하려면 "addRow()"
메서드가 있고, 데이터를 제거하려면 "removeRow()"
메서드가 있습니다. 이 메서드들은 메인 활동에서 사용됩니다.
@Synchronized fun addRow() { beginInsertRows(QtModelIndex(), m_dataList.size, m_dataList.size) m_dataList.add(UUID.randomUUID().toString()) endInsertRows() } @Synchronized fun removeRow() { if (!m_dataList.isEmpty()) { beginRemoveRows(QtModelIndex(), m_dataList.size - 1, m_dataList.size - 1) m_dataList.removeAt(m_dataList.size - 1) endRemoveRows() } }
주요 활동
MainActivity
클래스는 단순한 Kotlin 기반 액티비티이지만 QtQmlStatusChangeListener 인터페이스를 구현하여 QML 로딩 상태 이벤트를 수신합니다. 또한 QML 애플리케이션의 메인 뷰를 위한 QtQuickViewContent
객체와 위에서 설명한 데이터 모델의 인스턴스를 저장합니다.
class MainActivity : AppCompatActivity(), QtQmlStatusChangeListener { private val m_mainQmlContent: Main = Main() private val m_listModel = MyListModel()
애플리케이션의 메인 액티비티를 만들 때 이 예제에서는 먼저 QtQuickView를 생성하여 뷰 계층 구조에 배치합니다.
val qtQuickView: QtQuickView = QtQuickView(this) val params: ViewGroup.LayoutParams = FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) val qmlFrameLayout: FrameLayout = findViewById<FrameLayout>(R.id.qmlFrame) qmlFrameLayout.addView(qtQuickView, params)
UI에 QtQuickView를 추가한 후 이 예제에서는 데이터 모델을 조작하는 데 사용되는 버튼을 찾고 멤버 데이터 모델에서 addRow()
및 removeRow()
을 호출하는 몇 가지 클릭 리스너를 설정합니다.
val addRowAtEndButton: Button = findViewById<Button>(R.id.addRow) val removeRowFromEndButton: Button = findViewById<Button>(R.id.removeRow) addRowAtEndButton.setOnClickListener { _: View? -> m_listModel.addRow() } removeRowFromEndButton.setOnClickListener { _: View? -> m_listModel.removeRow() }
UI 설정과 리스너 설정이 완료되면 QML 컴포넌트를 준비하여 로드할 수 있습니다. 이 예제에서는 MainActivity
을 QML 컴포넌트의 상태 변경 신호 리스너로 설정하고 QtQuickView
에 QML 컴포넌트를 로드하도록 지시합니다.
m_mainQmlContent.setStatusChangeListener(this) qtQuickView.loadContent(m_mainQmlContent)
마지막으로, QML 컴포넌트가 성공적으로 로드되면 이 예제에서는 MyDataModel 인스턴스의 값을 QML 컴포넌트의 dataModel
속성에 할당합니다.
override fun onStatusChanged(qtQmlStatus: QtQmlStatus) { if (qtQmlStatus === QtQmlStatus.READY) { m_mainQmlContent.setDataModel(m_listModel) } }
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.