Jetpack Composeを使ってアプリのスタイルとテーマを設定する方法
この記事では、Jetpack Composeを使ってAndroidアプリのスタイルとテーマを設定する方法を解説します。
Composeは、従来のXMLベースのViewシステムに代わる新しいUIフレームワークです(デスクトップやWeb向けのサポートも開発中)。執筆時点ではまだベータ版ですが、安定版リリースに向けてこの部分が劇的に変わることはないと予想しています。
この記事で扱うトピックは以下のとおりです。
- XMLアプローチのおさらい
- XMLベースの色・テーマ・タイポグラフィ(フォント)システムから移行する方法
- わずか数行のコードでライトテーマとダークテーマを実装する方法
- Kotlinで定義したスタイル情報をコンポーザブルで活用する方法
- 特にTextコンポーザブルをスタイリングする方法
読み進める前に、コンポーザブル(composable)とは何かを理解しておくことが重要です。ここではその概念の説明は省きますので、別途学習しておいてください。
XMLリソースでAndroidアプリのスタイルを設定していた頃
いつものように、まずはこれらのトピックに至った背景と少しの歴史を共有したいと思います。興味がなければ、実践的な内容に入る次のセクションまでスキップして構いません。
Androidリソースシステムについて
Androidのアプリリソースシステムについては、Androidチームに拍手を送りたいところです。しかし、あらゆる設計判断に言えることですが、ある状況で長所となる機能が、別の状況では短所になります。
具体的に言えば、プラットフォーム開発者にとってもアプリ開発者にとっても最大の課題の一つが、私がローカライズされたリソースと呼ぶものの実現です。つまり、以下のようなアプリを作る難しさのことです。
- さまざまな言語や文字体系でテキストやグラフィックを表示する
- 多種多様なフォームファクター(画面サイズ、密度など)に対して、見た目のバランスを保つ
これはほんの一例にすぎず、他にもたくさんあります。リソースシステムは、アプリ開発者がローカライズされたリソースを提供し、プラットフォーム側がコンパイル時に適切なものを選択できる場を提供してくれます。おかげで、私たちは定型コードを自前で書く必要がありません。
長所か、それとも短所か?
ローカライズされた文字列リソースのための定型コードを自分で管理したくはないですが、だからといってXMLを書くのが好きなわけでもありません。
実際、KotlinやSwiftのようなモダンで慣用的、そしてエレガントな言語よりもXMLで書きたいことはごくわずかしかありません。個人的な好みはさておき、XMLリソースが必ずしも理想的でない技術的な理由があります。
なお、これはプラットフォーム開発者やエンジニアへの批判ではありません。設計判断には常にメリットとコストが伴うという、単なる観察です。
JVMベースのアプリケーションコードにXMLベースのリソースを統合するためには、必然的に翻訳レイヤー(コンパイル)とプラットフォームブリッジ(API)が必要になります。これはプラットフォーム開発者とアプリ開発者の双方にとって課題となります。
私が実際に遭遇したよくある問題は次の2つです。
- リソースにアクセスしたい場所で、そのリソースを提供するプラットフォームAPIとの密結合を避けたい
- Viewの見た目を変えるためだけに(つまり、リソースのスタイルやテーマで定義されたものをオーバーライドするために)、馬鹿げたほど多くの定型コードを書かなければならない
関係者全員にとっての根本的な問題は、ViewシステムとAndroidリソースシステム(この2つ自体も密結合しています)への密結合です。
プラットフォーム開発者にとっては、巨大で古いコードベースの上に構築したり、それを回避したりしなければならないことを意味します。さらに新機能を古いAndroid OSバージョンでも動作させようとすれば、報われない仕事になるのは明らかです。
一方、私たちアプリ開発者にとっては、大量の定型コードと、直感的にはワンライナーで済みそうな処理のための厄介な回避策が結果として残ります。しかも、これらのリソースを取得する主要なAPIはContextであり、メモリリークを起こしたくないクラスとして悪名高い存在です。
そこで登場するのがJetpack Composeです。
Jetpack Composeでテーマ・カラー・フォントを設定する方法
旧システムのおさらいは終わったので、より美しくシンプルな方法でAndroidアプリのスタイルとテーマを設定する方法を見ていきましょう。実践的な内容を心がけると言いましたが、一点だけ許してください。
今回の作業はすべてKotlinで行います。これは非常に重要な意味を持ちます。私たちもプラットフォーム開発者も、XMLとJVMの間の翻訳(コンパイル)やAPIブリッジ(AndroidのRクラスやContext)に縛られることが大幅に減るのです。
簡単に言えば、定型コードが大幅に減り、ランタイムでの制御性が大幅に向上するということです。
実践パートでは、私が説明する順番どおりに進めることをおすすめします。新しいアプリでこのコードを書く際に私自身が従っている順序で構成しています。
colors.xmlリソースをKotlin Composeに置き換える方法
まだアプリのカラースキームを決めていない場合は、公式のMaterial Designサイトで利用可能な各種リソースを活用することをおすすめします。試してみてください。
- カラーパレット
- カラーツール
ライトテーマとダークテーマの両方をサポートする予定なら(後ほど説明します)、白いテキストに対応するカラースキームと黒いテキストに対応するカラースキームを選ぶようにしましょう。

Color.kt のような名前のファイルを作成し(名前は何でも構いません)、不変のval値で埋めていきます。
import androidx.compose.ui.graphics.Color
val primaryGreen = Color(0XFF00bc00)
val primaryCharcoal = Color(0xFF2b2b2b)
val accentAmber = Color(0xFFffe400)
val textColorLight = Color(0xDCFFFFFF)
val textColorDark = Color(0xFFf3f3f3)
val gridLineColorLight = Color.Black
//...Color.Blackのような定義済みの値を使うこともできますし、独自のARGBヘックス値を指定することもできます。
ARGBヘックスは何やら専門用語の羅列なので、「0XFF00bc00」の意味を翻訳してみましょう。
- 最初の2文字
0xは、コンパイラにこれが16進数であることを伝えます - 次の2文字(
FFやDC)は、16進数での透明度・不透明度、つまりAlpha(アルファ値)を表します - 残りの6桁のペアはRed(赤)、Green(緑)、Blue(青)を表します
フォントの追加とfontFamily属性の置き換え
タイポグラフィ(フォント)も非常に簡単に管理できます。こここそ、Kotlinのデフォルト引数が威力を発揮する場面です。
Type.kt のような名前のファイルを作成し(名前は相変わらず何でも構いません)。Typographyクラスを作ります。
val typography = Typography(
body1 = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp
),
button = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Bold,
fontSize = 32.sp
),
caption = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 12.sp
)
)
//...さらに、いくつかのTextStyleクラスも定義します。
//...
val mainTitle = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Light,
fontSize = 48.sp,
textAlign = TextAlign.Center
)
fun dropdownText(color: Color) = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 32.sp,
textAlign = TextAlign.Center,
color = color
)
//...公開関数にするか公開値にするか(ここでvarを使うのはおすすめしません)は、個人の好みと現在の要件次第です。
Jetpack Composeでアプリテーマを作成する方法
コンポーザブル内でテーマを使用する前に設定すべき最後の項目は、MaterialTheme @Composableです。私はライト用とダーク用のカラーパレットとともに、GraphSudokuTheme というファイルにまとめています。
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.MaterialTheme
import androidx.compose.material.darkColors
import androidx.compose.material.lightColors
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
private val LightColorPalette = lightColors(
primary = primaryGreen,
secondary = textColorLight,
surface = lightGrey,
primaryVariant = gridLineColorLight,
onPrimary = accentAmber,
onSurface = accentAmber
)
private val DarkColorPalette = darkColors(
//メインの背景色
primary = primaryCharcoal,
//テキストの色に使用
secondary = textColorDark,
//数独盤面の背景
surface = lightGreyAlpha,
//数独盤面のグリッド線
primaryVariant = gridLineColorLight,
onPrimary = accentAmber,
onSurface = accentAmber
)
@Composable
fun GraphSudokuTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
MaterialTheme(
colors = if (darkTheme) DarkColorPalette else LightColorPalette,
typography = typography,
shapes = shapes,
content = content
)
}
コンポーザブルとは何かはすでにご存じでしょう(事前に警告しましたね)。ここで初めて登場するのは darkTheme: Boolean = isSystemInDarkTheme() だけです。
簡潔に説明すると、isSystemInDarkTheme() は、互換性のあるAndroid端末に対して、ユーザーがライトテーマとダークテーマのどちらを好むかを問い合わせる呼び出しです。
これはブール値を返すので、colors = if (darkTheme) DarkColorPalette else LightColorPalette のような三項(条件)代入式の中で利用できます。
以上で終わりです。数分でカラー、フォント、そして2種類のテーマを定義できました。
Composeでテーマを使用する方法
次は、このテーマをアプリで実際に使ってみましょう。このアプリには主要な画面が2つしかないため、私はActivityをコンポーザブルのコンテナとして使用しています。
class NewGameActivity : AppCompatActivity(), NewGameContainer {
//...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//...
setContent {
GraphSudokuTheme {
NewGameScreen(
onEventHandler = logic::onEvent,
viewModel
)
}
}
//...
}setContent {} を呼び出す場面では、初心者の方にはすぐにテーマのコンポーザブルをその内側に配置することをおすすめします。そうすることで、スタイル情報がネストされた各コンポーザブルへカスケード(継承)されます。
これで完成です!…と言いたいところですが、もう少しだけ続きます。
スタイルとテーマの上書き方法
可能であれば、必要になりそうな色はすべてライト用とダーク用のパレットに含めておきましょう。そうすれば、MaterialTheme.colors.<Color> を呼び出したときに、適切なパレットを選択するための条件分岐ロジックをシステムが自動的に処理してくれます。
@Composable
fun NewGameContent(
onEventHandler: (NewGameEvent) -> Unit,
viewModel: NewGameViewModel
) {
Surface(
Modifier
.wrapContentHeight()
.fillMaxWidth()
) {
ConstraintLayout(Modifier.background(MaterialTheme.colors.primary)) {
//...
}
//...
}
}
ただし、場合によっては独自の条件分岐ロジックを書いたほうが適切なこともあります…あるいは単に手を抜きたくなるときもあるかもしれません。幸い、Composeはこうした設定の多くをプロパティとして公開しています。
@Composable
fun DoneIcon(onEventHandler: (NewGameEvent) -> Unit) {
Icon(
imageVector = Icons.Filled.Done,
tint = if (MaterialTheme.colors.isLight) textColorLight
else textColorDark,
contentDescription = null,
modifier = Modifier
.clickable(
//...
)
)
}MaterialTheme.colors.isLight は現在のモードを示すブール値を返すので、そこからさらに三項代入式を使えます。
Textコンポーザブルのスタイリング方法
style 引数に、あなたが定義したテキストスタイル(MaterialTheme由来でもType.kt内のスタイルでも可)を設定するだけです。
Text(
text = stat.toTime(),
style = statsLabel.copy(
color = if (isZero) Color.White
else MaterialTheme.colors.onPrimary,
fontWeight = FontWeight.Normal
),
modifier = Modifier
.wrapContentSize()
.padding(end = 2.dp, bottom = 4.dp),
textAlign = TextAlign.End
)TextStyleにはcopy関数が標準で用意されているので、何かを上書きしたいときにも簡単に対応できます。
以上です!これであなたも、Jetpack Composeを使ってアプリのスタイルとテーマを設定できるようになりました。お読みいただきありがとうございました :)
SNS
InstagramとTwitterでも活動していますので、ぜひフォローしてください。
チュートリアル&講座のご案内
https://youtube.com/wiseass https://www.freecodecamp.org/news/author/ryan-michael-kay/ https://skl.sh/35IdKsj(Android Studioを使ったAndroid入門)
-
AndroidスマホにMicrosoft Teamsをインストール&セットアップする完全ガイド
新型コロナウイルスのパンデミックは、私たちの働き方に大きな変化をもたらしました。そのひとつが、テレワークの普及に伴うオンラインコミュニケーションツールの急速な浸透です。これにより、Microsoftの主力コミュニケーションアプリであるTeamsの利用者数は爆発的に増加しました。2019年には約2,200万人だったユーザー数は、現在では2億7,000万人を超えるまでに成長しています。 需要の急増を受け、Teamsはさまざまなオペレーティングシステムに対応したクロスプラットフォームアプリとして提供されています。本記事では、AndroidデバイスにTeamsアプリをインストールしてセットアップする方
-
AndroidにMicrosoft Edgeをインストールして使いこなす方法
Microsoftの「Edge」は、標準ブラウザとして快適に使える機能が満載です。すでにサポートが終了したInternet Explorerとは比べものにならないほど完成度の高いブラウザです。Edgeがお気に入りの方なら、スマートフォンでも同じ体験を楽しめるようにするのがおすすめです。この記事では、AndroidにEdgeブラウザをインストールする手順と、その後の便利な使い方について詳しく解説します。AndroidへのEdgeのインストール手順Android版Edgeは、GoogleやFacebook、Microsoftなどが共同開発したブラウザエンジン「Blink」をベースにしており、PC版