|  |  |  | 
|---|
|  |  |  | package com.example.firstapp.ui.dashboard | 
|---|
|  |  |  |  | 
|---|
|  |  |  | import com.example.firstapp.R | 
|---|
|  |  |  | import android.graphics.Color | 
|---|
|  |  |  | import android.os.Bundle | 
|---|
|  |  |  | import android.view.Gravity | 
|---|
|  |  |  | import android.view.LayoutInflater | 
|---|
|  |  |  | import android.view.View | 
|---|
|  |  |  | import android.view.ViewGroup | 
|---|
|  |  |  | import android.webkit.WebView | 
|---|
|  |  |  | import android.widget.GridLayout | 
|---|
|  |  |  | import android.widget.HorizontalScrollView | 
|---|
|  |  |  | import android.widget.LinearLayout | 
|---|
|  |  |  | import android.widget.TextView | 
|---|
|  |  |  | import android.widget.Toast | 
|---|
|  |  |  | import androidx.cardview.widget.CardView | 
|---|
|  |  |  | import androidx.fragment.app.Fragment | 
|---|
|  |  |  | import androidx.fragment.app.viewModels | 
|---|
|  |  |  | import com.example.firstapp.databinding.FragmentDashboardBinding | 
|---|
|  |  |  | import com.google.android.material.tabs.TabLayout | 
|---|
|  |  |  | import androidx.lifecycle.lifecycleScope | 
|---|
|  |  |  | import androidx.recyclerview.widget.LinearLayoutManager | 
|---|
|  |  |  | import com.example.firstapp.R | 
|---|
|  |  |  | import com.example.firstapp.adapter.PackageAdapter | 
|---|
|  |  |  | import com.example.firstapp.database.response.UserInfo | 
|---|
|  |  |  | import com.example.firstapp.database.service.RetrofitClient | 
|---|
|  |  |  | import com.example.firstapp.databinding.FragmentDashboardBinding | 
|---|
|  |  |  | import com.example.firstapp.model.DailyStat | 
|---|
|  |  |  | import com.example.firstapp.utils.PreferencesManager | 
|---|
|  |  |  | import com.github.mikephil.charting.charts.BarChart | 
|---|
|  |  |  | import com.github.mikephil.charting.charts.PieChart | 
|---|
|  |  |  | import com.github.mikephil.charting.components.Legend | 
|---|
|  |  |  | import com.github.mikephil.charting.components.XAxis | 
|---|
|  |  |  | import com.github.mikephil.charting.data.* | 
|---|
|  |  |  | import com.github.mikephil.charting.formatter.ValueFormatter | 
|---|
|  |  |  | import java.util.* | 
|---|
|  |  |  | import java.text.SimpleDateFormat | 
|---|
|  |  |  | import android.graphics.Color | 
|---|
|  |  |  | import android.view.Gravity | 
|---|
|  |  |  | import android.widget.GridLayout | 
|---|
|  |  |  | import android.widget.LinearLayout | 
|---|
|  |  |  | import android.widget.Toast | 
|---|
|  |  |  | import androidx.cardview.widget.CardView | 
|---|
|  |  |  | import androidx.lifecycle.lifecycleScope | 
|---|
|  |  |  | import com.example.firstapp.database.response.UserInfo | 
|---|
|  |  |  | import com.example.firstapp.database.service.RetrofitClient | 
|---|
|  |  |  | import com.example.firstapp.model.DailyStat | 
|---|
|  |  |  | import com.example.firstapp.utils.PreferencesManager | 
|---|
|  |  |  | import com.google.android.material.tabs.TabLayout | 
|---|
|  |  |  | import kotlinx.coroutines.launch | 
|---|
|  |  |  | import java.text.SimpleDateFormat | 
|---|
|  |  |  | import java.util.* | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | class DashboardFragment : Fragment() { | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  | private val viewModel: DashboardViewModel by viewModels() | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | override fun onCreateView( | 
|---|
|  |  |  | inflater: LayoutInflater, | 
|---|
|  |  |  | container: ViewGroup?, | 
|---|
|  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | 
|---|
|  |  |  | super.onViewCreated(view, savedInstanceState) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | //        val webView: WebView = binding.layoutWeekStats.webView | 
|---|
|  |  |  | //        val webSettings = webView.settings | 
|---|
|  |  |  | //        webSettings.javaScriptEnabled = true // 启用 JavaScript | 
|---|
|  |  |  | // | 
|---|
|  |  |  | // | 
|---|
|  |  |  | //        // 加载本地的 HTML 文件 | 
|---|
|  |  |  | //        webView.loadUrl("file:///android_asset/calendar-heatmap.html") | 
|---|
|  |  |  |  | 
|---|
|  |  |  | //渲染包裹列表 | 
|---|
|  |  |  | setupRecyclerView() | 
|---|
|  |  |  | 
|---|
|  |  |  | heatmapView.visibility = View.GONE | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 用于创建图例 | 
|---|
|  |  |  | private fun createLegend(): LinearLayout { | 
|---|
|  |  |  | val legendLayout = LinearLayout(context).apply { | 
|---|
|  |  |  | orientation = LinearLayout.HORIZONTAL | 
|---|
|  |  |  | gravity = Gravity.END or Gravity.CENTER_VERTICAL | 
|---|
|  |  |  | setPadding(8, 16, 8, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val labelLow = TextView(context).apply { | 
|---|
|  |  |  | text = "少" | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(0, 0, 8, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | legendLayout.addView(labelLow) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val legendLevels = listOf(0, 5, 10, 15, 20) | 
|---|
|  |  |  | legendLevels.forEach { level -> | 
|---|
|  |  |  | val colorBox = View(context).apply { | 
|---|
|  |  |  | setBackgroundColor(getHeatmapColor(level)) | 
|---|
|  |  |  | val size = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) | 
|---|
|  |  |  | layoutParams = LinearLayout.LayoutParams(size, size).apply { | 
|---|
|  |  |  | marginEnd = 4 | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val label = TextView(context).apply { | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(0, 0, 8, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | legendLayout.addView(colorBox) | 
|---|
|  |  |  | legendLayout.addView(label) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val labelHigh = TextView(context).apply { | 
|---|
|  |  |  | text = "多" | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(8, 0, 0, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | legendLayout.addView(labelHigh) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | return legendLayout | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private fun updateHeatmapData() { | 
|---|
|  |  |  | viewModel.getYearlyHeatmap(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats -> | 
|---|
|  |  |  | if (stats.isEmpty()) return@observe | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val heatmapMatrix = Array(7) { IntArray(52) } | 
|---|
|  |  |  | stats.forEach { stat -> | 
|---|
|  |  |  | val week = stat.weekOfYear - 1 | 
|---|
|  |  |  | val dayOfWeek = stat.dayOfWeek - 1 | 
|---|
|  |  |  | if (week in 0..51 && dayOfWeek in 0..6) { | 
|---|
|  |  |  | heatmapMatrix[dayOfWeek][week] = stat.count | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | binding.layoutWeekStats.heatmapYearly.apply { | 
|---|
|  |  |  | removeAllViews() | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 外层父布局:水平 LinearLayout,左固定周标签,右为横向滚动热力图 | 
|---|
|  |  |  | val outerLayout = LinearLayout(context).apply { | 
|---|
|  |  |  | orientation = LinearLayout.HORIZONTAL | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 左侧星期标签列 | 
|---|
|  |  |  | val dayLabelLayout = LinearLayout(context).apply { | 
|---|
|  |  |  | orientation = LinearLayout.VERTICAL | 
|---|
|  |  |  | val dayLabels = arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日") | 
|---|
|  |  |  | // 顶部空白占位 | 
|---|
|  |  |  | addView(TextView(context).apply { | 
|---|
|  |  |  | text = "" | 
|---|
|  |  |  | textSize = 20f | 
|---|
|  |  |  | height = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) | 
|---|
|  |  |  | }) | 
|---|
|  |  |  | dayLabels.forEach { label -> | 
|---|
|  |  |  | val textView = TextView(context).apply { | 
|---|
|  |  |  | text = label | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | height = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) | 
|---|
|  |  |  | setPadding(4, 0, 4, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | addView(textView) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 右侧滚动部分 | 
|---|
|  |  |  | val scrollView = HorizontalScrollView(context).apply { | 
|---|
|  |  |  | isHorizontalScrollBarEnabled = false | 
|---|
|  |  |  | layoutParams = LinearLayout.LayoutParams( | 
|---|
|  |  |  | ViewGroup.LayoutParams.MATCH_PARENT, | 
|---|
|  |  |  | ViewGroup.LayoutParams.WRAP_CONTENT | 
|---|
|  |  |  | ) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val scrollContentLayout = LinearLayout(context).apply { | 
|---|
|  |  |  | orientation = LinearLayout.VERTICAL | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val gridLayout = GridLayout(context).apply { | 
|---|
|  |  |  | rowCount = 8 | 
|---|
|  |  |  | columnCount = 52 | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 添加月份标签(估算) | 
|---|
|  |  |  | val months = arrayOf( | 
|---|
|  |  |  | "1月", "2月", "3月", "4月", "5月", "6月", | 
|---|
|  |  |  | "7月", "8月", "9月", "10月", "11月", "12月" | 
|---|
|  |  |  | ) | 
|---|
|  |  |  | /* months.forEachIndexed { index, month -> | 
|---|
|  |  |  | val label = TextView(context).apply { | 
|---|
|  |  |  | text = month | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(10, 0, 8, 4) | 
|---|
|  |  |  | val weekPosition = (index * 4.3).toInt() | 
|---|
|  |  |  | layoutParams = GridLayout.LayoutParams().apply { | 
|---|
|  |  |  | columnSpec = GridLayout.spec(weekPosition) | 
|---|
|  |  |  | rowSpec = GridLayout.spec(0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | gridLayout.addView(label) | 
|---|
|  |  |  | }*/ | 
|---|
|  |  |  | months.forEachIndexed { index, month -> | 
|---|
|  |  |  | val label = TextView(context).apply { | 
|---|
|  |  |  | text = month | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(10, 0, 8, 4) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val weekPosition = (index * 4.3).toInt() | 
|---|
|  |  |  | val span = 4  // 控制跨列范围 | 
|---|
|  |  |  | layoutParams = GridLayout.LayoutParams().apply { | 
|---|
|  |  |  | columnSpec = GridLayout.spec(weekPosition, span, GridLayout.CENTER) | 
|---|
|  |  |  | rowSpec = GridLayout.spec(0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | gridLayout.addView(label) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 添加热力格子 | 
|---|
|  |  |  | for (day in 0..6) { | 
|---|
|  |  |  | for (week in 0..51) { | 
|---|
|  |  |  | val count = heatmapMatrix[day][week] | 
|---|
|  |  |  | val cell = View(context).apply { | 
|---|
|  |  |  | layoutParams = GridLayout.LayoutParams().apply { | 
|---|
|  |  |  | width = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) | 
|---|
|  |  |  | height = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) | 
|---|
|  |  |  | columnSpec = GridLayout.spec(week) | 
|---|
|  |  |  | rowSpec = GridLayout.spec(day + 1) | 
|---|
|  |  |  | setMargins(1, 1, 1, 1) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | setBackgroundColor(getHeatmapColor(count)) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | gridLayout.addView(cell) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | scrollContentLayout.addView(gridLayout) | 
|---|
|  |  |  | scrollView.addView(scrollContentLayout) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 添加两个主要部分到外层布局 | 
|---|
|  |  |  | outerLayout.addView(dayLabelLayout) | 
|---|
|  |  |  | outerLayout.addView(scrollView) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 图例 | 
|---|
|  |  |  | val legendLayout = LinearLayout(context).apply { | 
|---|
|  |  |  | orientation = LinearLayout.HORIZONTAL | 
|---|
|  |  |  | gravity = Gravity.END or Gravity.CENTER_VERTICAL | 
|---|
|  |  |  | setPadding(8, 16, 8, 0) | 
|---|
|  |  |  | layoutParams = LinearLayout.LayoutParams( | 
|---|
|  |  |  | ViewGroup.LayoutParams.MATCH_PARENT, | 
|---|
|  |  |  | ViewGroup.LayoutParams.WRAP_CONTENT | 
|---|
|  |  |  | ) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val labelLow = TextView(context).apply { | 
|---|
|  |  |  | text = "少" | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(0, 0, 8, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | legendLayout.addView(labelLow) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val legendLevels = listOf(0, 5, 10, 15, 20) | 
|---|
|  |  |  | legendLevels.forEach { level -> | 
|---|
|  |  |  | val colorBox = View(context).apply { | 
|---|
|  |  |  | setBackgroundColor(getHeatmapColor(level)) | 
|---|
|  |  |  | val size = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) | 
|---|
|  |  |  | layoutParams = LinearLayout.LayoutParams(size, size).apply { | 
|---|
|  |  |  | marginEnd = 4 | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val label = TextView(context).apply { | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(0, 0, 8, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | legendLayout.addView(colorBox) | 
|---|
|  |  |  | legendLayout.addView(label) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val labelHigh = TextView(context).apply { | 
|---|
|  |  |  | text = "多" | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(8, 0, 0, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | legendLayout.addView(labelHigh) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 总容器垂直布局:热力图 + 图例 | 
|---|
|  |  |  | val container = LinearLayout(context).apply { | 
|---|
|  |  |  | orientation = LinearLayout.VERTICAL | 
|---|
|  |  |  | addView(outerLayout) | 
|---|
|  |  |  | addView(legendLayout) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | //                addView(container) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 设置外部 margin | 
|---|
|  |  |  | val params = LinearLayout.LayoutParams( | 
|---|
|  |  |  | LinearLayout.LayoutParams.MATCH_PARENT, | 
|---|
|  |  |  | LinearLayout.LayoutParams.WRAP_CONTENT | 
|---|
|  |  |  | ).apply { | 
|---|
|  |  |  | setMargins(16, 16, 16, 16)  // 左、上、右、下的 margin,单位是像素 | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 添加到父布局并应用 margin | 
|---|
|  |  |  | addView(container, params) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private fun updateHeatmapData_a() { | 
|---|
|  |  |  | viewModel.getYearlyHeatmap(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats -> | 
|---|
|  |  |  | if (stats.isEmpty()) return@observe | 
|---|
|  |  |  |  | 
|---|
|  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 添加月份标签 | 
|---|
|  |  |  | val months = arrayOf("1月", "2月", "3月", "4月", "5月", "6月", | 
|---|
|  |  |  | "7月", "8月", "9月", "10月", "11月", "12月") | 
|---|
|  |  |  | val months = arrayOf( | 
|---|
|  |  |  | "1月", "2月", "3月", "4月", "5月", "6月", | 
|---|
|  |  |  | "7月", "8月", "9月", "10月", "11月", "12月" | 
|---|
|  |  |  | ) | 
|---|
|  |  |  | months.forEachIndexed { index, month -> | 
|---|
|  |  |  | val label = TextView(context).apply { | 
|---|
|  |  |  | text = month | 
|---|
|  |  |  | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private fun updateHeatmapData_aaa() { | 
|---|
|  |  |  | viewModel.getYearlyHeatmap(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats -> | 
|---|
|  |  |  | if (stats.isEmpty()) return@observe | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 创建52周x7天的数据矩阵 | 
|---|
|  |  |  | val heatmapMatrix = Array(7) { IntArray(52) } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 填充数据 | 
|---|
|  |  |  | stats.forEach { stat -> | 
|---|
|  |  |  | val week = stat.weekOfYear - 1 // 0-51 | 
|---|
|  |  |  | val dayOfWeek = stat.dayOfWeek - 1 // 0-6 | 
|---|
|  |  |  | if (week in 0..51 && dayOfWeek in 0..6) { | 
|---|
|  |  |  | heatmapMatrix[dayOfWeek][week] = stat.count | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 更新UI | 
|---|
|  |  |  | binding.layoutWeekStats.heatmapYearly.apply { | 
|---|
|  |  |  | removeAllViews() | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val gridLayout = GridLayout(context).apply { | 
|---|
|  |  |  | rowCount = 8 // 1行月份 + 7行星期 | 
|---|
|  |  |  | columnCount = 53 // 1列星期 + 52列周数 | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 添加月份标签 | 
|---|
|  |  |  | val months = arrayOf("1月", "2月", "3月", "4月", "5月", "6月", | 
|---|
|  |  |  | "7月", "8月", "9月", "10月", "11月", "12月") | 
|---|
|  |  |  | months.forEachIndexed { index, month -> | 
|---|
|  |  |  | val label = TextView(context).apply { | 
|---|
|  |  |  | text = month | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(0, 0, 8, 4) | 
|---|
|  |  |  | val weekPosition = (index * 4.3).toInt() // 估算月份起始位置 | 
|---|
|  |  |  | layoutParams = GridLayout.LayoutParams().apply { | 
|---|
|  |  |  | columnSpec = GridLayout.spec(weekPosition + 1) | 
|---|
|  |  |  | rowSpec = GridLayout.spec(0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | gridLayout.addView(label) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 添加星期标签 | 
|---|
|  |  |  | val dayLabels = arrayOf("周一", "周二", "周三", "周四", "周五", "周六", "周日") | 
|---|
|  |  |  | dayLabels.forEachIndexed { index, label -> | 
|---|
|  |  |  | val textView = TextView(context).apply { | 
|---|
|  |  |  | text = label | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(4, 0, 8, 0) | 
|---|
|  |  |  | layoutParams = GridLayout.LayoutParams().apply { | 
|---|
|  |  |  | columnSpec = GridLayout.spec(0) | 
|---|
|  |  |  | rowSpec = GridLayout.spec(index + 1) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | gridLayout.addView(textView) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 添加热力图单元格 | 
|---|
|  |  |  | for (day in 0..6) { | 
|---|
|  |  |  | for (week in 0..51) { | 
|---|
|  |  |  | val count = heatmapMatrix[day][week] | 
|---|
|  |  |  | val cell = View(context).apply { | 
|---|
|  |  |  | layoutParams = GridLayout.LayoutParams().apply { | 
|---|
|  |  |  | width = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) | 
|---|
|  |  |  | height = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) | 
|---|
|  |  |  | columnSpec = GridLayout.spec(week + 1) | 
|---|
|  |  |  | rowSpec = GridLayout.spec(day + 1) | 
|---|
|  |  |  | setMargins(1, 1, 1, 1) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | setBackgroundColor(getHeatmapColor(count)) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | gridLayout.addView(cell) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // ✅ 把 gridLayout 包裹进 HorizontalScrollView | 
|---|
|  |  |  | val scrollView = HorizontalScrollView(context).apply { | 
|---|
|  |  |  | layoutParams = LinearLayout.LayoutParams( | 
|---|
|  |  |  | ViewGroup.LayoutParams.MATCH_PARENT, | 
|---|
|  |  |  | ViewGroup.LayoutParams.WRAP_CONTENT | 
|---|
|  |  |  | ) | 
|---|
|  |  |  | isHorizontalScrollBarEnabled = true | 
|---|
|  |  |  | } | 
|---|
|  |  |  | scrollView.addView(gridLayout) | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 创建图例(Legend) | 
|---|
|  |  |  | val legendLayout = LinearLayout(context).apply { | 
|---|
|  |  |  | orientation = LinearLayout.HORIZONTAL | 
|---|
|  |  |  | gravity = Gravity.END or Gravity.CENTER_VERTICAL // 靠右对齐 | 
|---|
|  |  |  | setPadding(8, 16, 8, 0) | 
|---|
|  |  |  | layoutParams = LinearLayout.LayoutParams( | 
|---|
|  |  |  | ViewGroup.LayoutParams.MATCH_PARENT, | 
|---|
|  |  |  | ViewGroup.LayoutParams.WRAP_CONTENT | 
|---|
|  |  |  | ) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 左侧“少”标签 | 
|---|
|  |  |  | val labelLow = TextView(context).apply { | 
|---|
|  |  |  | text = "少" | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(0, 0, 8, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | legendLayout.addView(labelLow) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 渐变色块 + 数值 | 
|---|
|  |  |  | val legendLevels = listOf(0, 5, 10, 15, 20) | 
|---|
|  |  |  | legendLevels.forEach { level -> | 
|---|
|  |  |  | val colorBox = View(context).apply { | 
|---|
|  |  |  | setBackgroundColor(getHeatmapColor(level)) | 
|---|
|  |  |  | val size = resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) | 
|---|
|  |  |  | layoutParams = LinearLayout.LayoutParams(size, size).apply { | 
|---|
|  |  |  | marginEnd = 4 | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | val label = TextView(context).apply { | 
|---|
|  |  |  | //                        text = "$level" | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(0, 0, 8, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | legendLayout.addView(colorBox) | 
|---|
|  |  |  | legendLayout.addView(label) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 右侧“多”标签 | 
|---|
|  |  |  | val labelHigh = TextView(context).apply { | 
|---|
|  |  |  | text = "多" | 
|---|
|  |  |  | textSize = 10f | 
|---|
|  |  |  | setPadding(8, 0, 0, 0) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | legendLayout.addView(labelHigh) | 
|---|
|  |  |  |  | 
|---|
|  |  |  | // 垂直组合 gridLayout + legendLayout | 
|---|
|  |  |  | val container = LinearLayout(context).apply { | 
|---|
|  |  |  | orientation = LinearLayout.VERTICAL | 
|---|
|  |  |  | addView(scrollView) | 
|---|
|  |  |  | addView(legendLayout) | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | addView(container) | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  | } | 
|---|
|  |  |  |  | 
|---|
|  |  |  | private fun getHeatmapColor(count: Int): Int { | 
|---|
|  |  |  | return when { | 
|---|
|  |  |  | count == 0 -> Color.parseColor("#EBECF1") // 最淡 | 
|---|