前回のコードラボに続き、今回は01.3
のコードラボを学習していきたいと思います。
前回の記事
学ぶこと
- アプリのリソースにファイルを追加する方法
- アプリのレイアウトで画像を使用する方法
- アプリのコードで View をより効率的に見つける方法
- XML のネームスペースを使用してアプリのデザインでプレースホルダー画像を使用する方法
- アプリの Android API レベルと、最小、ターゲット、およびコンパイル済みの API レベルを理解する方法
- アプリで
Jetpack
ライブラリを使用して、古いバージョンの Android をサポートする方法
やること
- 前回のコードラボの DiceRoller アプリを変更して、数値ではなくダイの値の画像を含めます。
- アプリのリソースに画像ファイルを追加します。
- 数値ではなくダイの値に画像を使用するようにアプリのレイアウトとコードを更新します。
- コードを更新して、View をより効率的に見つけます。
- アプリの起動時に空の画像を使用するようにコードを更新します。
- 古いバージョンの Android との下位互換性のために Android Jetpack ライブラリ を使用するようにアプリを更新します。
アプリの概要
今回は、前回のコードラボで作成した DiceRoller アプリに基づいてアプリを再構築し、サイコロを振ると変化する画像を追加します。最終的なアプリは下記のようなイメージになります。
画像リソースを追加および更新する👨💻
画像を追加する
アプリは、画像やアイコン、色、文字列、XML レイアウトなど、様々なリソースを使用します。これらのリソースは全て res
フォルダに保存されます。また、画像リソースなどは drawable
フォルダに保存します。drawable
フォルダー内にはすでに、アプリのランチャーアイコンリソースが存在しています。
ic_launcher_background.xml
をクリックします。これらはアイコンをベクター画像として記述する XML ファイルであることに注意してください。ベクトルを使用すると、画像をさまざまなサイズと解像度で描画できます。PNG
やGIF
などのビットマップ画像は、デバイスごとにスケーリングする必要があり、品質が低下する可能性があります。
=> Xcode とかだと Png イメージとかは 3サイズ用意して、ある程度の解像度を担保させることができるけど Android だとどんな感じでインポートできるんだろう🤔
- Android Studio で、現在 Android と表示されているプロジェクトビューの上部にあるドロップダウンメニューをクリックし、Project を選択します。
DiceRoller > app > src > main > res > drawable. でファイルを開きます。
ダウンロードした XML ファイルを
drawable
フォルダーにドラックアンドドロップし(フォルダーごとインポートしない)、OK をクリックします。プロジェクトを Android ビューに戻し、サイコロイメージの XML ファイルが drawable フォルダーにあることを確認します。
dice_1.xml
をクリックし、下記のように見えることを確認します。
=> Android は XML ファイルで定義した色や形をベクター画像として扱えるんですね🙃
画像を使用できるようにレイアウトを更新する
res/drawables
フォルダーにサイコロ画像ファイルができたので、アプリのレイアウトとコードからそれらのファイルにアクセスできます。このステップでは、数字を表示するために使用していた TextView
を ImageView
に置き換えて画像を表示できるようにします。
activity_main.xml
を開きます。<TextView>
要素を削除します。下記のような属性を持つ
<ImageView>
要素を追加します。
<ImageView android:id="@+id/dice_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:src="@drawable/dice_1" />
ImageView
を使用し、レイアウトに画像を表示します。ここで登場する新しい属性 android:src
では、画像のリソースを指定します。@drawable/dice_1
の場合は、システムが res/drawable
フォルダーの dice_1
ファイルを調べることを意味しています。
- レイアウトをプレビューすると下記のように表示されるようになります。
コードを更新する
MainActivity
を開いて、rollDice()
メソッドを見ると、R.id.result_text
が赤で強調表示されていることが確認できるかと思います。これはTextView
をレイアウトから削除し、その ID が参照できなくなっているためです。
private fun rollDice() { val randomInt = (1..6).random() val resultText: TextView = findViewById(R.id.result_text) resultText.text = randomInt.toString() }
TextView
を使用しなくなったので、resultText
変数に関するコードを削除します。findViewById()
を使い、Layout ID(R.id.dice_image
) からImageView
への参照を取得します。
var diceImage: ImageView = findViewById(R.id.dice_image)
when
ブロックを追加し、特定の数値に基づいたイメージを選択できるようにします。
val drawableResource = when (randomInt) { 1 -> R.drawable.dice_1 2 -> R.drawable.dice_2 3 -> R.drawable.dice_3 4 -> R.drawable.dice_4 5 -> R.drawable.dice_5 else -> R.drawable.dice_6 }
ID と同様に、R
クラスの値を使用して、drawable フォルダー内のサイコロ画像を参照できます。
ImageView
のsetImageResource()
メソッドでImageView
の画像を更新します。アプリをコンパイルして実行します。
Roll
ボタンをクリックすると適切な画像が更新されるのが確認できると思います。
View を効率的に見つける👨💻
このタスクでは、アプリをより効率的にする1つの方法について学びます。
MainActivity
を開き、rollDice()
メソッドの中のdiceImage
変数に注目してください。
val diceImage : ImageView = findViewById(R.id.dice_image)
この部分の処理では、rollDice()
メソッドが呼ばれるたびに findViewById()
で ImageView
への参照を取得します。findViewById()
は、システムが毎回 View の階層全体を検索するためコストがかかる操作です。そのため呼び出しは極力最小限に抑える必要があります。
これらは今回のような小さなアプリでは、大きな問題ではありませんが、より性能の低い Android デバイスなどを使用している場合にはアプリが遅れる原因になる可能性があります。そのため、findViewById()
を一回だけ呼び出して View
オブジェクトをフィールドに格納することがベストプラクティスです。ImageView
フィールドへの参照を保持することで、システムはいつでも View いアクセスができるようになり、パフォーマンスが向上します。
- クラスの上部で
onCreate()
メソッドの前に、ImageView
を保持するフィールドを作成します。
var diceImage : ImageView? = null
理想的には、この変数を宣言するとき、またはコンストラクターでこの変数を初期化しますが、Android Activity ではコンストラクターを使用しません。実際、レイアウト内の View は setContentView()
の呼び出しによって onCreate()
メソッドで拡張された後まで、メモリ内のアクセス可能なオブジェクトにはなりません。
1つの方法は、この例のように、変数を nullable として定義することです。これによって、onCreate()
時に findViewById()
で ImageView
を割り当てることができます。ただし、nullable のため使用する時に毎回値を確認する必要があるため、コードが複雑になります。下記でもっといい方法があります。
diceImage
をlateinit
を使用するように宣言を変更し、null
割り当てを削除します。
lateinit var diceImage : ImageView
この lateinit
キーワードはコードが変数に対する操作を呼び出す前に変数が初期化されることを Kotlin コンパイラーに約束します。したがって、null
で初期化する必要がなく、使用時に null
を許容しない変数として扱うことができます。このように、View を保持するフィールドで lateinit
を使用するのがベストプラクティスです。
=> Swift には無い表現ですね🤔 似てるとしたら force unwrap
での変数を定義するとかですかね🤔
- では、
onCreate()
メソッド内のsetContentView()
メソッドの後でfindViewById()
を使ってImageView
を取得します。
diceImage = findViewById(R.id.dice_image)
rollDice()
内で定義したImageView
を削除し、クラスで定義したdiceImage
を使用するに変更します。
val diceImage : ImageView = findViewById(R.id.dice_image)
- アプリを実行して、期待通りに動作することを確認します。
デフォルトの画像を使用する👨💻
現在は、dice_1
を初期イメージとして使用していますが、最初にサイコロを振るまでは画像をまったく表示したくないとします。これを実現するにはいくつか方法が存在します。
activity_main.xml
で Code タブをクリックします。<ImageView>
要素のandroid:src
属性に"@drawable/empty_dice"
をセットします。
android:src="@drawable/empty_dice"
この empty_dice
画像は、ダウンロードして drawable
フォルダーに追加した画像の1つです。これは、追加した他の画像と同じサイズですが、空です。この画像は、アプリが最初に起動された時に表示されるものになります。
Design
タブでプレビューを確認すると、空の画像がセットされている状態が確認できます。
activity_main.xml
で、android:src
行をコピーして、下記のようにtools
属性にペースとします。
android:src="@drawable/empty_dice" tools:src="@drawable/empty_dice" />
XML のネームスペースを android
から tools
に変更しました。プレビューや Android Studio のデザインエディタで使用されているプレースホルダのコンテンツを定義する場合、tools
ネームスペースが使用されます。tools
を使用する属性は、アプリをコンパイルすると削除されます。
=> これはプレビューでレイアウトをデバックする時にめちゃめちゃ便利ですね🥺 Xcode にはこのような機能は無いので(基本的にはプログラムで動的に入れ替えるしかない)、ぜひ取り入れて欲しい。
ネームスペースは、同じ名前の属性を参照する時の曖昧さを解決するために使用されます。例えば、<ImageView>
タグ内のこれらの属性は両方とも同じ名前(src
)を持っていますが、ネームスペースが異なります。
- レイアウトファイルのルートにある
<LinearLayout>
要素を調べ、ここで定義されている2つのネームスペースに注目してください。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" ...
ImageView
のtools:src
属性の値をdice_1
に変更してみてください。
android:src="@drawable/empty_dice" tools:src="@drawable/dice_1" />
プレビューのプレースホルダーが dice_1
になっていることに注目してください。
- アプリをコンパイルして実行します。実際のアプリでは、
roll
をクリックするまでダイのイメージは空です。
API レベルと互換性を理解する👨💻
Android 向けの開発の優れた点の1つは、Nexus One から Pixel、タブレットなどのフォームファクター、Pixelbooks、時計、テレビ、自動車まで、コードを実行できるデバイスの数が膨大であることです。
Android 用に作成する場合、これらの異なるデバイスごとに完全に別個のアプリを作成することはありません。溶けやテレビなどの根本的に異なるフォームファクターで実行されるアプリでもコードを共有することができます。ただし、これら全てをサポートするために注意する必要がある制約と互換性戦略があります。
このタスクでは、特定の Android API レベル(バージョン)をアプリのターゲットにする方法と、Android Jetpack ライブラリを使用して古いデバイスをサポートする方法を学びます。
API レベルを調べる
前回のコードラボでは、プロジェクトを作成する時に、アプリがサポートする必要がある特定の Android API レベルを指定しました。Android OS には、アルファベット順の美味しいおやつにちなんで名付けられたさまざまなバージョン番号があります。各 OS バージョンには、新しい機能が搭載されています。たとえば、Android Oreo はピクチャーインピクチャー がサポートされ、Android Pie は Slicesが導入されました。API レベルは、Android バージョンに対応しています。例えば、API 19 は Android4.4(KitKat)に対応しています。
ハードウェアがサポートできるもの、ユーザがデバイスを更新することを選択するかどうか、メーカーが異なる OS レベルをサポートするかどうかなど、多くの要因により、ユーザは必然的に異なる OS バージョンを実行するデバイスを使用します。
アプリプロジェクトを作成する時に、アプリがサポートする最小 API レベルを指定します。つまり、アプリがサポートする最も古い Android バージョンを指定します。アプリには、コンパイルするレベルと、ターゲットとするレベルもあります。これらの各レベルは、Gradle ビルドファイルの構成パラメーターです。
- Gradle Scripts の build.gradle(Module:app) ファイルを開きます。
このファイルは、アプリモジュールに固有のビルドパラメータと依存関係を定義します。build.gradle(Project: DiceRoller) ファイルは、プロジェクト全体のビルド・パラメータを定義します。多くの場合、アプリモジュールはプロジェクト内の唯一のモジュールであるため、この分割は恣意的に見える場合があります。ただ、アプリがより複雑になり、それをいくつかのパートに分割した場合やアプリが Android Watch などのプラットフォームをサポートしている場合、同じプロジェクトで頃なるモジュールに遭遇する可能性があります。
build.gradle
ファイルの上部にあるandroid
セクションを調べます。(以下のサンプルはセクション全体ではありませんが、このコードラボで最も関心のあるものが含まれています。)
android { compileSdkVersion 28 defaultConfig { applicationId "com.example.android.diceroller" minSdkVersion 19 targetSdkVersion 28 versionCode 1 versionName "1.0" }
compileSdkVersion
パラメータを調べます。
compileSdkVersion 28
このパラメーターは、Gradle がアプリのコンパイルに使用する Android API レベルを指定します。これは、アプリがサポートできる Android の最新バージョンです。つまり、アプリはこの API レベル以下に含まれる API 機能を使用できます。この場合、アプリは、Android9(Pie) に対応する API28 をサポートします。
defaultConfig
セクション内にあるtargetSdkVersion
パラメータを調べます。
targetSdkVersion 28
この値は、アプリをテストした最新の API です。多くの場合、これはcompileSdkVersion
と同じ値です。
minSdkVersion
パラメーターを調べます。
minSdkVersion 19
このパラメーターは、アプリが実行される Android の最も古いバージョンを決定するため、3つのパラメーターの中で最も重要です。この API レベルより古い Android OS を実行するデバイスは、アプリをまったく実行できません。
アプリの最小 API レベルを選択するのは難しい場合があります。APIレベルを低く設定しすぎると、Android OS の新しい機能を見逃してしまいます。高すぎる値に設定すると、アプリは新しいデバイスでのみ実行される可能性があります。
プロジェクトを設定し、アプリの最小 API レベルを定義する場所に到達したら、Help me choose をクリックし、API Version Distribution ダイアログを表示します。ダイアログには、さまざまな OS レベルを使用するデバイスの数、および OS レベルで追加または変更された機能に関する情報が表示されます。Android ドキュメントのリリースノートとダッシュボードをチェックして、さまざまな API レベルをサポートすることの影響に関する詳細情報を確認することもできます。
互換性を調べる
さまざま Android API レベル用に作成することは、アプリ開発者が直面する共通の課題であるため、Android フレームワークチーム はこれらの開発者を支援するために多くの作業を行ってきました。
2011年、チームは最初のサポートライブラリをリリースしました。これは、下位互換性のあるクラスと便利な関数を提供する Google が開発したライブラリです。2018年に、Google は Android Jetpack を発表しました。これは、サポートライブラリのクラスと関数を多く含み、サポートライブラリも拡張するライブラリコレクションです。
MainActivity
を開きます。MainActivity
クラスがActivity
クラス自体からではなく、AppCompatActivity()
から拡張されていることに注目してください。
class MainActivity : AppCompatActivity() {
...
}
AppCompatActivity
は、Activity が異なるプラットフォームの OS レベルで同じに見えるようにする互換性クラスです。
import
をクリックして、クラスのインポートを展開します。AppCompatActivity
クラスはandroidx.appcompat.app
パッケージからインポートされることに注意してください。Android Jetpack ライブラリの名前空間はandroidx
です。build.gradle(Module: app) を開き、依存関係セクションまでスクロールします。
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"" implementation 'androidx.appcompat:appcompat:1.0.0-beta01' implementation 'androidx.core:core-ktx:1.0.1' implementation 'androidx.constraintlayout:constraintlayout:1.1.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test:runner:1.1.0-alpha4' androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.0-alpha4' }
androidx
の一部であり、AppCompatActivity
クラスを含む appcompat
ライブラリへの依存関係に注意してください。
Vector Drawables の互換性を追加する
ネームスペース、Gradle、互換性に関する新しい知識を使用して、アプリに最後の調整を1つ加えます。これにより、古いプラットフォームでのアプリのサイズが最適化されます。
res/drawable
の中の dice イメージを1つクリックします。
すでに学んだように、全てのサイコロ画像は実際にはサイコロの色と形を定義する XML ファイルです。これらの種類のファイルは、Vector Drawables と呼ばれます。Vector Drawables が PNG などのビットマップイメージ形式よりも優れている点は、Vector Drawable が品質を損なうことなくスケーリングできることです。また、Vector Drawable は通常、ビットマップ形式の同じ画像よりもはるかに小さいファイルです。
Vector Drawable について注意すべき重要な点は、それらが API21以降でサポートされていることです。ただし、アプリの最小 SDK は API19 に設定されています。API19 デバイスまたはエミュレーターでアプリを試した場合、アプリは正常にビルドおよび実行されているように見えますが、これはどのように機能するのでしょうか。
アプリをビルドすると、Gradle ビルドプロセスによって各 Vector ファイルから PNG ファイルが生成され、それらの PNG ファイルは 21 未満の全ての Android デバイスで使用されます。これらの余分な PNG ファイルにより、アプリのサイズが大きくなります。不必要に大きいアプリはダウンロードが遅くなり、デバイスの限られたスペースをより多く使用します。大規模なアプリは、アンインストールされたり、ユーザがそれらのアプリのダウンロードに失敗したり、ダウンロードをキャンセルしたりする可能性も高くなります。
幸いなことに、API レベル 7までずっと Vector Drawable 用の AndroidX 互換ライブラリがあることです。
- build.gradle(Module: app) を開きます。
defaultConfig
セクションに次の行を追加します。
vectorDrawables.useSupportLibrary = true
Editor 上部の Sync Now ボタンをクリックします。
build.gradle
ファイルが変更されるたびに、ビルドファイルをプロジェクトと同期する必要があります。activity_main.xml
レイアウトファイルを開きます。このネームスペースを<LinearLayout>
タグのtools
ネームスペースの下に追加します。
xmlns:app="http://schemas.android.com/apk/res-auto"
app
のネームスペースは、カスタムコードまたはライブラリからの属性であり、コアの Android フレークワークではありません。
<ImageView>
のandroid:src
属性をapp:srcCompat
に変更します。
app:srcCompat="@drawable/empty_dice"
この app:srcCompat
属性は、AndroidX ライブラリを使用して、古いバージョンの Android Vector Drawable をサポートし、APIレベル7 に戻します。
- アプリをビルドして実行します。画面には何も表示されませんが、アプリはどこで実行されても、サイコロイメージに生成された PNG ファイルを使用する必要がないため、アプリファイルが小さくなります。
まとめ
アプリリソース
アプリのリソースには、画像とアイコン、アプリで使用される標準色、文字列、XML レイアウトを含めることができます。これらのリソースは全て
res
フォルダに保存されます。アプリの全ての画像リソースを
drawable
フォルダに置く必要があります。
ImageView で Vector Drawable を使用する
Vector Drawable は、XML形式で記述された画像です。Vector Drawable は、任意のサイズまたは解像度にスケリングできるため、ビットマップイメージ(PNGファイルなど)よりも柔軟性があります。
drawable をアプリのレイアウトに追加するには、
<ImageView>
要素を使用します。画像のそーすはandroid:src
属性にあります。リソースを参照するには@drawable
を使用します。(例:@drawable/image_name
)イメージを表示するために、
MainActivity
ではImageView
を使用します。setImageResource()
を使用して、View の画像を別のリソースに変更できます。(例:setImageResource(R.drawable.image_name)
)
lateinit
キーワード
lateinit
を使用することで、View を参照する際に Null ではないことを Kotlin コンパイラに約束します。また、それらの View はonCreate()
内のsetContentView()
後にfindViewById()
を使用して初期化する必要があります。
tools
設計時属性のネームスペース
<ImageView>
のtools:src
を使用して、画像をセットすると、Android Studio のプレビューまたは、デザインエディターのみに画像を表示します。その後、android:src
で実際にアプリに組み込む画像をセットします。tools
Android レイアウトファイルのネームスペースを使用して、Android Studio でのレイアウトのプレースホルダーコンテンツまたはヒントを作成します。tools
属性によって宣言されたデータは、最終的なアプリでは使用されません。
API レベル
各 Android OS には正式なバージョン番号と名前(Android9.0 Pie)とAPIレベル(API28) があります。アプリの Gradle ファイルの API レベルを使用して、アプリがサポートする Android のバージョンを示します。
ファイル内の
compileSdkVersion
パラメータは、build.gradle
がアプリのコンパイルに使用する Android API レベルを指定します。targetSdkVersion
パラメーターは。アプリをテストした最新の API レベルを指定します。多くの場合、このパラメーターの値はcompileSdkVersion
と同じ値になります。minSdkVersion
パラメーターは、アプリを実行できる最も古い API レベルを指定します。
Android Jetpack
Android Jetpack は、Google が開発したライブラリのコレクションであり、旧バージョンの Android をサポートするための下位互換性のあるクラスと便利な関数を提供します。Jetpack は、以前は Androidサポートライブラリと呼ばれていた一連のライブラリを置き換えて拡張します。
androidx
パッケージからインポートされたクラスは、Jetpack ライブラリを参照します。build.gradle
ファイルの Jetpack への依存関係もandroidx
で始まります。
Vector Drawable の下位互換性
Vector Drawable は、API21以降のバージョンの Android でのみサポートされています。古いバージョンでは、Gradleは、アプリのビルド時にこれらの Drawable の PNG 画像を生成します。
build.gradle
ファイルのvectorDrawables.useSupportLibrary = true
パラメーターを使用して、古い API バージョンの Vector Drawable に Android サポートライブラリを使用するように指定できます。Vector Drawable のサポートライブラリを有効にしたら、
<ImageView>
のandroid:src
属性はapp:srcCpmpat
に置き換えます。