  
- package com.example.mynumset
- import android.app.Activity
- import android.content.Context
- import android.os.Bundle
- import android.util.Log
- import androidx.activity.ComponentActivity
- import androidx.activity.compose.setContent
- import androidx.activity.enableEdgeToEdge
- import androidx.compose.foundation.background
- import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
- import androidx.compose.foundation.layout.*
- import androidx.compose.foundation.lazy.LazyColumn
- import androidx.compose.foundation.lazy.rememberLazyListState
- import androidx.compose.foundation.shape.RoundedCornerShape
- import androidx.compose.material3.*
- import androidx.compose.runtime.*
- import androidx.compose.ui.Alignment
- import androidx.compose.ui.Modifier
- import androidx.compose.ui.platform.LocalDensity
- import androidx.compose.ui.text.font.FontWeight
- import androidx.compose.ui.text.style.TextAlign
- import androidx.compose.ui.unit.dp
- import androidx.compose.ui.unit.sp
- import com.example.mynumset.ui.theme.MyNumSetTheme
- import kotlinx.coroutines.launch
- import androidx.datastore.core.DataStore
- import androidx.datastore.preferences.core.Preferences
- import androidx.datastore.preferences.core.edit
- import androidx.datastore.preferences.core.intPreferencesKey
- import androidx.datastore.preferences.preferencesDataStore
- import androidx.compose.ui.graphics.Color
- import androidx.compose.ui.platform.LocalContext
- import kotlinx.coroutines.Dispatchers
- import kotlinx.coroutines.flow.first
- import androidx.compose.foundation.layout.fillMaxSize
- import androidx.compose.foundation.clickable
- import java.time.LocalDateTime
- import java.time.format.DateTimeFormatter
- import java.util.*
- import androidx.compose.material3.ExperimentalMaterial3Api
- import androidx.compose.ui.window.Dialog
- import java.time.Instant
- import java.time.LocalDate
- import java.time.LocalTime
- import java.time.ZoneId
- import android.content.ContentValues
- import android.database.sqlite.SQLiteDatabase
- import android.database.sqlite.SQLiteOpenHelper
- import android.view.ViewGroup
- import android.widget.Toast
- import androidx.activity.compose.BackHandler
- import androidx.compose.runtime.saveable.rememberSaveable
- import kotlinx.coroutines.withContext
- import com.github.mikephil.charting.charts.LineChart
- import com.github.mikephil.charting.components.XAxis
- import com.github.mikephil.charting.data.Entry
- import com.github.mikephil.charting.data.LineData
- import com.github.mikephil.charting.data.LineDataSet
- import com.github.mikephil.charting.formatter.ValueFormatter
- import androidx.compose.ui.viewinterop.AndroidView
- import com.github.mikephil.charting.components.Legend
- import com.github.mikephil.charting.components.LimitLine
- import androidx.compose.ui.graphics.Brush
- import androidx.compose.ui.geometry.Offset
- import androidx.compose.ui.graphics.Shadow
- import androidx.compose.ui.res.painterResource
- // 在原有类中添加数据库帮助类(放在MainActivity类外)
- class RecordDbHelper(context: Context) : SQLiteOpenHelper(
- context, DATABASE_NAME, null, DATABASE_VERSION
- ) {
- init {
- // 添加连接池配置
- setWriteAheadLoggingEnabled(true)
- writableDatabase.enableWriteAheadLogging()
- }
- companion object {
- // 定义数据库的名称和版本
- private const val DATABASE_NAME = "health_records.db"
- private const val DATABASE_VERSION = 1
- }
- // 创建数据库表
- override fun onCreate(db: SQLiteDatabase) {
- // 执行SQL语句创建records表
- db.execSQL("""
- CREATE TABLE records (
- date TEXT NOT NULL,
- time TEXT NOT NULL,
- low INTEGER NOT NULL,
- heart_rate INTEGER NOT NULL,
- status TEXT NOT NULL,
- PRIMARY KEY (date, time)
- )
- """)
- }
- // 升级数据库
- override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
- // 如果存在records表,则删除
- db.execSQL("DROP TABLE IF EXISTS records")
- // 重新创建数据库表
- onCreate(db)
- }
- }
- // 定义一个数据类 HealthRecord,用于存储健康记录信息
- data class HealthRecord(
- // 日期字段,存储记录的日期,类型为 String
- val date: String,
- // 时间字段,存储记录的时间,类型为 String
- val time: String,
- // 高血压字段,存储高压值,类型为 Int
- val high: Int,
- // 低压字段,存储低压值,类型为 Int
- val low: Int,
- // 心率字段,存储心率值,类型为 Int
- val heartRate: Int,
- // 状态字段,存储健康状态描述,类型为 String
- val status: String
- )
- class MainActivity : ComponentActivity() {
- // 原色
- val originalGreen = Color(0xFFE8F5E9)
- // 加深方案(任选其一)
- val deepGreen1 = Color(0xFFC8E6C9) // 浅灰绿(比原色深10%)
- val deepGreen2 = Color(0xFFA5D6A7) // 中等青绿(Material Teal 200)
- val deepGreen3 = Color(0xFF80CBC4) // 深青蓝色(Material Teal 300)
- val gradientColors = listOf(deepGreen2, deepGreen3)
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- enableEdgeToEdge()
- setContent {
- MyNumSetTheme {
- BottomNavigationExample()
- }
- }
- }
- }
- @Composable
- fun NumberPicker(
- modifier: Modifier = Modifier,
- initialValue: Int = 0,
- range: IntRange = 20..200,
- onValueChange: (Int) -> Unit
- ) {
- val itemHeight = 50.dp
- val startOffset = (initialValue - range.first).coerceIn(0 until range.count())
- //Log.d("initialValue=", "读取成功: $initialValue")
- val listState = rememberLazyListState(initialFirstVisibleItemIndex = startOffset)
- val coroutineScope = rememberCoroutineScope()
- val itemHeightPx = with(LocalDensity.current) { itemHeight.toPx() }
- // 实时计算当前选中项索引
- val selectedIndex by remember {
- derivedStateOf {
- val offset = listState.firstVisibleItemScrollOffset
- val index = listState.firstVisibleItemIndex
- if (offset > itemHeightPx / 2) index + 1 else index
- }
- }
- // 当滚动停止时,吸附到中间项并回调
- LaunchedEffect(selectedIndex, listState.isScrollInProgress) {
- if (!listState.isScrollInProgress) {
- coroutineScope.launch {
- listState.animateScrollToItem(selectedIndex)
- }
- onValueChange((range.first + selectedIndex))
- }
- }
- LazyColumn(
- state = listState,
- modifier = modifier
- .height(itemHeight * 3)
- .width(100.dp),
- horizontalAlignment = Alignment.CenterHorizontally,
- contentPadding = PaddingValues(vertical = itemHeight),
- flingBehavior = rememberSnapFlingBehavior(lazyListState = listState)
- ) {
- items(range.count()) { index ->
- val value = range.first + index
- val isSelected = selectedIndex == index
- Text(
- text = value.toString(),
- fontSize = if (isSelected) 32.sp else 20.sp,
- fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Normal,
- color = if (isSelected) Color.Black else Color.Gray,
- modifier = Modifier
- .height(itemHeight)
- .fillMaxWidth(),
- textAlign = TextAlign.Center
- )
- }
- }
- }
- // 定义 DataStore 的扩展属性,用于访问应用的设置数据存储
- val Context.settingsDataStore: DataStore<Preferences> by preferencesDataStore(name = "my_settings")
- // PreferencesKeys 是一个单例对象,用于定义 DataStore 的键
- private object PreferencesKeys {
- val NumbGy = intPreferencesKey("saved_gaoya")
- val NumbDy = intPreferencesKey("saved_diya")
- val NumbXl = intPreferencesKey("saved_xinlv")
- }
- @Composable
- fun NumberPickerDemo(dbHelper: RecordDbHelper) {
- val context = LocalContext.current
- val scope = rememberCoroutineScope() // 新增协程作用域
- //val dbHelper = remember { RecordDbHelper(context) }
- val coroutineScope = rememberCoroutineScope()
- var currentStatusText by remember { mutableStateOf("") }
- var currentStatusColor by remember { mutableStateOf(Color.Black) }
- var lastClickTime by remember { mutableStateOf(0L) }
- // 添加两个新状态
- var selectedNumberGy by remember { mutableStateOf<Int?>(null) }
- var selectedNumberDy by remember { mutableStateOf<Int?>(null) }
- var selectedNumberXl by remember { mutableStateOf<Int?>(null) }
- // 记住是否显示日期选择器的状态
- var showDatePicker by remember { mutableStateOf(false) }
- // 记住是否显示时间选择器的状态
- var showTimePicker by remember { mutableStateOf(false) }
- // 记住当前选中的日期和时间
- var selectedDateTime by remember { mutableStateOf(LocalDateTime.now()) }
- // 日期格式化器
- val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.getDefault())
- // 时间格式化器
- val timeFormatter = DateTimeFormatter.ofPattern("HH:mm", Locale.getDefault())
- LaunchedEffect(Unit) {
- try {
- val preferences = context.settingsDataStore.data.first()
- selectedNumberGy = preferences[PreferencesKeys.NumbGy] ?: 100
- selectedNumberDy = preferences[PreferencesKeys.NumbDy] ?: 80
- selectedNumberXl = preferences[PreferencesKeys.NumbXl] ?: 75
- }
- catch (e: Exception) {
- Log.e("NumberPickerDemo", "读取设置失败", e)
- }
- }
- Column(
- modifier = Modifier.fillMaxSize(),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- // 显示血压状态
- selectedNumberGy?.let { gy ->
- selectedNumberDy?.let { dy ->
- val (statusText, statusColor) = run {
- // 分别判断高压和低压的等级
- val gyLevel = when {
- gy < 90 -> 1
- gy in 90 until 120 -> 2
- gy in 120 until 140 -> 3
- gy in 140 until 160 -> 4
- gy in 160 until 180 -> 5
- gy >= 180 -> 6
- else -> 0
- }
- val dyLevel = when {
- dy < 60 -> 1
- dy in 60 until 80 -> 2
- dy in 80 until 90 -> 3
- dy in 90 until 100 -> 4
- dy in 100 until 110 -> 5
- dy >= 110 -> 6
- else -> 0
- }
- // 取最高等级作为最终结果
- when (maxOf(gyLevel, dyLevel)) {
- 1 -> "低血压" to Color.Blue
- 2 -> "正常血压" to Color(0xFF006400)
- 3 -> "正常高值血压" to Color(0xFFFFB300)
- 4 -> "1级高血压" to Color(0xFFFF6700)
- 5 -> "2级高血压" to Color.Red
- 6 -> "3级高血压" to Color.Red
- else -> "****" to Color.Black
- }
- }.also {
- currentStatusText = it.first
- currentStatusColor = it.second
- }
- Card(
- modifier = Modifier
- .padding(top = 50.dp, bottom = 50.dp)
- .size(width = 350.dp, height = 140.dp)
- .background(
- brush = Brush.verticalGradient(
- colors = listOf(
- Color.White.copy(alpha = 0.3f),
- Color.Transparent
- ),
- startY = 0f,
- endY = 350f
- ),
- shape = RoundedCornerShape(24.dp)
- ),
- shape = RoundedCornerShape(24.dp),
- elevation = CardDefaults.cardElevation(4.dp)
- ) {
- val density = LocalDensity.current
- Text(
- text = statusText,
- fontSize = 50.sp,
- color = statusColor,
- style = LocalTextStyle.current.copy(
- shadow = with(density) {
- Shadow(
- color = Color.Black.copy(alpha = 0.3f),
- offset = Offset(2.dp.toPx(), 2.dp.toPx()),
- blurRadius = 4f
- )
- }
- ),
- fontWeight = FontWeight.Bold,
- modifier = Modifier
- .fillMaxSize()
- .padding(24.dp),
- textAlign = TextAlign.Center
- )
- }
- }
- } ?: run {
- CircularProgressIndicator()
- }
- selectedNumberGy?.let { gy ->
- selectedNumberDy?.let { dy ->
- selectedNumberXl?.let { xl ->
- // 横向排列三个选择器
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceEvenly
- ) {
- // 高压选择器
- NumberPickerComponent(
- value = gy,
- label = "收缩压",
- range = 80..200,
- onSave = { value ->
- coroutineScope.launch {
- //delay(300)
- withContext(Dispatchers.Main) {
- selectedNumberGy = value
- }
- withContext(Dispatchers.IO) {
- context.settingsDataStore.edit { settings ->
- settings[PreferencesKeys.NumbGy] = value
- Log.d("NumberPickerDemo", "保存高压设置:$value")
- }
- }
- }
- }
- )
- // 低压选择器
- NumberPickerComponent(
- value = dy,
- label = "舒张压",
- range = 50..150,
- onSave = { value ->
- coroutineScope.launch {
- //delay(300)
- withContext(Dispatchers.Main) {
- selectedNumberDy = value
- }
- withContext(Dispatchers.IO) {
- context.settingsDataStore.edit { settings ->
- settings[PreferencesKeys.NumbDy] = value
- Log.d("NumberPickerDemo", "保存舒张压设置:$value")
- }
- }
- }
- }
- )
- // 心率选择器
- NumberPickerComponent(
- value = xl,
- label = "心率",
- range = 40..200,
- onSave = { value ->
- coroutineScope.launch {
- //delay(300)
- withContext(Dispatchers.Main) {
- selectedNumberXl = value
- }
- withContext(Dispatchers.IO) {
- context.settingsDataStore.edit { settings ->
- settings[PreferencesKeys.NumbXl] = value
- Log.d("NumberPickerDemo", "保存心率设置:$value")
- }
- }
- }
- }
- )
- }
- val saveLock = remember { Any() } // 添加同步锁对象
- Button(
- onClick = {
- val now = System.currentTimeMillis()
- if (now - lastClickTime > 1000) {
- val date = selectedDateTime.format(dateFormatter)
- val time = selectedDateTime.format(timeFormatter)
- val high = selectedNumberGy ?: return@Button
- val low = selectedNumberDy ?: return@Button
- val heartRate = selectedNumberXl ?: return@Button
- val status = currentStatusText
- lastClickTime = now
- synchronized(saveLock) {
- scope.launch(Dispatchers.IO){
- try {
- dbHelper.writableDatabase.use { db ->
- val values = ContentValues().apply {
- put("date", date)
- put("time", time)
- put("high", high)
- put("low", low)
- put("heart_rate", heartRate)
- put("status", status)
- }
- db.insertWithOnConflict(
- "records",
- null,
- values,
- SQLiteDatabase.CONFLICT_REPLACE
- )
- }
- withContext(Dispatchers.Main) {
- //delay(300)
- Toast.makeText(context, "记录保存成功", Toast.LENGTH_SHORT).show()
- }
- } catch (e: Exception) {
- Log.e("Database", "保存失败", e)
- }
- }
- }
- }
- },
- modifier = Modifier
- .padding(vertical = 8.dp)
- .width(200.dp) // 加长按钮
- .height(48.dp), // 增加高度
- shape = RoundedCornerShape(8.dp),
- colors = ButtonDefaults.buttonColors(
- containerColor = Color(0xFF006400), // 使用深绿色配色
- contentColor = Color.White
- )
- ) {
- Text(
- "记 录",
- fontSize = 22.sp, // 加大字号
- fontWeight = FontWeight.Medium
- )
- }
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp)
- .background(Color(0xFFE0F2F1), RoundedCornerShape(8.dp))
- .padding(16.dp)
- ) {
- Text(
- text = when (currentStatusText) {
- "低血压" -> "收缩压<90,舒张压<60 ,一般无需治疗,若出现头晕、乏力等症状,建议就医。"
- "正常血压" -> "90≤收缩压<120,60≤舒张压<80 ,无需治疗,保持健康生活方式(如低盐饮食、适量运动、戒烟限酒)。"
- "正常高值血压" -> "120≤收缩压<140,80≤舒张压<90 ,注意饮食、运动和定期监测血压。建议生活方式干预,必要时咨询医生。"
- "1级高血压" -> "140≤收缩压<160,90≤舒张压<100 ,改变生活方式(如减少盐摄入、增加运动),可能需要药物治疗。"
- "2级高血压" -> "160≤收缩压<180,100≤舒张压<110 ,需要药物治疗并结合生活方式干预(如饮食调整、运动、减重)。"
- "3级高血压" -> "180≤收缩压,110≤舒张压, 需紧急医疗干预,可能需要住院治疗。通常需要联合药物治疗。"
- else -> ""
- },
- color = Color.Black,
- fontSize = 20.sp
- )
- }
- }
- }
- } ?: run {
- CircularProgressIndicator()
- }
- // 显示日期和时间选择器
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.SpaceEvenly
- ){
- Text(
- text = "日期: ${selectedDateTime.format(dateFormatter)}",
- style = MaterialTheme.typography.bodyLarge,
- fontSize = 20.sp, // 增大字体尺寸
- //fontWeight = FontWeight.Bold, // 加粗字体
- color = Color.DarkGray, // 使用更深的颜色
- modifier = Modifier
- .clickable { showDatePicker = true }
- .padding(8.dp)
- )
- Text(
- text = "时间: ${selectedDateTime.format(timeFormatter)}",
- style = MaterialTheme.typography.bodyLarge,
- fontSize = 20.sp, // 增大字体尺寸
- //fontWeight = FontWeight.Bold, // 加粗字体
- color = Color.DarkGray, // 使用更深的颜色
- modifier = Modifier
- .clickable { showTimePicker = true }
- .padding(8.dp)
- )
- // 日期选择器
- if (showDatePicker) {
- DatePickerDialog(
- onDismissRequest = { showDatePicker = false }, // 点击外部时关闭日期选择器
- onDateSelected = { date ->
- // 更新选中的日期
- selectedDateTime = selectedDateTime.withYear(date.year).withMonth(date.monthValue).withDayOfMonth(date.dayOfMonth)
- showDatePicker = false // 关闭日期选择器
- }
- )
- }
- // 时间选择器
- if (showTimePicker) {
- TimePickerDialog(
- onDismissRequest = { showTimePicker = false }, // 点击外部时关闭时间选择器
- onTimeSelected = { time ->
- // 更新选中的时间
- selectedDateTime = selectedDateTime.withHour(time.hour).withMinute(time.minute)
- showTimePicker = false // 关闭时间选择器
- }
- )
- }
- }
- }
- }
- @Composable
- private fun NumberPickerComponent(
- value: Int,
- label: String,
- range: IntRange,
- onSave: (Int) -> Unit
- ) {
- var currentValue by remember(value) { mutableStateOf(value) } // 添加当前值状态
- Column(horizontalAlignment = Alignment.CenterHorizontally) {
- Text(label, fontSize = 16.sp)
- NumberPicker(
- initialValue = value,
- range = range,
- onValueChange = { newValue ->
- if (newValue != currentValue) { // 只在数值变化时触发保存
- currentValue = newValue
- onSave(newValue)
- }
- }
- )
- }
- }
- @OptIn(ExperimentalMaterial3Api::class)
- @Composable
- fun DatePickerDialog(
- onDismissRequest: () -> Unit,
- onDateSelected: (LocalDate) -> Unit
- ) {
- // 获取当前日期
- val currentDate = LocalDate.now()
- // 创建DatePicker状态,并初始化为当前日期
- val datePickerState = rememberDatePickerState(initialSelectedDateMillis = currentDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli())
- // 创建对话框
- Dialog(
- onDismissRequest = onDismissRequest,
- ) {
- // 使用Column布局来组织对话框内容
- Column(
- modifier = Modifier
- .fillMaxWidth()
- .padding(16.dp)
- ) {
- // 显示标题
- Text(
- text = "Select Date",
- style = MaterialTheme.typography.headlineSmall,
- modifier = Modifier.padding(bottom = 16.dp)
- )
- // 显示日期选择器
- DatePicker(state = datePickerState)
- // 使用Row布局来组织按钮
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 16.dp),
- horizontalArrangement = Arrangement.End
- ) {
- // 取消按钮
- Button(
- onClick = onDismissRequest,
- modifier = Modifier.padding(end = 8.dp)
- ) {
- Text("Cancel")
- }
- // 确定按钮
- Button(onClick = {
- // 获取选中的日期
- val selectedDate = LocalDate.ofInstant(
- Instant.ofEpochMilli(datePickerState.selectedDateMillis ?: currentDate.atStartOfDay(ZoneId.systemDefault()).toInstant().toEpochMilli()),
- ZoneId.systemDefault()
- )
- // 调用回调函数,传递选中的日期
- onDateSelected(selectedDate)
- }) {
- Text("OK")
- }
- }
- }
- }
- }
- @OptIn(ExperimentalMaterial3Api::class) // 使用实验性API的注解
- @Composable // 标记为可组合函数
- fun TimePickerDialog(
- onDismissRequest: () -> Unit, // 对话框关闭时的回调函数
- onTimeSelected: (LocalTime) -> Unit // 时间选择后的回调函数
- ) {
- val currentTime = LocalTime.now(ZoneId.systemDefault()) // 获取当前时间
- val timePickerState = rememberTimePickerState(initialHour = currentTime.hour, initialMinute = currentTime.minute) // 创建时间选择器的状态,并初始化为当前时间
- Dialog(
- onDismissRequest = onDismissRequest, // 设置对话框关闭的回调函数
- ) {
- Column(
- modifier = Modifier
- .fillMaxWidth() // 填充父布局的宽度
- .padding(16.dp) // 设置内边距
- ) {
- Text(
- text = "Select Time", // 显示文本
- style = MaterialTheme.typography.headlineSmall, // 使用主题中的小标题样式
- modifier = Modifier.padding(bottom = 16.dp) // 设置底部内边距
- )
- TimePicker(state = timePickerState) // 显示时间选择器
- Row(
- modifier = Modifier
- .fillMaxWidth() // 填充父布局的宽度
- .padding(top = 16.dp), // 设置顶部内边距
- horizontalArrangement = Arrangement.End // 水平排列方式为靠右
- ) {
- Button(
- onClick = onDismissRequest, // 点击按钮时调用关闭回调函数
- modifier = Modifier.padding(end = 8.dp) // 设置右边距
- ) {
- Text("Cancel") // 显示取消文本
- }
- Button(onClick = {
- val selectedTime = LocalTime.of(timePickerState.hour, timePickerState.minute) // 获取选中的时间
- onTimeSelected(selectedTime) // 调用时间选择后的回调函数
- }) {
- Text("OK") // 显示确定文本
- }
- }
- }
- }
- }
- @Composable
- fun BottomNavigationExample() {
- val context = LocalContext.current
- val activity = context as Activity // 直接转换为 Activity
- val dbHelper = remember { RecordDbHelper(context) }
- var selectedItem by rememberSaveable { mutableStateOf(0) }
- val historyStack = remember { mutableStateListOf<Int>() }
- val originalGreen = Color(0xFFE8F5E9)
- // 加深方案(任选其一)
- val deepGreen1 = Color(0xFFC8E6C9) // 浅灰绿(比原色深10%)
- val deepGreen2 = Color(0xFFA5D6A7) // 中等青绿(Material Teal 200)
- val deepGreen3 = Color(0xFF80CBC4) // 深青蓝色(Material Teal 300)
- val gradientColors = listOf(deepGreen2, deepGreen3)
- // 处理返回键
- BackHandler(enabled = historyStack.isNotEmpty()) {
- if (historyStack.isNotEmpty()) {
- // 兼容性写法(支持所有 Kotlin 版本)
- val lastIndex = historyStack.size - 1
- selectedItem = historyStack.removeAt(lastIndex)
- } else {
- activity.finish() // 正确调用 Activity 的 finish()
- }
- }
- // 点击底部导航项时更新历史栈
- fun onTabSelected(index: Int) {
- historyStack.add(selectedItem)
- selectedItem = index
- }
- val items = listOf("记录", "历史", "图表")
- // 使用Scaffold布局,包含底部导航栏
- Scaffold(
- bottomBar = {
- // 创建底部导航栏
- NavigationBar(
- modifier = Modifier
- //.height(72.dp) // 保持高度设置
- .padding(horizontal = 8.dp)
- .background(
- brush = Brush.linearGradient(
- colors = gradientColors,
- start = Offset(0f, 0f),
- end = Offset(1000f, 0f)
- ),
- alpha = 0.9f // 添加背景透明度
- ),
- containerColor = Color.Transparent
- ) {
- // 遍历items列表,为每个项创建一个NavigationBarItem
- items.forEachIndexed { index, item ->
- NavigationBarItem(
- // 根据index选择不同的图标
- icon = {
- Icon(
- painter = painterResource(id = when (index) {
- 0 -> R.drawable.write // 修正资源引用(去掉_png后缀)
- 1 -> R.drawable.list // 修正资源引用(去掉_png后缀)
- else -> R.drawable.chart2
- }),
- contentDescription = item,
- modifier = Modifier
- .size(48.dp),
- //.background(Color.Red)
- tint = Color.Unspecified
- )
- },
- // 显示项的标签
- label = { Text(
- item,
- fontSize = 12.sp,
- color = Color.Black, // 强制文字颜色为白色
- modifier = Modifier.padding(top = 0.dp)
- //modifier = Modifier.padding(vertical = 2.dp)
- ) },
- // 判断当前项是否被选中
- selected = selectedItem == index,
- onClick = { onTabSelected(index) },
- colors = NavigationBarItemDefaults.colors(
- selectedIconColor = Color.White,
- indicatorColor = Color(0xFF006400).copy(alpha = 0.5f)
- )
- )
- }
- }
- }
- ) { innerPadding ->
- // 根据选中的项显示不同的内容
- when (selectedItem) {
- 0 -> Box(modifier = Modifier
- .fillMaxSize()
- .background(
- brush = Brush.verticalGradient(
- colors = gradientColors,
- startY = 0f,
- endY = 1200f
- )
- )
- .padding(innerPadding) // 添加内边距
- ) {
- NumberPickerDemo(dbHelper = dbHelper)
- }
- 1 -> Box(modifier = Modifier
- .fillMaxSize()
- .background(
- brush = Brush.verticalGradient( // 历史界面也改为渐变背景
- colors = gradientColors,
- startY = 0f,
- endY = 1200f
- )
- )
- .padding(top = 20.dp) // 新增顶部内边距
- .padding(innerPadding) // 添加内边距
- ){
- HistoryScreen(dbHelper = dbHelper)
- }
- 2 -> Box(modifier = Modifier
- .fillMaxSize()
- .background(
- brush = Brush.verticalGradient( // 统计界面改为渐变背景
- colors = gradientColors,
- startY = 0f,
- endY = 1200f
- )
- )
- .padding(innerPadding) // 添加内边距
- ){
- StatisticsScreen(dbHelper = dbHelper)
- }
- }
- }
- }
- @Composable
- fun HistoryScreen(dbHelper: RecordDbHelper) {
- val records = remember { mutableStateListOf<HealthRecord>() }
- var showDeleteDialog by remember { mutableStateOf(false) }
- var selectedRecord by remember { mutableStateOf<HealthRecord?>(null) }
- val scope = rememberCoroutineScope()
- // 加载数据库记录
- LaunchedEffect(Unit) {
- scope.launch(Dispatchers.IO) {
- try {
- val loadedRecords = mutableListOf<HealthRecord>()
- dbHelper.readableDatabase.use { db ->
- val cursor = db.query(
- "records",
- arrayOf("date", "time", "high", "low", "heart_rate", "status"),
- null, null, null, null,
- "date DESC, time DESC"
- )
- try {
- while (cursor.moveToNext()) {
- val date = cursor.getString(0) ?: ""
- val time = cursor.getString(1) ?: ""
- val high = cursor.getInt(2)
- val low = cursor.getInt(3)
- val heartRate = cursor.getInt(4)
- val status = cursor.getString(5) ?: "未知"
- loadedRecords.add(
- HealthRecord(date, time, high, low, heartRate, status)
- )
- }
- } finally {
- cursor.close()
- }
- }
- withContext(Dispatchers.Main) {
- records.clear()
- records.addAll(loadedRecords)
- }
- } catch (e: Exception) {
- Log.e("HistoryScreen", "加载记录失败: ${e.message}", e)
- }
- }
- }
- // 删除确认对话框
- if (showDeleteDialog) {
- AlertDialog(
- onDismissRequest = { showDeleteDialog = false },
- title = { Text("删除记录") },
- text = { Text("确定要删除这条记录吗?") },
- confirmButton = {
- TextButton(
- onClick = {
- selectedRecord?.let { record ->
- scope.launch(Dispatchers.IO) {
- try {
- dbHelper.writableDatabase.delete(
- "records",
- "date = ? AND time = ?",
- arrayOf(record.date, record.time)
- )
- withContext(Dispatchers.Main) {
- records.remove(record)
- showDeleteDialog = false
- }
- } catch (e: Exception) {
- Log.e("HistoryScreen", "删除失败: ${e.message}", e)
- }
- }
- }
- }
- ) { Text("确认") }
- },
- dismissButton = {
- TextButton(
- onClick = { showDeleteDialog = false }
- ) { Text("取消") }
- }
- )
- }
- // 记录列表
- LazyColumn(
- modifier = Modifier.fillMaxSize(),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- items(
- count = records.size,
- key = { index -> "${records[index].date}_${records[index].time}" }
- ) { index ->
- val record = records[index]
- Card(
- modifier = Modifier
- .fillMaxWidth(0.9f)
- .padding(vertical = 2.dp)
- .clickable {
- selectedRecord = record
- showDeleteDialog = true
- },
- elevation = CardDefaults.cardElevation(4.dp),
- colors = CardDefaults.cardColors(
- containerColor = when (record.status) {
- "低血压" -> Color(0xFFE3F2FD)
- "正常血压" -> Color(0xFFE8F5E9)
- "正常高值血压" -> Color(0xFFFFF8E1)
- "1级高血压" -> Color(0xFFFFF3E0)
- "2级高血压", "3级高血压" -> Color(0xFFFFEBEE)
- else -> Color.White
- }
- )
- ) {
- Column(modifier = Modifier.padding(16.dp)) {
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 16.dp)
- ) {
- Text(
- "日期: ${record.date} ${record.time}",
- fontSize = 16.sp,
- modifier = Modifier.weight(1f)
- )
- Text(
- "状态: ${record.status}",
- color = when (record.status) {
- "低血压" -> Color.Blue
- "正常血压" -> Color(0xFF006400)
- "正常高值血压" -> Color(0xFFFFB300)
- "1级高血压" -> Color(0xFFFF6700)
- "2级高血压", "3级高血压" -> Color.Red
- else -> Color.Gray
- },
- fontSize = 18.sp
- )
- }
- Row(
- modifier = Modifier
- .fillMaxWidth()
- .padding(top = 16.dp)
- ) {
- Text(
- "血压: ${record.high}/${record.low} mmHg",
- fontSize = 18.sp,
- modifier = Modifier.weight(1f)
- )
- Text(
- "心率: ${record.heartRate} 次/分钟",
- fontSize = 18.sp
- )
- }
- }
- }
- }
- }
- }
- @Composable
- fun StatisticsScreen(dbHelper: RecordDbHelper) {
- val context = LocalContext.current
- val records = remember { mutableStateListOf<HealthRecord>() }
- val isLoading = remember { mutableStateOf(true) }
- // 加载数据
- LaunchedEffect(Unit) {
- withContext(Dispatchers.IO) {
- val tempList = dbHelper.readableDatabase.use { db ->
- db.query(
- "records",
- arrayOf("date", "time", "high", "low", "heart_rate"),
- null, null, null, null,
- "date || time ASC"
- ).use { cursor ->
- val list = mutableListOf<HealthRecord>()
- while (cursor.moveToNext()) {
- list.add(
- HealthRecord(
- date = cursor.getString(0),
- time = cursor.getString(1),
- high = cursor.getInt(2),
- low = cursor.getInt(3),
- heartRate = cursor.getInt(4),
- status = ""
- )
- )
- }
- list
- }
- }
- withContext(Dispatchers.Main) {
- records.clear()
- records.addAll(tempList)
- isLoading.value = false
- }
- }
- }
- // 加载指示器
- if (isLoading.value) {
- Box(
- modifier = Modifier.fillMaxSize(),
- contentAlignment = Alignment.Center
- ) {
- CircularProgressIndicator()
- }
- return
- }
- // 图表视图
- AndroidView(
- factory = { context ->
- LineChart(context).apply {
- layoutParams = ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT,
- ViewGroup.LayoutParams.MATCH_PARENT
- )
- setTouchEnabled(true)
- setPinchZoom(true)
- description.isEnabled = false
- // X轴配置
- xAxis.apply {
- position = XAxis.XAxisPosition.BOTTOM
- granularity = 1f
- labelRotationAngle = -45f
- valueFormatter = object : ValueFormatter() {
- override fun getFormattedValue(value: Float): String {
- return records.getOrNull(value.toInt())?.let {
- "${it.date}\n${it.time}"
- } ?: ""
- }
- }
- setLabelCount(5, true)
- }
- // Y轴配置
- axisLeft.apply {
- granularity = 20f
- axisMinimum = 0f
- axisMaximum = 220f
- addLimitLine(LimitLine(90f, "收缩压阈值").apply {
- lineColor = Color.Red.hashCode()
- lineWidth = 2f
- enableDashedLine(10f, 10f, 0f)
- })
- addLimitLine(LimitLine(140f, "舒张压阈值").apply {
- lineColor = Color.Red.hashCode()
- lineWidth = 2f
- enableDashedLine(10f, 10f, 0f)
- })
- }
- axisRight.isEnabled = false
- legend.apply {
- verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM
- horizontalAlignment = Legend.LegendHorizontalAlignment.CENTER
- orientation = Legend.LegendOrientation.HORIZONTAL
- setDrawInside(false)
- yOffset = 20f
- }
- }
- },
- update = { chart ->
- try {
- if (records.isNotEmpty()) {
- val highEntries = mutableListOf<Entry>()
- val lowEntries = mutableListOf<Entry>()
- val heartEntries = mutableListOf<Entry>()
- records.forEachIndexed { index, record ->
- highEntries.add(Entry(index.toFloat(), record.high.toFloat()))
- lowEntries.add(Entry(index.toFloat(), record.low.toFloat()))
- heartEntries.add(Entry(index.toFloat(), record.heartRate.toFloat()))
- }
- val highSet = LineDataSet(highEntries, "收缩压").apply {
- color = Color.Red.hashCode()
- lineWidth = 2f
- setDrawCircles(false)
- }
- val lowSet = LineDataSet(lowEntries, "舒张压").apply {
- color = Color.Blue.hashCode()
- lineWidth = 2f
- setDrawCircles(false)
- }
- val heartSet = LineDataSet(heartEntries, "心率").apply {
- color = Color.Green.hashCode()
- lineWidth = 2f
- setDrawCircles(false)
- }
- chart.data = LineData(highSet, lowSet, heartSet)
- chart.animateY(1000)
- chart.invalidate()
- chart.setVisibleXRangeMaximum(7f)
- chart.moveViewToX((records.size - 1).toFloat())
- }
- } catch (e: Exception) {
- Log.e("ChartUpdate", "更新图表失败: ${e.message}", e)
- }
- },
- modifier = Modifier
- .fillMaxSize()
- .padding(16.dp)
- )
- }
复制代码 settings.gradle.kts 配置如下:
- pluginManagement {
- repositories {
- google {
- content {
- includeGroupByRegex("com\\.android.*")
- includeGroupByRegex("com\\.google.*")
- includeGroupByRegex("androidx.*")
- }
- }
- mavenCentral()
- gradlePluginPortal()
- }
- }
- dependencyResolutionManagement {
- repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
- repositories {
- google()
- mavenCentral()
- maven {
- url = uri("https://jitpack.io")
- }
- }
- }
- rootProject.name = "MyNumSet"
- include(":app")
复制代码 build.gradle.kts配置如下:
- plugins {
- alias(libs.plugins.android.application)
- alias(libs.plugins.kotlin.android)
- alias(libs.plugins.kotlin.compose)
- }
- android {
- namespace = "com.example.mynumset"
- compileSdk = 35
- defaultConfig {
- applicationId = "com.example.mynumset"
- minSdk = 34
- targetSdk = 35
- versionCode = 1
- versionName = "1.0"
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- isMinifyEnabled = false
- proguardFiles(
- getDefaultProguardFile("proguard-android-optimize.txt"),
- "proguard-rules.pro"
- )
- }
- }
- compileOptions {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
- }
- kotlinOptions {
- jvmTarget = "11"
- }
- buildFeatures {
- compose = true
- }
- }
- dependencies {
- implementation ("com.github.PhilJay:MPAndroidChart:v3.1.0")
- implementation ("androidx.datastore:datastore-preferences:1.0.0")
- implementation("androidx.compose.foundation:foundation:1.4.0")
- implementation(libs.androidx.core.ktx)
- implementation(libs.androidx.lifecycle.runtime.ktx)
- implementation(libs.androidx.activity.compose)
- implementation(platform(libs.androidx.compose.bom))
- implementation(libs.androidx.ui)
- implementation(libs.androidx.ui.graphics)
- implementation(libs.androidx.ui.tooling.preview)
- implementation(libs.androidx.material3)
- testImplementation(libs.junit)
- androidTestImplementation(libs.androidx.junit)
- androidTestImplementation(libs.androidx.espresso.core)
- androidTestImplementation(platform(libs.androidx.compose.bom))
- androidTestImplementation(libs.androidx.ui.test.junit4)
- debugImplementation(libs.androidx.ui.tooling)
- debugImplementation(libs.androidx.ui.test.manifest)
- }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |