记账本App主页页面的绘制
记账本App的主页界面绘制
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ExpenseTrackerApp(
appViewModel: ExpenseTrackerViewModel = viewModel()
) {
val appUiState by appViewModel.uiState.collectAsState()
Box(modifier = Modifier.safeContentPadding()) {
Scaffold(
topBar = {
TopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
title = {
Text("Expense Tracker")
}
)
},
floatingActionButton = {
ExtendedFloatingActionButton(
onClick = { appUiState.showModalBottomSheet.value = true },
icon = { Icon(Icons.Filled.Add, "Add New Expense Record") },
text = { Text(text = "Add New") },
)
}
) { innerPadding ->
Column(
modifier = Modifier
.padding(innerPadding),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
ExpenseRecordList(records = appUiState.expenseRecords)
if (appUiState.showModalBottomSheet.value) {
ModalBottomSheet(
onDismissRequest = { appUiState.showModalBottomSheet.value = false },
modifier = Modifier.navigationBarsPadding(),
) {
ExpenseRecordInputSheet(appViewModel)
}
}
}
}
}
}
@Composable
fun ExpenseRecordList(records: List<ExpenseRecord>) {
LazyColumn {
items(records) { record ->
ExpenseRecordContent(record)
}
}
}
@Composable
fun ExpenseRecordContent(record: ExpenseRecord) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
Column(modifier = Modifier.padding(10.dp, 10.dp)) {
Text(
text = record.name,
style = MaterialTheme.typography.titleSmall
)
Text(
text = record.money.toString(),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.End,
style = MaterialTheme.typography.displayMedium
)
Text(
text = record.time.toString(),
modifier = Modifier.fillMaxWidth(),
textAlign = TextAlign.End,
style = MaterialTheme.typography.labelSmall
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ExpenseRecordInputSheet(viewModel: ExpenseTrackerViewModel) {
val inputName = viewModel.inputName
val inputMoney = viewModel.inputMoney
Column(modifier = Modifier.padding(20.dp)) {
TextField(
value = inputName.value,
onValueChange = { inputName.value = it },
label = { Text("Name") },
modifier = Modifier.fillMaxWidth().align(Alignment.CenterHorizontally).padding(10.dp)
)
TextField(
value = inputMoney.value,
onValueChange = { inputMoney.value = it },
label = { Text("Money") },
modifier = Modifier.fillMaxWidth().align(Alignment.CenterHorizontally).padding(10.dp)
//todo add filter to only allow numbers
)
Button(
onClick = { viewModel.submitData() },
modifier = Modifier.fillMaxWidth().align(Alignment.CenterHorizontally).padding(10.dp)
) {
Text("Submit")
}
}
}
在这个例子中,我们使用了Scaffold
来构建整个页面的布局,Scaffold
是一个Material组件,它提供了一个标准的布局,包括TopAppBar
、BottomAppBar
、FloatingActionButton
等,我们可以通过Scaffold
的参数来设置这些组件的属性。在这个例子中,我们使用了TopAppBar
和FloatingActionButton
,并且通过ExtendedFloatingActionButton
来设置FloatingActionButton
的样式。
在Scaffold
的content
中,我们使用了Column
来垂直排列两个组件,一个是ExpenseRecordList
,另一个是ModalBottomSheet
。ExpenseRecordList
是一个LazyColumn
,它用来显示所有的记录,ModalBottomSheet
是一个模态的底部表单,用来输入新的记录。
在ExpenseRecordInputSheet
中,我们使用了TextField
来输入记录的名字和金额,使用Button
来提交数据。
这个例子中,我们使用了ViewModel
来管理数据,ExpenseTrackerViewModel
是一个ViewModel
,它包含了所有的数据和状态,我们通过viewModel
函数来获取ViewModel
的实例。
在ExpenseTrackerViewModel
中,我们使用了State
来管理数据,State
是一个可以被观察的数据,当数据发生变化时,State
会通知所有的观察者。我们使用collectAsState
来观察State
的变化,当State
发生变化时,collectAsState
会重新计算Composable
。
在ExpenseTrackerViewModel
中,我们使用了MutableState
来管理用户的输入,MutableState
是一个可以被修改的数据,我们可以通过value
来获取MutableState
的值,通过value = newValue
来修改MutableState
的值。
在ExpenseTrackerViewModel
中,我们使用了MutableStateFlow
来管理数据的流,MutableStateFlow
是一个可以被观察的数据流,当数据流发生变化时,MutableStateFlow
会通知所有的观察者。我们使用collect
来观察MutableStateFlow
的变化,当MutableStateFlow
发生变化时,collect
会重新计算Composable
。