初心者向けの Jetpack Compose チュートリアル – コンポーザブルと再構成を理解する方法
このチュートリアルでは、Android の Jetpack Compose UI ライブラリに関連するいくつかの基本的な概念と用語について説明します。
これは Compose の初心者向けガイドですが、Android の初心者向けガイドではありません。そのため、少なくとも 1 つまたは 2 つのアプリケーションを作成している必要があります (ただし、Compose ではなくてもかまいません)。
始める前に、私は当初、Leland Richardson の 2 部構成の記事シリーズに出会うまで、より上級の開発者向けのフォローアップ記事を書くことを計画していました。 Leland は Jetpack Compose チームで働くソフトウェア エンジニアであるだけでなく、優れたライターでもあるようです。
この記事は、Jetpack Compose の基本を紹介するものとして独立していると思いますが、強くお勧めします。 Compose の実践的な経験を積んだら (または、その方法を学びたい場合はすぐに) 彼の記事を読んでください。
この記事で説明されている主な用語/概念:
- 古いビュー システムと階層の簡単なレビュー
- コンポーザブルとビューとの関係
- 再構成と、再構成がうまくいかない方法
コンポーザブルとは
このセクションでは、Jetpack Compose ライブラリの最も基本的な部分について説明します。あなたが経験豊富な Android 開発者である場合は、「コンポーザブルはビューですか?」というタイトルのサブセクションにスキップすることをお勧めします。
ビュー システムにまだ慣れていない場合は、次のセクションを読んで、コンポーザブルとは何かを理解する必要があります。
階層を表示
Android SDK (このプラットフォームでユーザー インターフェイスを作成するために使用するライブラリ) のコンテキストでは、View は、アプリケーションに構造とスタイルを与えるために使用するものです。
これは、特定のユーザー インターフェース (UI) の最も基本的な種類の構成要素または要素であり、これらの各構成要素には (特に) 次の種類の情報が含まれます:
- デバイス画面のどこにビューを描画するかをコンピューターに指示する X および Y の開始位置と終了位置
- 色とアルファ (透明度) の値
- フォント情報、テキスト、記号、画像
- ユーザー インタラクション (クリック) やアプリケーション データの変更 (詳細は後述) などのイベントに基づく動作
ビューはボタンのようなものであることを理解することが重要です (一般に「ウィジェット」と呼ばれます)、画面全体、画面の一部、または他の子ビューのコンテナーにすることもできます .
このような コンテナ コンテキストに応じて、一般にレイアウトまたはビューグループと呼ばれます。また、ウィジェットと同じ種類の情報のほとんどを共有しながら、ネストされた他のビューを配置および表示する方法に関する情報も含まれています。
それを念頭に置いて、ビュー システムのこのレビューの重要な部分である ビュー階層 に進みます。 . Web 開発者にとって、ビュー階層は基本的にドキュメント オブジェクト モデル (DOM) の Android バージョンです。
Android 開発者の場合、ビュー階層は、XML ファイルで、または Java または Kotlin でプログラムによって定義したすべてのビューの仮想表現と考えることができます。
これを説明するために、そのような XML ファイルを見てみましょう (詳しく調べる必要はありません。名前だけに注意してください)。次に、デバッガー/ステッパー ツールを使用して、このファイルを膨張させるフラグメントのメモリ空間がどのように見えるかを調べます。
fragment_hour_view.xml:
<?xml version=”1.0" encoding=”utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=”https://schemas.android.com/apk/res/android"
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:id=”@+id/root_hour_view_fragment”
xmlns:app=”https://schemas.android.com/apk/res-auto"
>
<androidx.compose.ui.platform.ComposeView
android:id=”@+id/tlb_hour_view”
//...
/>
<com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView
android:id=”@+id/vqht_one”
//...
/>
<com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView
android:id=”@+id/vqht_two”
//...
/>
<com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView
android:id=”@+id/vqht_three”
//...
/>
<com.wiseassblog.samsaradayplanner.ui.managehourview.HourToggleView
android:id=”@+id/vqht_four”
//...
/>
</androidx.constraintlayout.widget.ConstraintLayout>
(フラグメント)HourView.kt のメモリ容量:
デバッガーとステッパー ツールは、さまざまなライブラリーから使用するコードの内部で何が起こっているかを知るための私のお気に入りの方法の 1 つです。しばらく試してみてください!
この XML ファイルと、プロセス でこの XML ファイルがどうなるかを示す目的 (プロセスは単に実行中のプログラムです
願わくば、古いシステムがどのように機能するかについてのシンプルだが具体的なモデルを使用して、新しいシステムと比較できるようにしてください。
コンポーザブルはビューですか?
これは、私が Compose を使い始めたときに最初に尋ねた質問の 1 つであり、たどり着いた答えはどちらも yes です。 いいえ .
はい 、コンポーザブルがビューと同じ概念的役割を果たすという意味で 古いシステムで。 Composable は、ボタンのようなウィジェットにすることも、ConstraintLayout などのコンテナーにすることもできます (利用可能な ConstraintLayout の Composable 実装があることに注意してください)。
いいえ 、UI がビュー階層で仮想的に表現されなくなったという意味で (相互運用性を伴う状況は別として)。そうは言っても、compose は UI を仮想的に表現して追跡するために魔法を使用しません。これは、View Hierarchy と概念的に類似した独自のものを持たなければならないことを意味します。
このことを非常に簡単に見てみましょう。ここに、setContent {…}
を使用するアクティビティがあります。 Composable をそれ自体にバインドする関数:
ActiveGameActivity.kt:
class ActiveGameActivity : AppCompatActivity(), ActiveGameContainer {
private lateinit var logic: ActiveGameLogic
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ActiveGameViewModel()
setContent {
ActiveGameScreen(
onEventHandler = {
logic.onEvent(it)
},
viewModel
)
}
logic = buildActiveGameLogic(this, viewModel, applicationContext)
}
//…
}
ActiveGameScreen.kt:
@Composable
fun ActiveGameScreen(
onEventHandler: ((ActiveGameEvent) -> Unit),
viewModel: ActiveGameViewModel
) {
//...
GraphSudokuTheme {
Column(
Modifier
.background(MaterialTheme.colors.primary)
.fillMaxHeight()
) {
ActiveGameToolbar(
clickHandler = {
onEventHandler.invoke(
ActiveGameEvent.OnNewGameClicked
)
}
)
Box {
//content
}
}
}
}
Compose では、View Hierarchy は、 mWindow を深く掘り下げた場合に見つけられるものに置き換えられます。 このアクティビティのフィールド。そのフィールド内には、ビュー階層の概念的な置き換えがあります: Composer
と slotTable
.
この時点で、Composer
の詳細な概要が必要な場合は、 とその slotTable
、リーランドの記事を読むことを再度提案する必要があります (彼はパート 2 で詳しく説明します)。 Compose Hierarchy には、Composer とその slotTable よりも多くの機能がありますが、開始するにはこれで十分です。
一般的に言えば、Jetpack Compose は Compose Hierarchy (Composer やその slotTable などで作成および管理されるもの) と呼ばれるものを使用します。
繰り返しますが、これは、まとめて UI を表すメモリ空間内の一連のオブジェクトであるビュー階層と同じ概念ですが、実装方法は大きく異なります。
ただし、重要な違いがあります。技術的に理解するのは難しいですが、原則として理解するのは簡単です。これは、Compose が Compose 階層への更新を処理する方法です:再構成 .
再構成:Compose UI を更新する方法
私の ESL の友人にとって、Compose という言葉はラテン語の componere に由来します。 、これは大まかに「まとめる」という意味です。音楽を書く人は、しばしば「作曲家」と呼ばれます。これは、1 つまたは複数の楽器からの音を組み合わせて作曲 (曲) を作成する人だと考えることができます。
組み合わせるということは、個々のピースがあることを意味します。ほとんどすべての優れたソフトウェア開発者は、コードを合理的な最小の部分に分解するために少なくともある程度の努力を払っていることを理解することが重要です。 .
合理的だと言います なぜなら、DRY (Don't Repeat Yourself) のような原則は、それらが生み出すよりも多くの問題を解決するという範囲でのみ従うべきだと思うからです.
この概念を適用することには、多くの場合、モジュール性 (または、私が好むように、関心の分離 (SOC)) と呼ばれる多くの利点があります。これを読んでいる方の中には、Leland が彼の記事で述べたことを単にコピーしているだけだと思う人もいるかもしれませんが、私はソフトウェア アーキテクチャの黄金原理として SOC について何年も前から話してきました。
これが Compose に作用するのは、人気のある Javascript ライブラリ React で見られるのと同じ原則です。 .適切に行われると、Compose は再構成が必要なコンポーザブル (UI のパーツ/要素) のみを「再構成」 (再描画、再レンダリング、更新など) します。
これは、アプリケーションのパフォーマンスに関して非常に重要です。これは、UI の再描画は、古い View システムでも Compose でも、システム リソースにコストがかかるためです。
ご存じないかもしれませんが、古い RecyclerView (私が 2016 年に初めてチュートリアルを作成したのはこれでした!) の全体的な目的は、ViewHolder パターンをデータのリストに使用することでした。これにより、リスト アイテムごとに新しいビューを常にインフレート (作成) する必要がなくなりました。
この記事の目標は、主に理論に焦点を当てることでした。今後数か月にわたって実践的な内容をたくさん書く予定です。ただし、再構成の仕組みと、再構成の失敗を回避する方法をさらに理解するのに役立つ、私の直接の経験からの話で記事を締めくくります。
ストップウォッチの例
初めての完全な Compose アプリケーションとして、数独を作成することにしました。非常に複雑な UI を持たないプロジェクトが欲しかったという事実を含め、理由はいくつかあります。また、数独パズルに非常に適した Graph DS と Algos を深く掘り下げる機会も欲しかった.
私が欲しかったのは、ユーザーがパズルを完成させるのにかかった時間を追跡するストップウォッチでした:
私の職業ではよくあることですが、このタイマーは実際よりもはるかに簡単に追加できると思っていました。私は Android の Chronometer クラスと Java Timer クラスをいじりましたが、どちらも異なるものでしたが、アプリケーションを壊す問題がありました。
最終的に一歩下がって、Kotlin で書いていることに気付きました。そこで、プレゼンテーション ロジック クラスにコルーチン ベースのタイマーを設定しました (最終的にそこに配置するのが最も理にかなっています)。これにより、viewmodel が毎秒更新されます。
Class ActiveGameLogic(…):…{
//…
inline fun startCoroutineTimer(
delayMillis: Long = 0,
repeatMillis: Long = 1000,
crossinline action: () -> Unit
) = launch {
delay(delayMillis)
if (repeatMillis > 0) {
while (true) {
action()
delay(repeatMillis)
}
} else {
action()
}
}
private fun onStart() =
launch {
gameRepo.getCurrentGame(
{ puzzle, isComplete ->
viewModel.initializeBoardState(
puzzle,
isComplete
)
if (!isComplete) timerTracker = startCoroutineTimer {
viewModel.updateTimerState()
}
},{
container?.onNewGameClick()
})
}
//…
}
ViewModel (AAC からではありません。私は独自の VM を作成します。しかし、Compose は、私が見る限り、AAC VM との相互運用性は既に良好です。) は、コンポーザブルを更新するために使用するコールバック関数への参照を公開しました:
class ActiveGameViewModel {
//…
internal var subTimerState: ((Long) -> Unit)? = null
internal var timerState: Long = 0L
//…
internal fun updateTimerState(){
timerState++
subTimerState?.invoke(timerState)
}
//…
}
ここからが重要な部分です! remember
などの構成の特定の機能を使用して、構成階層の再構成をトリガーできます。 関数:
var timerState by remember {
mutableStateOf(“”)
}
知っておく必要がある場合、これらの機能は、覚えているものの状態を slotTable
に保存します .つまり、ここでの状態という言葉は、データの現在の「状態」を意味し、最初は空の文字列です。
ここで失敗しました .シンプルなタイマー コンポーザブルを独自の関数 (適用された SOC) に取り込み、timerState
を渡していました。 そのコンポーザブルへのパラメーターとして。
ただし、上記のスニペットは、UI の最も複雑な部分のコンテナーであるタイマーの親コンポーザブルに置かれていました (9x9 数独には多数のウィジェットが必要です):
@Composable
fun GameContent(
onEventHandler: (ActiveGameEvent) -> Unit,
viewModel: ActiveGameViewModel
) {
Surface(
Modifier
.wrapContentHeight()
.fillMaxWidth()
) {
BoxWithConstraints(Modifier.background(MaterialTheme.colors.primary)) {
//…
ConstraintLayout {
val (board, timer, diff, inputs) = createRefs()
var isComplete by remember {
mutableStateOf(false)
}
var timerState by remember {
mutableStateOf("")
}
viewModel.subTimerState = {
timerState = it.toTime()
}
viewModel.subIsCompleteState = { isComplete = it }
//…Sudoku board
//Timer
Box(Modifier
.wrapContentSize()
.constrainAs(timer) {
top.linkTo(board.bottom)
start.linkTo(parent.start)
}
.padding(start = 16.dp))
{
TimerText(timerState)
}
//…difficulty display
//…Input buttons
}
}
}
}
@Composable
fun TimerText(timerState: String) {
Text(
text = timerState,
style = activeGameSubtitle.copy(color = MaterialTheme.colors.secondary)
)
}
これにより、かなりの遅延と無反応が発生していました。デバッガーを多用することで、その理由を突き止めることができました。 timerState
だから 変数が親 Composable 内で作成および更新された場合、UI のその部分全体の再構成がトリガーされていました。 毎回。独身。クリックしてください。
適切なコードを TimerText
に移動した後 構成可能で、物事は非常にスムーズに機能しました:
@Composable
fun TimerText(viewModel: ActiveGameViewModel) {
var timerState by remember {
mutableStateOf("")
}
viewModel.subTimerState = {
timerState = it.toTime()
}
Text(
text = timerState,
style = activeGameSubtitle.copy(color = MaterialTheme.colors.secondary)
)
}
うまくいけば、再構成の実用的な理解と、再構成を誤って実行する最大の方法の 1 つについて説明できたことを願っています。
不必要な再構成を避けることは、パフォーマンスにとって非常に重要です。そしてこれまでのところ、SOC を厳密に適用することは、別々のコンポーザブルで状態を記憶しておくという点でさえ、標準的な方法になるはずです.
リソースとサポート
この記事が気に入ったら、ソーシャル メディアで共有してください。また、こちらの freeCodeCamp に関する他の記事もチェックしてください。また、何百ものチュートリアルを含む YouTube チャンネルも持っており、さまざまなプラットフォームで活発なライターを務めています。
ソーシャル メディアで私とつながる
Instagram はこちら、Twitter はこちらです。
また、私が Jetpack Compose を使い始めるために使用した 1 つのリソースを指摘したいと思います:優れた開発者による実用的なコード サンプル。
-
Android 可視性リスナーを使用する方法と理由
Android UI は Views から構築され、通常のアプリケーションには通常いくつかあります。ユーザーが現在どのビューを見ているかを調べるには、Visibility Listeners をインストールする必要があります . ビューの可視性ステータスを識別するために必要なさまざまなオプションについては、以下をお読みください。 可視化する方法 リスナーが機能するためには、まず View がレイアウト階層にあることを確認する必要があります。これには 2 つの方法があります: ビューは XML ファイルで定義されているため、既にレイアウトの一部です View を動的に作成したため、addV
-
Kotlin Android アニメーションにアクセスできるようにする方法
史上初の Android コントリビューションの例を調査したとき、Kotlin で記述されたアニメーションの例はほとんどありませんでした。ネイティブ アニメーション内のアクセシビリティに関する考慮事項のコード例もほとんどありませんでした。 では、行きましょう! Kotlin でネイティブの「展開」アニメーションを作成する方法を見て、TalkBack や拡大テキストがオンになっている人を支援する方法について話しましょう。すべてのコードは、このサンプル リポジトリで利用でき、アニメーション ビューを含む単一のアクティビティを作成します。これが基づいているコードは、Calum Turner と共同