ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

安卓学习记录

安卓学习记录 阶段一新手村语言与工具目标能看懂代码能让程序跑起来。Kotlin 基础只需要学变量、if/else、for/while、函数、类。协程、泛型、高阶函数统统先跳过。Android Studio学会点绿色三角形运行、学会看底部的 Logcat日志用来排查闪退。 阶段二搭积木Compose UI目标能画出静态页面。基础组件Text文字、Button按钮、Image图片、TextField输入框。布局组件Column竖着排、Row横着排、Box叠着放。修饰符Modifier大小、背景色、边距 padding。 你现在就在这个阶段。不要纠结原理就像搭乐高能把文字和按钮摆在屏幕正确的位置就算赢。耗时2~3周 阶段三注入灵魂状态与交互目标点按钮有反应数据变了界面跟着变。核心魔法remember和mutableStateOf状态。事件onClick点击事件。任务自己写一个“计数器”点一下数字1再写一个简单的“待办事项清单”能输入能显示在列表里。耗时2~3周 阶段四多页面与长列表进阶UI目标能做多页面的App能滑长列表。列表LazyColumn纵向滑动列表、LazyRow。导航Navigation Compose页面跳转比如从主页跳到详情页。任务写一个假的“商城App”有首页、商品列表、点击商品能跳到详情页数据先写死。耗时2~3周 阶段五连接真实世界网络与架构—— 分水岭目标从网上拉取真实数据把代码写规范。网络请求Retrofit或Ktor。图片加载Coil从网上下载图片显示。架构模式ViewModelStateFlow把数据和界面分开管理这一步是新手到大白的关键。任务调用一个免费的天气API把真实的天气数据显示在你的App上。耗时1~2个月 阶段六以后再说高级技能本地数据库Room、依赖注入Hilt、推送、上架Google Play。现在完全不用看看了也会忘等找到工作或者做毕设时再学。kotlinColumn、Row、Button、Modifier不知道要学多久计算契代码 无逻辑阶段二// // 1. 包声明 // 作用告诉电脑这个代码文件属于哪个文件夹结构你的项目路径。 // 注意这是系统自动生成的不要改。 // package com.example.brodcast.compents // // 2. 导入工具箱Import // 作用代码里用到的每一个组件都放在特定的工具包里。Import 就是去取工具。 // 语法import 包名.具体类名 或者 import 包名.* (星号代表把整个包全部拿过来) // // Compose 的基础画图工具比如排列、大小、背景、圆角等 import androidx.compose.foundation.Canvas // 画布用来画坐标轴、线、圆点 import androidx.compose.foundation.background // 背景颜色 import androidx.compose.foundation.clickable // 让某个组件变成可点击的 import androidx.compose.foundation.layout.* // ⭐ 通配符导入所有布局工具Column, Row, Box, Spacer, padding, fillMaxSize 等 import androidx.compose.foundation.lazy.LazyColumn // 懒加载列表列表长了可以滑动不卡顿 import androidx.compose.foundation.lazy.items // 配合 LazyColumn 用的用来循环生成列表行 import androidx.compose.foundation.shape.CircleShape // 圆形形状 import androidx.compose.foundation.shape.RoundedCornerShape // 圆角形状 import androidx.compose.foundation.text.KeyboardOptions // 键盘配置 import androidx.compose.material3.* // ⭐ 通配符导入 Material3 组件库Text, Button, Card, OutlinedTextField, CardDefaults 等 // Compose 运行时的核心工具状态管理 import androidx.compose.runtime.* // ⭐ 通配符导入 Composable 标签、remember、mutableStateOf、LaunchedEffect 等数据灵魂工具 // 一些基础工具对齐、修饰符、颜色、文字样式、单位等 import androidx.compose.ui.Alignment // 对齐方式居中、靠左、靠右等 import androidx.compose.ui.Modifier // 万能修饰符用来改大小、边距、背景等 import androidx.compose.ui.geometry.Offset // 用来表示画布上的一个坐标点 (x, y) import androidx.compose.ui.graphics.Color // 颜色 import androidx.compose.ui.text.font.FontWeight // 字体粗细加粗 import androidx.compose.ui.text.input.KeyboardType // 键盘类型数字、字母、小数 import androidx.compose.ui.unit.dp // 尺寸单位类似厘米用于大小、边距 import androidx.compose.ui.unit.sp // 字体大小单位 // // 3. 数据模型 // 作用用来装每一个坐标点的数据。 // 语法data class 是 Kotlin 的特有写法。加上 data 后电脑会自动帮你生成对比、打印等函数。 // 上下关系这是一个独立的模型不包含在任何界面里但界面里的所有操作都是围绕它转的。 // data class DataPoint(val x: Float, val y: Float) // // 4. 界面主函数大管家 // 作用所有 UI 界面的入口。 // 语法Composable 是固定标签告诉编译器“这是一个画界面的函数”。 // Composable fun LeastSquaresChart() { // 4.1 状态管理数据大脑 // 常识Compose 是“数据驱动”的。数据变了界面就会自动刷新不需要你手动去改文字。 // 语法var 变量名 by remember { mutableStateOf(初始值) } // 作用记住这个数据。数据改变时界面会自动重新画一遍。 // 作用存储所有输入的点。默认给 5 个点一打开就能看到图表不用手动输入。 var points by remember { mutableStateOf( listOf( DataPoint(1f, 1.5f), // 1f 代表 1.0Float 类型的固定写法 DataPoint(2f, 2.8f), DataPoint(3f, 3.2f), DataPoint(4f, 5.1f), DataPoint(5f, 5.9f) ) ) } // 作用记住 X 和 Y 输入框里面的文字。初始为空字符串。 var inputX by remember { mutableStateOf() } var inputY by remember { mutableStateOf() } // 作用存储计算出来的斜率(a) 和截距(b)。 var slopeA by remember { mutableStateOf(0f) } var interceptB by remember { mutableStateOf(0f) } // 作用存储最终格式化好的方程文本比如 y 1.234x 0.567直接显示在屏幕上。 var equationText by remember { mutableStateOf() } // 4.2 自动计算后台小房间 // 作用监听 points 列表。只要 points 变了增加、删除、清空就会自动触发里面的代码重新计算最小二乘法。 // 语法LaunchedEffect(要监听的数据) { 逻辑代码 } LaunchedEffect(points) { // 常识要画一条直线至少需要 2 个点。如果不够直接退出计算。 if (points.size 2) { equationText 至少需要 2 个点才能拟合 // returnLaunchedEffect 的意思是退出这个后台小房间后面的代码不跑了。 // 为什么不能直接写 return因为这是匿名函数必须用 LaunchedEffect 标签告诉电脑我要退出哪里。 returnLaunchedEffect } // 最小二乘法公式需要用的“求和”变量。初始都为 0.0Double 类型精度比 Float 高。 var sumX 0.0 var sumY 0.0 var sumXY 0.0 var sumX2 0.0 val n points.size // 点的总数量 // 作用用 for 循环把所有点遍历一遍把数据累加起来。 for (p in points) { sumX p.x sumY p.y sumXY p.x * p.y sumX2 p.x * p.x } // 作用计算分母。如果分母为 0说明所有的 X 坐标一样无法画出一条斜线。 val denominator n * sumX2 - sumX * sumX if (denominator 0.0) { equationText X坐标不能完全相同 returnLaunchedEffect } // 作用最小二乘法核心公式。计算出斜率 a 和截距 b。 val a (n * sumXY - sumX * sumY) / denominator val b (sumY - a * sumX) / n // 作用把计算结果存进状态里。数据一变界面自动重绘红线和方程文本。 slopeA a.toFloat() interceptB b.toFloat() // 作用格式化输出保留三位小数。 equationText y ${%.3f.format(a)}x ${%.3f.format(b)} } // 4.3 整体界面布局 // 作用最外层的 Column让所有内容从上往下排列。 // 语法Modifier 是一节一节拼起来的。fillMaxSize 占满屏幕padding 四周留白background 设置浅灰底色。 // verticalArrangement Arrangement.spacedBy(12.dp) 作用每个子元素之间自动留出 12dp 的空隙。 Column( modifier Modifier .fillMaxSize() .padding(16.dp) .background(Color(0xFFF7F7F7)), verticalArrangement Arrangement.spacedBy(12.dp) ) { // 作用顶部标题文字。 Text( text 最小二乘法拟合器, fontSize 24.sp, fontWeight FontWeight.Bold, color Color(0xFF333333) ) // --- 输入卡片 --- // 作用做一个白色的卡片框把输入区包起来带阴影。 Card( modifier Modifier.fillMaxWidth(), // 撑满宽度 colors CardDefaults.cardColors(containerColor Color.White), // 卡片背景色 // elevation 作用卡片阴影大小。数字越大卡片浮得越高影子越深。 // 语法必须通过 CardDefaults.cardElevation(...) 来设置不能直接写 2.dp。 elevation CardDefaults.cardElevation(defaultElevation 2.dp) ) { // 作用卡片内部的内容从上往下排。padding 内部留白 12dp。 Column(modifier Modifier.padding(12.dp), verticalArrangement Arrangement.spacedBy(8.dp)) { Text(添加新数据点, fontSize 14.sp, fontWeight FontWeight.Medium) // 作用把输入框和按钮横着排Row。 Row( modifier Modifier.fillMaxWidth(), horizontalArrangement Arrangement.spacedBy(8.dp), // 横向元素之间留 8dp 空隙 // verticalAlignment 作用让 Row 里面所有元素在垂直方向居中对齐。 // 比如输入框和按钮高度不一样时加上这个就会对齐到中间不会一个高一个低。 verticalAlignment Alignment.CenterVertically ) { // 作用X 输入框。 // value 是输入框显示的内容onValueChange 是用户打字时触发的动作。 // singleLine true 作用限制只能输入一行。按回车也不换行高度固定不变。 // keyboardOptions 作用点击输入框时弹出来的键盘类型。这里是 Decimal代表带小数点的数字键盘。 // label 是输入框的占位提示文字。 OutlinedTextField( value inputX, onValueChange { inputX it }, // it 代表用户输入的最新文字 label { Text(X) }, modifier Modifier.weight(1f), // 平分宽度 singleLine true, keyboardOptions KeyboardOptions(keyboardType KeyboardType.Decimal) ) // 作用Y 输入框和 X 输入框逻辑完全一样。 OutlinedTextField( value inputY, onValueChange { inputY it }, label { Text(Y) }, modifier Modifier.weight(1f), singleLine true, keyboardOptions KeyboardOptions(keyboardType KeyboardType.Decimal) ) // 作用添加按钮。点击时解析输入框内容并转为 Float如果合法就加到 points 列表里并清空输入框。 Button( onClick { val x inputX.toFloatOrNull() val y inputY.toFloatOrNull() if (x ! null y ! null) { points points DataPoint(x, y) inputX inputY } }, modifier Modifier.height(56.dp) // 按钮高度 ) { Text(添加) } } } } // --- 结果显示区 --- // 作用专门用来展示计算出的方程。 Card( modifier Modifier.fillMaxWidth(), colors CardDefaults.cardColors(containerColor Color(0xFFE3F2FD)), // 浅蓝色 elevation CardDefaults.cardElevation(defaultElevation 0.dp) // 阴影为0紧贴屏幕 ) { // 作用横向排版左边显示方程右边显示清空按钮。 Row( modifier Modifier .fillMaxWidth() .padding(16.dp), // SpaceBetween 作用把这两个孩子推到左右两端中间留白。 horizontalArrangement Arrangement.SpaceBetween, verticalAlignment Alignment.CenterVertically ) { Column { Text(拟合方程, fontSize 14.sp, color Color.DarkGray) // 作用显示计算出来的方程加大加粗。 Text(equationText, fontSize 20.sp, fontWeight FontWeight.Bold, color Color(0xFF1565C0)) } // 作用清空按钮。点击后把列表置空触发自动重新计算。 TextButton( onClick { points emptyList() }, colors ButtonDefaults.textButtonColors(contentColor Color.Red) ) { Text(清空所有) } } } // --- 图表绘制区 --- // 作用用 Card 包起来占据屏幕剩余的垂直空间。weight(1f) 就是“把剩下的高度全吃光”。 Card( modifier Modifier .fillMaxWidth() .weight(1f), colors CardDefaults.cardColors(containerColor Color.White), elevation CardDefaults.cardElevation(defaultElevation 2.dp) ) { // 作用Box 是一个盒子里面的东西可以叠加在一起。加个 padding 防止图贴边。 Box(modifier Modifier.fillMaxSize().padding(16.dp)) { // 作用Canvas 画布。坐标轴、红线和圆点都在这里画出来。 Canvas(modifier Modifier.fillMaxSize()) { // 如果点不够直接退出画图。 if (points.isEmpty()) returnCanvas // 作用找出数据里的最大、最小值用来计算坐标系的范围。 val minX points.minOf { it.x } val maxX points.maxOf { it.x } val minY points.minOf { it.y } val maxY points.maxOf { it.y } // 作用给四周加 10% 的留白防止数据点贴着画布边缘显得好看。 val paddingX (maxX - minX) * 0.1f val paddingY (maxY - minY) * 0.1f // 作用防止除零错误。如果所有点的 X 一样范围强制设为 1f。 val rangeX if (maxX - minX 0f) 1f else (maxX - minX) paddingX * 2 val rangeY if (maxY - minY 0f) 1f else (maxY - minY) paddingY * 2 val startX minX - paddingX val startY minY - paddingY // 拿到画布的真实宽高像素。 val width size.width val height size.height // 作用坐标映射。 // 数学坐标系是“左下角为原点Y 向上”。 // 手机屏幕坐标系是“左上角为原点Y 向下”。 // 所以 Y 轴的计算需要用 height 减去一部分才能把图翻正过来。 fun mapX(x: Float): Float (x - startX) / rangeX * width fun mapY(y: Float): Float height - (y - startY) / rangeY * height // 计算原点 (0,0) 在屏幕上的像素位置。 val zeroX mapX(0f) val zeroY mapY(0f) // 作用画 X 轴横线。只有在原点在可视范围内才画。 if (zeroY in 0f..height) { drawLine(Color.Black, start Offset(0f, zeroY), end Offset(width, zeroY), strokeWidth 2f) } // 作用画 Y 轴竖线。只有在原点在可视范围内才画。 if (zeroX in 0f..width) { drawLine(Color.Black, start Offset(zeroX, 0f), end Offset(zeroX, height), strokeWidth 2f) } // 作用画红色拟合直线。起点和终点延伸到画布边缘。 if (points.size 2) { val lineStartX startX val lineStartY slopeA * lineStartX interceptB val lineEndX startX rangeX val lineEndY slopeA * lineEndX interceptB drawLine( color Color.Red, start Offset(mapX(lineStartX), mapY(lineStartY)), end Offset(mapX(lineEndX), mapY(lineEndY)), strokeWidth 6f // 线宽 ) } // 作用画蓝色的圆点散点。外面画一个白色的稍大圆点做光圈里面画蓝色小圆点看起来有层次感。 points.forEach { p - drawCircle(Color.White, radius 12f, center Offset(mapX(p.x), mapY(p.y))) drawCircle(Color.Blue, radius 8f, center Offset(mapX(p.x), mapY(p.y))) } } } } // --- 数据列表区 --- // 作用卡片固定高度 150dp防止列表太长把上面的图表挤没。 Card( modifier Modifier .fillMaxWidth() .height(150.dp), colors CardDefaults.cardColors(containerColor Color.White), elevation CardDefaults.cardElevation(defaultElevation 2.dp) ) { // 作用内部垂直排布标题在上列表在下。 Column(modifier Modifier.padding(8.dp)) { Text(数据列表 (点击可删除), fontSize 12.sp, color Color.Gray) // 作用LazyColumn 是懒加载列表。屏幕外的内容不渲染性能好。可以上下滑动。 LazyColumn(modifier Modifier.fillMaxSize()) { // 作用循环遍历 points 列表。 // 语法items(数据源) { 当前项变量名 - 每一行长什么样 } // 这里 p 代表当前正在处理的某一个坐标点。 items(points) { p - // 作用列表的每一行。Row 横向排列。 Row( modifier Modifier .fillMaxWidth() .padding(vertical 4.dp) .background(Color(0xFFF0F0F0), RoundedCornerShape(4.dp)) .padding(horizontal 8.dp, vertical 6.dp), // SpaceBetween 让文字靠左删除按钮靠右中间撑开。 horizontalArrangement Arrangement.SpaceBetween, verticalAlignment Alignment.CenterVertically ) { // 作用显示当前坐标点的文字。 Text((${p.x}, ${p.y}), fontSize 14.sp) // 作用删除按钮。 // 因为官方图标库需要额外导包这里我们用 Box 包着一个 × 文本手动做一个删除按钮。 Box( modifier Modifier .size(24.dp) // 按钮大小 .background(Color(0xFFFFCDD2), shape CircleShape) // 浅红背景圆形 // 点击时从 points 列表中减去 p 这个点。数据一变列表和图表就会自动刷新。 .clickable { points points - p }, contentAlignment Alignment.Center // 让 × 居中 ) { Text(×, color Color.Red, fontWeight FontWeight.Bold, fontSize 16.sp) } } } } } } } }package com.example.brodcast.compents // 导入需要用到的 Compose 组件和工具 import androidx.compose.foundation.layout.* // 包含了 Column, Row, Spacer, padding, fillMaxWidth 等布局工具 import androidx.compose.foundation.shape.CircleShape // 用于把按钮变成圆形 import androidx.compose.material3.Button // 按钮组件 import androidx.compose.material3.ButtonDefaults // 按钮的默认样式比如颜色 import androidx.compose.material3.Text // 文本组件 import androidx.compose.runtime.* // 包含了 remember, mutableStateOf 等状态管理工具 import androidx.compose.ui.Alignment // 对齐方式 import androidx.compose.ui.Modifier // 修饰符用来设置大小、边距、背景等 import androidx.compose.ui.graphics.Color // 颜色 import androidx.compose.ui.text.font.FontWeight // 字体粗细 import androidx.compose.ui.text.style.TextAlign // 文字对齐方式 import androidx.compose.ui.unit.dp // dp 尺寸单位 import androidx.compose.ui.unit.sp // sp 字体大小单位 Composable fun CalculatorScreen() { // 状态管理计算器的大脑 // displayText: 屏幕上显示的数字。用户每按一个数字这个值就会改变屏幕也会跟着刷新。 // by remember 是 Compose 的固定写法表示“记住这个值即使屏幕刷新了也不要丢掉”。 var displayText by remember { mutableStateOf(0) } // firstNumber: 用来偷偷存下第一个数字。比如按了 53按加号时5 就会存到这里。 var firstNumber by remember { mutableStateOf() } // currentOperation: 用来记住当前按的是哪个运算符、-、×、÷。 var currentOperation by remember { mutableStateOf() } // isNewInput: 用来标记“是否要开始输入新数字了”。按了运算符之后这个值会变成 true。 var isNewInput by remember { mutableStateOf(false) } // 逻辑处理函数 // 处理数字按钮的点击0-9 和 小数点 fun onNumberClick(num: String) { if (isNewInput) { // 如果是新输入直接替换屏幕上的内容 displayText num isNewInput false // 重置标记表示现在开始接着拼数字了 } else { // 如果屏幕是 0并且按的不是小数点就替换。否则就把新数字拼在后面 if (displayText 0 num ! .) { displayText num } else { // 如果已经有小数点了防止重复输入多个小数点简单判断 if (num . displayText.contains(.)) return displayText num } } } // 处理运算符按钮的点击、-、×、÷ fun onOperatorClick(op: String) { // 把当前屏幕上的数字存到 firstNumber 里 firstNumber displayText // 记住按下的运算符 currentOperation op // 标记接下来要输入新数字了 isNewInput true } // 处理等号按钮的点击 fun onEqualClick() { // 只有当有第一个数字并且有运算符的时候才进行计算 if (firstNumber.isNotEmpty() currentOperation.isNotEmpty()) { // 把字符串转成小数Double如果转不了就默认是 0.0 val num1 firstNumber.toDoubleOrNull() ?: 0.0 val num2 displayText.toDoubleOrNull() ?: 0.0 var result 0.0 // 根据运算符进行对应的数学计算 when (currentOperation) { - result num1 num2 - - result num1 - num2 × - result num1 * num2 ÷ - if (num2 ! 0.0) result num1 / num2 else result 0.0 // 防止除以0导致崩溃 } // 处理结果显示如果是 5.0 这种整数就显示成 5如果是 5.5就保留小数 displayText if (result % 1.0 0.0) result.toInt().toString() else result.toString() // 算完之后清空大脑准备下一次计算 firstNumber currentOperation isNewInput true } } // 界面布局 // 最外层的 Column用来从上到下排列所有内容 Column( modifier Modifier .fillMaxSize() // 占满整个屏幕 .padding(16.dp), // 屏幕四周留出 16dp 的空白 verticalArrangement Arrangement.Bottom // 让所有内容靠屏幕底部对齐 ) { // ---- 顶部显示屏 ---- Text( text displayText, // 显示当前计算器上的数字 fontSize 64.sp, // 字体很大 fontWeight FontWeight.Bold, // 加粗 modifier Modifier .fillMaxWidth() // 占满整行宽度 .padding(bottom 24.dp), // 显示屏底部留出空隙 textAlign TextAlign.End // 文字靠右对齐 ) // ---- 键盘区域一共 5 行 ---- // 第一行 Row(modifier Modifier.fillMaxWidth()) { // AC清空所有状态 CalcButton(AC) { displayText 0 firstNumber currentOperation isNewInput false } // /-正负号切换 CalcButton(/-) { if (displayText ! 0) { displayText if (displayText.startsWith(-)) displayText.drop(1) else -$displayText } } // %百分号 CalcButton(%) { val num displayText.toDoubleOrNull() ?: 0.0 displayText (num / 100).toString() } // ÷除号 CalcButton(÷) { onOperatorClick(÷) } } // 第二行 Row(modifier Modifier.fillMaxWidth()) { CalcButton(7) { onNumberClick(7) } CalcButton(8) { onNumberClick(8) } CalcButton(9) { onNumberClick(9) } CalcButton(×) { onOperatorClick(×) } } // 第三行 Row(modifier Modifier.fillMaxWidth()) { CalcButton(4) { onNumberClick(4) } CalcButton(5) { onNumberClick(5) } CalcButton(6) { onNumberClick(6) } CalcButton(-) { onOperatorClick(-) } } // 第四行 Row(modifier Modifier.fillMaxWidth()) { CalcButton(1) { onNumberClick(1) } CalcButton(2) { onNumberClick(2) } CalcButton(3) { onNumberClick(3) } CalcButton() { onOperatorClick() } } // 第五行 Row(modifier Modifier.fillMaxWidth()) { // 这里的 0 按钮比较特殊我们让它占两份宽度 (weight(2f))宽高比是 2:1 Button( onClick { onNumberClick(0) }, modifier Modifier .weight(2f) // 占据两份宽度 .aspectRatio(2f) // 宽高比 2:1这样它就会变成一个横着的椭圆 .padding(4.dp), shape CircleShape, colors ButtonDefaults.buttonColors(containerColor Color(0xFFEEEEEE)) ) { Text(0, fontSize 24.sp, color Color.Black) } // 小数点 CalcButton(.) { onNumberClick(.) } // 等号 CalcButton() { onEqualClick() } } } } // 自定义按钮组件 // RowScope. 的作用告诉 Compose 这个按钮是属于 Row 的这样它才能使用 weight() 来平分宽度。 // 如果没有 RowScope.写 weight() 会报错。 Composable fun RowScope.CalcButton(text: String, onClick: () - Unit) { Button( onClick onClick, // 点击事件传给外部 modifier Modifier .weight(1f) // 把当前行的剩余宽度平分每个按钮占一份 .aspectRatio(1f) // 让按钮变成正方形宽高比 1:1 .padding(4.dp), // 按钮之间的缝隙 shape CircleShape, // 按钮形状是圆形 // 按钮背景颜色这里用十六进制颜色代码避免引入不必要的颜色库 colors ButtonDefaults.buttonColors(containerColor Color(0xFFEEEEEE)) ) { // 按钮里面的文字 Text(text, fontSize 24.sp, color Color.Black) } }package com.example.brodcast.compents import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color 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 Composable fun CalculatorScreen() { // 魔法1state 状态。用来存屏幕上显示的文字。 // 只要你改了 displayText 的值界面就会自动刷新 var displayText by remember { mutableStateOf(0) } // 整个屏幕的容器 Column( modifier Modifier .fillMaxSize() .padding(16.dp), // 让所有内容都对齐到屏幕底部 verticalArrangement Arrangement.Bottom ) { // 顶部显示屏 Text( text displayText, fontSize 64.sp, fontWeight FontWeight.Bold, modifier Modifier .fillMaxWidth() .padding(bottom 24.dp), // 给底部留点空隙 textAlign TextAlign.End // 文字靠右对齐 ) // 键盘区域 // 第一行 Row(modifier Modifier.fillMaxWidth()) { CalcButton(AC) { displayText 0 } CalcButton(/-) { /* 暂时不写逻辑 */ } CalcButton(%) { /* 暂时不写逻辑 */ } CalcButton(÷) { displayText ÷ } } // 第二行 Row(modifier Modifier.fillMaxWidth()) { CalcButton(7) { displayText appendNum(displayText, 7) } CalcButton(8) { displayText appendNum(displayText, 8) } CalcButton(9) { displayText appendNum(displayText, 9) } CalcButton(×) { displayText × } } // 第三行 Row(modifier Modifier.fillMaxWidth()) { CalcButton(4) { displayText appendNum(displayText, 4) } CalcButton(5) { displayText appendNum(displayText, 5) } CalcButton(6) { displayText appendNum(displayText, 6) } CalcButton(-) { displayText - } } // 第四行 Row(modifier Modifier.fillMaxWidth()) { CalcButton(1) { displayText appendNum(displayText, 1) } CalcButton(2) { displayText appendNum(displayText, 2) } CalcButton(3) { displayText appendNum(displayText, 3) } CalcButton() { displayText } } // 第五行0占两格我们用两个按钮拼起来 Row(modifier Modifier.fillMaxWidth()) { // 这个 0 的按钮稍微特殊一点我们让它占两份宽度 (weight 2f) Button( onClick { displayText appendNum(displayText, 0) }, modifier Modifier .weight(2f) .aspectRatio(2f) // 宽高比 2:1 .padding(4.dp), shape CircleShape, colors ButtonDefaults.buttonColors(containerColor Color(0xFFEEEEEE)) ) { Text(0, fontSize 24.sp, color Color.Black) } CalcButton(.) { displayText . } CalcButton() { /* 暂时不写逻辑 */ } } } } // 魔法2自定义一个按钮组件避免写一堆重复的代码 Composable fun CalcButton(text: String, onClick: () - Unit) { Button( onClick onClick, modifier Modifier .weight(1f) // 把当前行的剩余宽度平分 .aspectRatio(1f) // 让按钮变成正方形 .padding(4.dp), // 按钮之间的缝隙 shape CircleShape, // 圆形按钮 // 这里为了安全直接写颜色代码避免你再遇到 BlendMode 那个坑 colors ButtonDefaults.buttonColors(containerColor Color(0xFFEEEEEE)) ) { Text(text, fontSize 24.sp, color Color.Black) } } // 魔法3处理数字输入的简单逻辑 fun appendNum(current: String, num: String): String { // 如果当前屏幕是 0就直接替换成新的数字否则就拼在后面 return if (current 0) num else current num }
返回列表