app/src/main/java/com/example/firstapp/database/dao/CodeDao.kt
@@ -9,6 +9,7 @@ import com.example.firstapp.database.entity.Msg import com.example.firstapp.model.CourierStat import com.example.firstapp.model.DailyStat import com.example.firstapp.model.HeatmapStat import io.reactivex.Completable import kotlinx.coroutines.flow.Flow @@ -83,30 +84,28 @@ fun getCourierStatsByWeek(date: Long): Flow<List<CourierStat>> @Query(""" WITH RECURSIVE weeks(week_start, week_end) AS ( -- 从选定的日期开始计算周 SELECT date(datetime(:date/1000, 'unixepoch', 'localtime'), 'weekday 1', '-28 days') as week_start, date(datetime(:date/1000, 'unixepoch', 'localtime'), 'weekday 0', '-28 days') as week_end UNION ALL SELECT date(week_start, '+7 days'), date(week_end, '+7 days') FROM weeks WHERE date(week_start) <= date(datetime(:date/1000, 'unixepoch', 'localtime')) LIMIT 5 ) SELECT CAST(strftime('%W', week_start) AS INTEGER) as date, COUNT(code.id) as count, week_start as week_start FROM weeks LEFT JOIN code ON datetime(code.createtime) BETWEEN datetime(weeks.week_start) AND datetime(weeks.week_end, '+23 hours', '+59 minutes', '+59 seconds') GROUP BY weeks.week_start ORDER BY weeks.week_start ASC """) fun getDailyStatsByWeek(date: Long): Flow<List<DailyStat>> WITH RECURSIVE dates(date_value) AS ( -- 从当前日期往前3周的周一开始 SELECT date(datetime(:date/1000, 'unixepoch', 'localtime'), 'weekday 0', '-21 days') as date_value UNION ALL -- 每次加7天,直到后2周 SELECT date(date_value, '+7 days') FROM dates WHERE date_value < date(datetime(:date/1000, 'unixepoch', 'localtime'), 'weekday 0', '+14 days') ) SELECT strftime('%Y-%m-%d', date_value) as date, COUNT(c.id) as count, date_value as weekStart FROM dates d LEFT JOIN code c ON strftime('%Y-%m-%d', c.createtime) BETWEEN strftime('%Y-%m-%d', d.date_value) AND strftime('%Y-%m-%d', date(d.date_value, '+6 days')) GROUP BY d.date_value ORDER BY d.date_value ASC LIMIT :weekCount """) fun getWeeklyStats(date: Long, weekCount: Int): Flow<List<DailyStat>> @Query(""" SELECT * FROM code @@ -115,4 +114,119 @@ ORDER BY createtime DESC """) fun getPackagesByWeek(date: Long): Flow<List<Code>> @Query(""" SELECT type as courierName, COUNT(*) as count FROM code WHERE strftime('%Y-%m', substr(createtime, 1, 10)) = strftime('%Y-%m', datetime(:date/1000, 'unixepoch', 'localtime')) GROUP BY type ORDER BY count DESC """) fun getCourierStatsByMonth(date: Long): Flow<List<CourierStat>> @Query(""" SELECT type as courierName, COUNT(*) as count FROM code WHERE strftime('%Y', substr(createtime, 1, 10)) = strftime('%Y', datetime(:date/1000, 'unixepoch', 'localtime')) GROUP BY type ORDER BY count DESC """) fun getCourierStatsByYear(date: Long): Flow<List<CourierStat>> // @Query(""" // WITH RECURSIVE months(month_start) AS ( // SELECT date(datetime(:date/1000, 'unixepoch', 'localtime'), 'start of month', '-11 months') // UNION ALL // SELECT date(month_start, '+1 month') // FROM months // WHERE date(month_start) < date(datetime(:date/1000, 'unixepoch', 'localtime'), 'start of month') // ) // SELECT // strftime('%m', month_start) as date, // COUNT(code.id) as count, // month_start as week_start // FROM months // LEFT JOIN code ON strftime('%Y-%m', code.createtime) = strftime('%Y-%m', months.month_start) // GROUP BY months.month_start // ORDER BY months.month_start ASC // """) // fun getMonthlyStats(date: Long): Flow<List<DailyStat>> @Query(""" WITH RECURSIVE years(year_start) AS ( SELECT date(datetime(:date/1000, 'unixepoch', 'localtime'), 'start of year', '-4 years') UNION ALL SELECT date(year_start, '+1 year') FROM years WHERE date(year_start) < date(datetime(:date/1000, 'unixepoch', 'localtime'), 'start of year') ) SELECT strftime('%Y', year_start) as date, COUNT(code.id) as count, year_start as week_start FROM years LEFT JOIN code ON strftime('%Y', code.createtime) = strftime('%Y', years.year_start) GROUP BY years.year_start ORDER BY years.year_start ASC """) fun getYearlyStats(date: Long): Flow<List<DailyStat>> @Query(""" WITH RECURSIVE dates(date) AS ( SELECT date(datetime(:date/1000, 'unixepoch', 'localtime'), 'start of year') UNION ALL SELECT date(date, '+1 day') FROM dates WHERE date < date(datetime(:date/1000, 'unixepoch', 'localtime'), 'start of year', '+1 year', '-1 day') ) SELECT CAST(strftime('%w', dates.date) AS INTEGER) as dayOfWeek, CAST(strftime('%W', dates.date) AS INTEGER) as weekOfYear, COUNT(code.id) as count FROM dates LEFT JOIN code ON date(code.createtime) = dates.date GROUP BY dates.date ORDER BY dates.date """) fun getYearlyHeatmap(date: Long): Flow<List<HeatmapStat>> @Query(""" WITH RECURSIVE days(date) AS ( SELECT date(datetime(:date/1000, 'unixepoch', 'localtime'), 'start of month') UNION ALL SELECT date(date, '+1 day') FROM days WHERE date < date(datetime(:date/1000, 'unixepoch', 'localtime'), 'start of month', '+1 month', '-1 day') ) SELECT strftime('%d', days.date) || '' as date, -- 确保 date 不为空 COUNT(code.id) as count, days.date as week_start FROM days LEFT JOIN code ON date(code.createtime) = days.date GROUP BY days.date ORDER BY days.date ASC """) fun getMonthlyStats(date: Long): Flow<List<DailyStat>> @Query(""" WITH RECURSIVE months(month_start) AS ( SELECT date(datetime(:date/1000, 'unixepoch', 'localtime'), 'start of year') as month_start UNION ALL SELECT date(month_start, '+1 month') FROM months WHERE strftime('%m', month_start) < '12' ) SELECT strftime('%m', month_start) as date, COUNT(code.id) as count, month_start as weekStart FROM months LEFT JOIN code ON strftime('%Y-%m', code.createtime) = strftime('%Y-%m', months.month_start) GROUP BY months.month_start ORDER BY months.month_start ASC """) fun getYearMonthlyStats(date: Long): Flow<List<DailyStat>> } app/src/main/java/com/example/firstapp/database/repository/CodeRepository.kt
@@ -3,9 +3,7 @@ import androidx.annotation.WorkerThread import com.example.firstapp.database.dao.CodeDao import com.example.firstapp.database.entity.Code import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.withContext class CodeRepository(private val codeDao: CodeDao) { @@ -41,9 +39,19 @@ } } fun getCourierStats(date: Long) = codeDao.getCourierStatsByWeek(date) fun getCourierStats(date: Long, dateType: String) = when(dateType) { "WEEK" -> codeDao.getCourierStatsByWeek(date) "MONTH" -> codeDao.getCourierStatsByMonth(date) "YEAR" -> codeDao.getCourierStatsByYear(date) else -> codeDao.getCourierStatsByWeek(date) } fun getDailyStats(date: Long) = codeDao.getDailyStatsByWeek(date) // fun getStats(date: Long, dateType: String) = when(dateType) { // "WEEK" -> codeDao.getDailyStatsByWeek(date) // "MONTH" -> codeDao.getMonthlyStats(date) // "YEAR" -> codeDao.getYearlyStats(date) // else -> codeDao.getDailyStatsByWeek(date) // } @WorkerThread @@ -51,5 +59,15 @@ return codeDao.getNewPackagesByDay(date) } fun getYearlyHeatmap(date: Long) = codeDao.getYearlyHeatmap(date) fun getWeeklyStats(date: Long, weekCount: Int) = codeDao.getWeeklyStats(date, weekCount) fun getMonthlyStats(date: Long) = codeDao.getMonthlyStats(date) fun getYearlyStats(date: Long) = codeDao.getYearlyStats(date) fun getYearMonthlyStats(date: Long) = codeDao.getYearMonthlyStats(date) } app/src/main/java/com/example/firstapp/model/DailyStat.kt
@@ -13,5 +13,5 @@ data class DailyStat( val date: String, val count: Int, val week_start: String? = null val weekStart: Long? = null ) app/src/main/java/com/example/firstapp/model/HeatmapStat.kt
对比新文件 @@ -0,0 +1,7 @@ package com.example.firstapp.model data class HeatmapStat( val dayOfWeek: Int, // 0-6 (周日-周六) val weekOfYear: Int, // 0-51 val count: Int ) app/src/main/java/com/example/firstapp/ui/dashboard/DashboardFragment.kt
@@ -7,37 +7,33 @@ import android.view.ViewGroup import android.widget.TextView import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.fragment.app.viewModels import com.example.firstapp.databinding.FragmentDashboardBinding import com.google.android.material.tabs.TabLayout import androidx.recyclerview.widget.LinearLayoutManager import com.example.firstapp.adapter.PackageAdapter import com.example.firstapp.core.Core 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.IndexAxisValueFormatter import com.github.mikephil.charting.formatter.ValueFormatter import java.util.* import java.text.SimpleDateFormat import android.util.Log import android.graphics.Color import android.widget.GridLayout import com.example.firstapp.model.DailyStat class DashboardFragment : Fragment() { private var _binding: FragmentDashboardBinding? = null // This property is only valid between onCreateView and // onDestroyView. private val binding get() = _binding!! private val packageAdapter = PackageAdapter() private var currentDate = Calendar.getInstance() private var currentDateType = DateType.DAY private lateinit var barChart: BarChart private lateinit var pieChart: PieChart private lateinit var heatmapView: View enum class DateType { DAY, WEEK, MONTH, YEAR } @@ -55,11 +51,14 @@ override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) //渲染包裹列表 setupRecyclerView() //初始化tab内容和数据 setupTabLayout() //日期调整 setupDatePicker() setupView(view) updateDateDisplay() setupWeekView(view) loadPackages() } @@ -80,16 +79,9 @@ 3 -> DateType.YEAR else -> DateType.DAY } // 切换视图 binding.viewFlipperStats.displayedChild = when(currentDateType) { DateType.DAY -> 0 DateType.WEEK -> 1 else -> 0 } updateDateDisplay() //加载按天统计包裹数量和列表 updateCharts() loadPackages() observePackages() } override fun onTabUnselected(tab: TabLayout.Tab?) {} override fun onTabReselected(tab: TabLayout.Tab?) {} @@ -113,25 +105,48 @@ DateType.YEAR -> currentDate.add(Calendar.YEAR, amount) } updateDateDisplay() updateCharts() loadPackages() } private fun updateDateDisplay() { val dateFormat = when (currentDateType) { DateType.DAY -> "yyyy年MM月dd日" DateType.WEEK -> "yyyy年第ww周" DateType.WEEK -> { // 获取本周的起始和结束日期 val calendar = currentDate.clone() as Calendar calendar.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY) val startDate = SimpleDateFormat("MM月dd日", Locale.getDefault()).format(calendar.time) calendar.add(Calendar.DAY_OF_WEEK, 6) val endDate = SimpleDateFormat("MM月dd日", Locale.getDefault()).format(calendar.time) "$startDate-$endDate" } DateType.MONTH -> "yyyy年MM月" DateType.YEAR -> "yyyy年" } binding.textCurrentDate.text = SimpleDateFormat(dateFormat, Locale.getDefault()) .format(currentDate.time) if (currentDateType == DateType.WEEK) { binding.textCurrentDate.text = dateFormat } else { binding.textCurrentDate.text = SimpleDateFormat(dateFormat, Locale.getDefault()) .format(currentDate.time) } } private fun setupWeekView(view: View) { private fun setupView(view: View) { val weekStatsView = binding.layoutWeekStats.root barChart = weekStatsView.findViewById(R.id.chart_daily_packages) pieChart = weekStatsView.findViewById(R.id.chart_courier_distribution) heatmapView = weekStatsView.findViewById(R.id.heatmap_yearly) // 初始化时隐藏统计视图 weekStatsView.visibility = View.GONE setupBarChart() setupPieChart() setupHeatmap() updateCharts() } private fun setupBarChart() { barChart.apply { @@ -155,10 +170,15 @@ axisLeft.apply { setDrawGridLines(true) axisMinimum = 0f granularity = 1f granularity = 0.5f // 将刻度间隔设为0.5 valueFormatter = object : ValueFormatter() { override fun getFormattedValue(value: Float): String { return value.toInt().toString() // 只有整数时才显示标签 return if (value % 1 == 0f) { value.toInt().toString() } else { "" } } } } @@ -173,7 +193,17 @@ updateBarChartData() } private fun updateBarChartData() { viewModel.getDailyStats(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats -> val statsFlow = when (currentDateType) { DateType.WEEK -> { viewModel.getWeeklyStats(currentDate.timeInMillis, 6) } DateType.MONTH -> { viewModel.getYearMonthlyStats(currentDate.timeInMillis) } else -> return } statsFlow.observe(viewLifecycleOwner) { stats -> if (stats.isEmpty()) return@observe val entries = stats.mapIndexed { index, stat -> @@ -181,58 +211,77 @@ } val dataSet = BarDataSet(entries, "包裹数量") dataSet.color = resources.getColor(R.color.purple_500) dataSet.valueTextSize = 12f dataSet.apply { color = resources.getColor(R.color.purple_500) valueTextSize = 12f valueFormatter = object : ValueFormatter() { override fun getFormattedValue(value: Float): String { return value.toInt().toString() } } } val barData = BarData(dataSet) barChart.data = barData // 设置X轴标签 // 修改X轴标签显示 barChart.xAxis.apply { valueFormatter = object : ValueFormatter() { override fun getFormattedValue(value: Float): String { val position = value.toInt() if (position >= 0 && position < stats.size) { return "第${stats[position].date}周" return when(currentDateType) { DateType.WEEK -> { val weekStat = stats[position] val calendar = Calendar.getInstance() calendar.timeInMillis = weekStat.weekStart!! SimpleDateFormat("MM/dd", Locale.getDefault()).format(calendar.time) } DateType.MONTH -> { // 显示月份标签(1-12月) "${position + 1}月" } else -> "" } } return "" } } position = XAxis.XAxisPosition.BOTTOM setDrawGridLines(false) labelCount = stats.size granularity = 1f labelRotationAngle = -45f textSize = 10f setDrawGridLines(false) } // 调整图表显示 barChart.apply { setVisibleXRangeMaximum(5f) moveViewToX(0f) axisLeft.apply { axisMinimum = 0f granularity = 1f setDrawGridLines(true) } barData.barWidth = 0.6f description.isEnabled = false legend.isEnabled = false invalidate() // 高亮当前月份 if (currentDateType == DateType.MONTH) { val currentMonth = currentDate.get(Calendar.MONTH) dataSet.setColors(List(stats.size) { index -> if (index == currentMonth) resources.getColor(R.color.purple_500) else resources.getColor(R.color.purple_200) }) } else if (currentDateType == DateType.WEEK) { // 保持周视图的高亮逻辑 val highlightIndex = 3f dataSet.setColors(List(stats.size) { index -> if (index == 3) resources.getColor(R.color.purple_500) else resources.getColor(R.color.purple_200) }) } barChart.invalidate() } } private fun setupPieChart() { pieChart.apply { description.isEnabled = false setUsePercentValues(false) // 改为显示实际数量 setUsePercentValues(false) setDrawEntryLabels(false) // 增大饼图尺寸 setExtraOffsets(20f, 10f, 80f, 10f) minimumHeight = (resources.displayMetrics.density * 400).toInt() // 设置最小高度 // 调整饼图边距 setExtraOffsets(20f, 10f, 60f, 10f) // 配置图例 legend.apply { @@ -241,17 +290,24 @@ horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT orientation = Legend.LegendOrientation.VERTICAL setDrawInside(false) xEntrySpace = 7f yEntrySpace = 0f xEntrySpace = 10f yEntrySpace = 5f yOffset = 0f textSize = 12f // 增大图例文字大小 textSize = 14f } // 设置中心空白 holeRadius = 45f transparentCircleRadius = 50f } updatePieChartData() } private fun updatePieChartData() { viewModel.getCourierStats(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats -> viewModel.getCourierStats( currentDate.timeInMillis, currentDateType.name ).observe(viewLifecycleOwner) { stats -> val entries = stats.map { stat -> PieEntry(stat.count.toFloat(), "${stat.courierName}(${stat.count})") } @@ -281,36 +337,132 @@ } private fun loadPackages() { // 这里应该从数据库或网络加载数据 // 根据当前选择的日期类型传入对应参数 // val packages = when (currentDateType) { // DateType.DAY -> Core.code.getPackagesByDay(currentDate.timeInMillis) // DateType.WEEK -> Core.code.getPackagesByWeek(currentDate.timeInMillis) // DateType.MONTH -> Core.code.getPackagesByMonth(currentDate.timeInMillis) // DateType.YEAR -> Core.code.getPackagesByYear(currentDate.timeInMillis) // } val packages =Core.code.getPackagesByDay(currentDate.timeInMillis) packageAdapter.updatePackages(packages) binding.textPackageCount.text = "${packageAdapter.itemCount}个" } private fun observePackages() { viewModel.getPackages( currentDate.timeInMillis, currentDateType.name ).observe(viewLifecycleOwner) { packages -> when (currentDateType) { DateType.WEEK -> { // 更新图表数据 updateBarChartData() updatePieChartData() } else -> { packageAdapter.updatePackages(packages) } } packageAdapter.updatePackages(packages) binding.textPackageCount.text = "${packages.size}个" } } private fun setupHeatmap() { heatmapView.visibility = View.GONE } private fun updateHeatmapData() { 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 = 7 columnCount = 52 } // 添加日期标签 val dayLabels = arrayOf("周日", "周一", "周二", "周三", "周四", "周五", "周六") for (i in 0..6) { val label = TextView(context).apply { text = dayLabels[i] textSize = 10f setPadding(0, 0, 8, 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 = ViewGroup.LayoutParams( resources.getDimensionPixelSize(R.dimen.heatmap_cell_size), resources.getDimensionPixelSize(R.dimen.heatmap_cell_size) ) setBackgroundColor(getHeatmapColor(count)) setPadding(1, 1, 1, 1) } gridLayout.addView(cell) } } addView(gridLayout) } } } private fun getHeatmapColor(count: Int): Int { // 根据数量返回不同深浅的颜色 return when { count == 0 -> Color.parseColor("#EBEDF0") count <= 2 -> Color.parseColor("#9BE9A8") count <= 4 -> Color.parseColor("#40C463") count <= 6 -> Color.parseColor("#30A14E") else -> Color.parseColor("#216E39") } } private fun updateCharts() { when (currentDateType) { DateType.DAY -> { // 日视图显示包裹列表,隐藏统计图表 binding.recyclerPackages.visibility = View.VISIBLE binding.layoutWeekStats.root.visibility = View.GONE binding.layoutYearStats.root.visibility = View.GONE } DateType.WEEK, DateType.MONTH -> { // 周和月视图显示柱状图和饼图,隐藏包裹列表 binding.recyclerPackages.visibility = View.GONE binding.layoutWeekStats.root.visibility = View.VISIBLE binding.layoutYearStats.root.visibility = View.GONE binding.layoutWeekStats.chartDailyPackages.visibility = View.VISIBLE binding.layoutWeekStats.heatmapYearly.visibility = View.GONE updateBarChartData() updatePieChartData() } DateType.YEAR -> { // 年视图显示热力图和饼图,隐藏包裹列表和柱状图 binding.recyclerPackages.visibility = View.GONE binding.layoutWeekStats.root.visibility = View.VISIBLE binding.layoutYearStats.root.visibility = View.VISIBLE binding.layoutWeekStats.chartDailyPackages.visibility = View.GONE binding.layoutWeekStats.heatmapYearly.visibility = View.VISIBLE updateHeatmapData() updatePieChartData() } } } private fun updateYearlyStats() { viewModel.getYearlyStats(currentDate.timeInMillis).observe(viewLifecycleOwner) { stats: List<DailyStat> -> if (stats.isEmpty()) return@observe // 更新年度包裹总数 binding.layoutYearStats.textTotalPackages.text = "${stats.sumOf { it.count }}个" // 更新平均每天包裹数 val avgDaily = stats.sumOf { it.count }.toFloat() / 365 binding.layoutYearStats.textDailyAverage.text = String.format("%.2f", avgDaily) } } override fun onDestroyView() { super.onDestroyView() _binding = null app/src/main/java/com/example/firstapp/ui/dashboard/DashboardViewModel.kt
@@ -21,9 +21,20 @@ fun getPackages(date: Long, dateType: String) = repository.getPackages(date, dateType).asLiveData() fun getCourierStats(date: Long) = repository.getCourierStats(date).asLiveData() fun getCourierStats(date: Long, dateType: String) = repository.getCourierStats(date, dateType).asLiveData() fun getDailyStats(date: Long) = repository.getDailyStats(date).asLiveData() fun getYearlyHeatmap(date: Long) = repository.getYearlyHeatmap(date).asLiveData() fun getWeeklyStats(date: Long, weekCount: Int = 6) = repository.getWeeklyStats(date, weekCount).asLiveData() fun getMonthlyStats(date: Long) = repository.getMonthlyStats(date).asLiveData() fun getYearlyStats(date: Long) = repository.getYearlyStats(date).asLiveData() fun getYearMonthlyStats(date: Long) = repository.getYearMonthlyStats(date).asLiveData() fun insert(code: Code) = viewModelScope.launch { repository.insert(code) app/src/main/res/drawable/ic_average.xml
对比新文件 @@ -0,0 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:fillColor="#FF000000" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM9,17L7,17v-7h2v7zM13,17h-2L11,7h2v10zM17,17h-2v-4h2v4z"/> </vector> app/src/main/res/drawable/ic_package.xml
对比新文件 @@ -0,0 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:fillColor="#FF000000" android:pathData="M21,16.5c0,0.38 -0.21,0.71 -0.53,0.88l-7.9,4.44c-0.16,0.12 -0.36,0.18 -0.57,0.18s-0.41,-0.06 -0.57,-0.18l-7.9,-4.44A0.991,0.991 0,0 1,3 16.5L3,7.5c0,-0.38 0.21,-0.71 0.53,-0.88l7.9,-4.44c0.16,-0.12 0.36,-0.18 0.57,-0.18s0.41,0.06 0.57,0.18l7.9,4.44c0.32,0.17 0.53,0.5 0.53,0.88v9zM12,4.15L6.04,7.5 12,10.85l5.96,-3.35L12,4.15zM5,15.91l6,3.38v-6.71L5,9.21v6.7zM19,15.91v-6.7l-6,3.37v6.71l6,-3.38z"/> </vector> app/src/main/res/layout/fragment_dashboard.xml
@@ -101,15 +101,22 @@ </LinearLayout> </androidx.cardview.widget.CardView> <!-- 包裹列表 --> <!-- 年度统计布局 --> <include android:id="@+id/layout_year_stats" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" app:layout_constraintTop_toBottomOf="@id/card_package_stats" layout="@layout/layout_year_stats" /> <!-- 包裹列表和统计图表 --> <androidx.core.widget.NestedScrollView android:layout_width="match_parent" android:layout_height="0dp" android:fillViewport="true" android:clipToPadding="false" app:layout_constraintTop_toBottomOf="@id/card_package_stats" app:layout_constraintBottom_toBottomOf="parent" android:layout_marginBottom="56dp"> app:layout_constraintTop_toBottomOf="@id/layout_year_stats" app:layout_constraintBottom_toBottomOf="parent"> <ViewFlipper android:id="@+id/view_flipper_stats" app/src/main/res/layout/layout_week_stats.xml
@@ -4,7 +4,7 @@ android:layout_height="wrap_content" android:orientation="vertical"> <!-- 每日包裹数量柱状图 --> <!-- 柱状图容器 --> <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="280dp" @@ -19,10 +19,10 @@ android:padding="16dp" /> </androidx.cardview.widget.CardView> <!-- 快递公司占比饼图 --> <!-- 饼图容器 --> <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="280dp" android:layout_height="400dp" android:layout_marginHorizontal="16dp" android:layout_marginBottom="16dp"> @@ -32,4 +32,29 @@ android:layout_height="match_parent" android:padding="16dp" /> </androidx.cardview.widget.CardView> <!-- 热力图容器 --> <androidx.cardview.widget.CardView android:id="@+id/heatmap_yearly" android:layout_width="match_parent" android:layout_height="280dp" android:layout_marginHorizontal="16dp" android:layout_marginTop="8dp" android:layout_marginBottom="16dp" android:visibility="gone"> <HorizontalScrollView android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp"> <LinearLayout android:layout_width="wrap_content" android:layout_height="match_parent" android:orientation="horizontal"> <!-- 热力图将在这里动态添加 --> </LinearLayout> </HorizontalScrollView> </androidx.cardview.widget.CardView> </LinearLayout> app/src/main/res/layout/layout_year_stats.xml
对比新文件 @@ -0,0 +1,61 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="16dp"> <!-- 年度包裹总数 --> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center"> <ImageView android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/ic_package" /> <TextView android:id="@+id/text_total_packages" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="24sp" android:textStyle="bold" android:text="0个" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="年度包裹总数" /> </LinearLayout> <!-- 平均每天包裹数 --> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical" android:gravity="center"> <ImageView android:layout_width="48dp" android:layout_height="48dp" android:src="@drawable/ic_average" /> <TextView android:id="@+id/text_daily_average" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="24sp" android:textStyle="bold" android:text="0.00" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="平均每天包裹数" /> </LinearLayout> </LinearLayout> app/src/main/res/values/dimens.xml
@@ -4,4 +4,5 @@ <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="list_item_spacing">16dp</dimen> <dimen name="list_item_spacing_half">8dp</dimen> <dimen name="heatmap_cell_size">12dp</dimen> </resources>