【習慣トラッカー開発記 #10】データの可視化をするホーム画面の設計について

はじめに

前回の記事では、カテゴリー選択画面の実装と画面遷移とデータ連携について紹介しました。

今回は、アプリのメイン画面である「ホーム画面」の実装についてです。

前回はカテゴリー機能についてでしたが、今回はそのカテゴリーに紐づいたデータを「どう見せるか」にこだわりました。ただ記録するだけでなく、自分の努力がひと目でわかる画面をグラフをお用いて表現しました。

1. 「一瞬で状況がわかる」3つのエリア構成

ホーム画面は、ユーザーが直感的に状況を把握できるよう、大きく3つのセクションに分けて構成しました。

  1. 集計サマリー(Summary Period Contents)
    「今週どれくらい頑張ったか」を、合計時間と達成回数で表示します。
  2. カテゴリ別内訳(Category Break Down Contents)
    ドーナツチャートを使って、どのカテゴリに時間を使っているかを視覚的に表現しました。
  3. カテゴリ別詳細(CategoryDetailsContents)
    各カテゴリーごとの具体的な活動時間をグリッド形式で並べています。

2. ドーナツチャートでのデータ可視化

今回は、co.yml.charts ライブラリを使用して、リッチなドーナツチャートを実装しました。

特に工夫した点は、「データが空のときの処理」です。記録がゼロのときにグラフが消えてしまうと寂しいため、ダミーのスライスを表示して「これから記録を埋めていく楽しさ」を感じてもらえるようにしました。

// データが空の場合のプレースホルダー処理
val slices = remember(data.categoryDetails) {
    if (isEmpty) {
        listOf(PieChartData.Slice("データなし", 1f, Color.LightGray.copy(alpha = 0.3f)))
    } else {
        // ...実際のデータ表示
    }
}

3. UI/UXの細かな配慮

期間切り替えのシームレスな体験

TimeRange という enum クラスを定義し、ViewModel で状態を管理することで、タブを切り替えた瞬間に集計範囲が変わるようにしました。

/**
 * データ集計期間
 */
enum class TimeRange(val label: String) {
    THIS_WEEK("今週"),
    THIS_MONTH("今月"),
    THIS_YEAR("今年"),
    ALL_TIME("全期間")
}

「色」による直感的なフィードバック

カテゴリーごとに固定の色(CategoryColors)を割り当てています。これにより、ユーザーは何度もアプリを開くうちに「赤が見えるから今週は食事関連を頑張っているな」と、文字を読まずとも直感的に状況を判断できるようになります。

/**
 * カテゴリIDに基づいて固定の色を返す
 */
private fun getCategoryColorById(categoryId: Int): Color {
    return when (categoryId) {
        1 -> CategoryColors.Sports
        2 -> CategoryColors.Mental
        // ... (省略)
        else -> CategoryColors.Other
    }
}

詳細情報のアクセシビリティ

画面の複雑さを抑えるため、詳細な数値は「カテゴリーをクリックした際のモーダル」に集約しました。画面遷移をさせないことで、ユーザーの操作テンポを損ないません。

4. ViewModelでのデータ整形

Repositoryから取得した生のデータを、UIで使いやすい形に map して HomeUiState に流し込んでいます。

val uiState: StateFlow<HomeUiState> = selectedTimeRange
    .flatMapLatest { timeRange ->
        homeRepository.getPeriodData(timeRange)
    }
    .map { habitSummary ->
        // 大カテゴリごとにグループ化して、表示用の詳細データを作成
        val uiDetails = habitSummary.categoryDetails
            .groupBy { it.categoryId }
            .map { (categoryId, subCategories) ->
                // UI用のデータモデルに変換
                HabitStatsData.CategoryStatsDetail(...)
            }.sortedByDescending { it.minutes }

        HomeUiState.Success(statsData)
    }

おわりに

ホーム画面の役割は、単なる記録の表示に留まりません。ここで意識したことは、「開くたびに自己肯定感が上がる場所」です。 可視化された「積み上げ」は、モチベーションになると思います。

次回は、本連載の最終回です。 開発の総括とともに、制作過程で得た気づきなどを深堀りする予定です。

タイトルとURLをコピーしました