关于初学Android开发一段时间的总结,并非教程。主要是为自己防呆,避免以后学习新知识时走一遍老路。再次强调并非教程,如有不妥之处还望见谅
零基础学习比较庞大的内容是十分吃力不讨好的,建议此前至少学习相关编程知识。此谓“薄基础”是相交而言的,指熟练掌握至少一门编程语言(最好是面向对象),并且开发过桌面应用程序或终端应用程序或嵌入式程序等,大致了解过打包发布的过程。
Android开发现在主流推的编程语言是Koltin,IDE是Android Studio(安装过程可能会有一步卡住,gradle下载问题)。此时如果你有过一门编程语言的基础,即便是没有任何Java或Koltin基础,它也不会成为障碍的。因为学习编程语言只有第一次打基础时注定会很难很煎熬,要接触变量、函数、类、多态继承、泛型编程等。此后再学习一门新的编程语言就不需要重新再按部就班地从头学习什么变量、函数、循环分支的了,这不仅浪费时间还会耗费学习热情。因为在编程语言这个大框架中,语法基本上大同小异,剩下的基本上是什么新特性,但编程理念却是一脉相承。
在准备学习Android开发时,由于基础薄弱首推应该是视频教程,相较于书籍文档,虽然知识密度没那么高,但优点是直观、容易上手。比较好一点的视频教程有2022 最新 Android 基础教程_哔哩哔哩
下面为这个视频教程的P1-P36的个人总结,不包含对知识点的讲解,主要是学习过程
一、基础学习
1,在下版本,有何贵干
在开始的学习中,会介绍IDE的使用,然后是UI界面的简单设计。从这个教程的名字就可以看出这个版本已经“落后了”,互联网的技术迭代很快,所以要格外注重版本,关注更新了什么。这并不是说教程完全不管用,只不过部分内容需要自行抉择。
比如学习这个界面显示时,视频教程从最初创建工程就已经不同了,视频里的工程创建有Java这个选项,而2024这个版本就只有Koltin了,索性就从Koltin开始(反正Java、Koltin都不会)。其中关于“Java是Koltin的必由之路”的观点我不是很赞同,掌握一门编程语言后,跨“语种”并非跨天堑。
生成工程后,你会发现初始代码也不同(我是指调用函数的差别而非语言),甚至res目录下都没有layout这个包。此时遇到这种巨大的版本差距时,就应该查询相关信息了
除了直接上网查询外,问AI也不错,因为对话式的问询更容易拓展知识面。从版本差异引发的问询中,可以得出几个关键信息:Compose、XML布局。
接下来无论是继续问AI还是直接上网查询,都能获得一些教程之外的信息:
——现在谷歌主推Jetpack Compose这种声明式UI
——声明式UI是什么,XML是什么
——Jetpack Compose是什么,有什么优点
—— …… …… ……
2,初识Jetpack Compose
了解到这些信息后,无论是否继续学习XML,问题都不大,至少知道“前沿”是什么方向了,而且里面可以对XML进行可视化编程。本着“越新越好”的原则,教程里的UI设计相关部分P17-P27就可以跳过了,直接去学习Jetpack Compose。
那么怎么学呢?首推应是视频,但在b站上没有找到合适的视频,所幸是谷歌为了大力推广Jetpack Compose,文档教程做的很棒,还是中文 初识 Jetpack Compose | 你好 Compose
初学过程中,免不了要写一些代码,即便此前没有接触过Koltin,但一定的编程基础会让你大致能分辨哪些是变量、函数和类。此时主要以应用出发,需要哪个学哪个,比如需要知道怎么编写函数,那么就查函数,而不是连着协程、反射、解构什么的一块去学。
而Koltin这块也有详细的中文文档,函数 · Kotlin 官方文档 中文版 (kotlincn.net)。
Tips:
由于此前学的是C/C++,有过不停地造轮子的经历,初学Koltin时竟有一种解放双手的感觉,比如函数特性多用起来异常顺手、分号可有可无等等,逐渐陷入调包侠的快乐中
3, 页面导航
经过前面学习,应该可以做一个基础的文本框什么的,而视频教程开头讲了使用XML怎么切换页面。此时由于已经转向Jetpack Compose的怀抱了,那么就先看看Jetpack Compose里有没有关于页面切换的,这里推荐一个插件:TongYi灵码,个体户是免费的。与之相似的好像还有Fittencode什么的
从这里可以获取到一个关键信息——NavHost,更确切的说是NavController这个类
4,Activity
这一部分是P37节的,但是前面如果好奇心旺盛的话,也许会了解到这。那么通过查阅资料可能会接触到到单Activity模式,而视频中所用的是多Activity,上网查的话会发现后者现在逐渐淡化,版本更迭嘛
二、简易计算器的搭建
1,项目组织
创建一个项目时,由于此前有过一些单片机小项目的开发经历,所以对于项目组织这一块很重视。此时Android对于我来说还是一个完全陌生的领域,Android项目里的文件组织完全看不懂。那么首要做的自然是了解,并且是带着强烈目的去了解,于是通过一系列对话让通义灵码生成了一个安卓开发常用的树形结构
至于这个结构后续能不能沿用,很难说,因为后续必然会接触到一些新东西,不过船到桥头自然直,且行且看
2,UI设计
考虑到这只是项目学习的开头,后续可能还会添加,那么就把这次实验(简易计算器)作为一个页面。简陋的UI设计只需理解两点
其一:@Composable注解的函数为可组合函数
其二:熟悉Koltin的传参方式和lambda表达式,以及Jetpack Compose组件的属性调用
当然,初学在于模仿
3,运算逻辑
其实一开始,我是AI式编程,“产品优先”嘛。先由AI给出实现方式,不断提出改进建议,再尝试理解,并以此为基础自行开发完善。
AI体验过程:通义(不是通义灵码)代码常常不能直接用,并且总是喜欢调包,尽管它调的包往往不能找不到。智普清言的话,只要描述的清楚,还是能够给出正确的代码的。不过总得来说,单一AI很难满足所有需求,两个得配合使用
用户:
现在按钮一共有数字、操作符+-*/、小数点、=、左右括号、C和CE类型,现在需要你编写函数handleButtonClick,可以传入的参数为label: String, textContent: MutableState<String>,前者为按钮的值,后者为一个组件的文本内容,可通过修改它来显示计算过程和结果(所以函数无需返回值)。
你需要考虑到优先级,并且需要写出完整的算法实现过程。使用Koltin编程,UI界面无需编写用户:
现在发现了以下问题,对刚才代码进行改进:
1,扩展性有待提高,因为后续可能添加其他复杂运算(按钮的值可能是个复杂的字符串,不再是单一字符),而下面代码中tokenize函数里却以一个字符作为判断,这在拓展后可能会出现问题
2,注释太少AI:
…… …… …… …… ……
下面为AI生成的简易计算器的代码供参考,涉及到逆波兰表示法
目录结构
app/
├── src/main/java
│ └── com/example/yourapp
│ ├── ui
│ │ ├── theme
│ │ │ └── AppTheme.kt
│ │ ├── screens
│ │ │ ├── CalculatorScreen.kt
│ │ ├── components
│ │ └── navigation
│ │ └── AppNavigator.kt
│ ├── data
│ │ ├── models
│ │ └── repositories
│ ├── domain
│ ├── utils
│ └── viewmodels│ └── MainActivity.kt
│ ├── CalculatorViewModel.kt
├── src/main/res
│ ├── drawable
│ ├── layout
│ ├── values
│ │ └── colors.xml
│ └── ...
└── build.gradle
代码:
CalculatorScreen.kt
package com.example.test_1.ui.screens
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.example.test_1.viewmodels.CalculatorViewModel
@Composable
fun CalculatorScreen() {
val viewModel = CalculatorViewModel()
val textContent = remember { mutableStateOf("") }
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
modifier = Modifier.padding(16.dp)
) {
// 显示计算结果
Text(
text = textContent.value,
fontSize = 24.sp,
modifier = Modifier.padding(bottom = 16.dp)
)
// 按钮布局
val buttons = listOf(
listOf("sqrt", "1/x", "x^2", "X^3"),
listOf("CE", "(", ")", "C"),
listOf("7", "8", "9", "/"),
listOf("4", "5", "6", "*"),
listOf("1", "2", "3", "-"),
listOf("0", ".", "=", "+")
)
buttons.forEachIndexed { _, row ->
Row(horizontalArrangement = Arrangement.Center) {
row.forEach { buttonLabel ->
Button(
onClick = {
viewModel.handleButtonClick(buttonLabel,textContent)
},
modifier = Modifier
.weight(1f)
.height(64.dp)
.padding(4.dp) // 内边距
) {
Text(text = buttonLabel)
}
}
}
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
CalculatorViewModel.kt
package com.example.test_1.viewmodels
import androidx.compose.runtime.MutableState
import androidx.lifecycle.ViewModel
class CalculatorViewModel : ViewModel() {
// 处理按钮点击事件
fun handleButtonClick(label: String, textContent: MutableState<String>) {
when (label) {
"C" -> textContent.value = "" // 清空显示
"CE" -> if (textContent.value.isNotEmpty()) textContent.value =
textContent.value.dropLast(1) // 删除最后一个字符
"=" -> {
try {
val result = evaluateExpression(textContent.value)
textContent.value = result.toString()
} catch (e: Exception) {
textContent.value = "Error" // 显示错误信息
}
}
// 特殊运算符处理
"sqrt", "1/x" -> {
if (textContent.value == "Error") {
textContent.value = ""
}
textContent.value += " $label(" // 添加运算符和左括号
}
else -> {
if (textContent.value == "Error") {
textContent.value = ""
}
textContent.value += label // 添加其他字符到显示
}
}
}
// 计算表达式
private fun evaluateExpression(expression: String): Double {
val outputQueue = mutableListOf<String>()
val operatorStack = mutableListOf<String>()
val tokens = tokenize(expression)
for (token in tokens) {
when {
token.toDoubleOrNull() != null -> outputQueue.add(token)
token in listOf("+", "-", "*", "/") -> {
while (operatorStack.isNotEmpty() && hasPrecedence(
token,
operatorStack.last()
)
) {
outputQueue.add(operatorStack.removeLast())
}
operatorStack.add(token)
}
token == "(" -> operatorStack.add(token)
token == ")" -> {
while (operatorStack.isNotEmpty() && operatorStack.last() != "(") {
outputQueue.add(operatorStack.removeLast())
}
operatorStack.removeLast() // 移除左括号
}
// 特殊运算符直接入栈
token == "sqrt" || token == "1/x" -> operatorStack.add(token)
}
}
while (operatorStack.isNotEmpty()) {
outputQueue.add(operatorStack.removeLast())
}
return evaluateRPN(outputQueue.toList())
}
// 将表达式转换为令牌列表
private fun tokenize(expression: String): List<String> {
val tokens = mutableListOf<String>()
val buffer = StringBuilder()
// 使用正则表达式来分割字符串,支持更复杂的运算符
val tokenPattern = Regex("[+\\-*/()\\s]|sqrt|1/x|\\d+(\\.\\d+)?")
tokenPattern.findAll(expression).forEach { matchResult ->
val token = matchResult.value
when {
token.toDoubleOrNull() != null || token == "." -> buffer.append(token)
token.matches(Regex("[+\\-*/()\\s]")) -> {
if (buffer.isNotEmpty()) {
tokens.add(buffer.toString())
buffer.clear()
}
tokens.add(token)
}
else -> tokens.add(token) // 添加特殊运算符
}
}
if (buffer.isNotEmpty()) {
tokens.add(buffer.toString())
}
return tokens
}
// 判断操作符优先级
private fun hasPrecedence(op1: String, op2: String): Boolean {
return (op2 == "*" || op2 == "/") && (op1 == "+" || op1 == "-")
}
// 计算逆波兰表达式
private fun evaluateRPN(tokens: List<String>): Double {
val stack = mutableListOf<Double>()
tokens.forEach { token ->
when {
token.toDoubleOrNull() != null -> stack.add(token.toDouble())
token in listOf("+", "-", "*", "/") -> {
if (stack.size < 2) throw IllegalArgumentException("Insufficient values in the expression.")
val b = stack.removeLast()
val a = stack.removeLast()
stack.add(
when (token) {
"+" -> a + b
"-" -> a - b
"*" -> a * b
"/" -> {
if (b == 0.0) throw ArithmeticException("Cannot divide by zero.")
a / b
}
else -> throw IllegalArgumentException("Unknown operator.")
}
)
}
token == "sqrt" -> {
if (stack.isEmpty()) throw IllegalArgumentException("Insufficient values in the expression.")
val a = stack.removeLast()
if (a < 0) throw ArithmeticException("Cannot take the square root of a negative number.")
stack.add(kotlin.math.sqrt(a))
}
token == "1/x" -> {
if (stack.isEmpty()) throw IllegalArgumentException("Insufficient values in the expression.")
val a = stack.removeLast()
if (a == 0.0) throw ArithmeticException("Cannot take the reciprocal of zero.")
stack.add(1 / a)
}
else -> throw IllegalArgumentException("Unknown token: $token")
}
}
if (stack.size != 1) throw IllegalArgumentException("The user input has too many values.")
return stack.last()
}
}
AppNavigator.kt
package com.example.test_1.ui.navigation
import android.util.Log
import androidx.compose.runtime.Composable
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.test_1.ui.screens.CalculatorScreen
@Composable
fun AppNavigator() {
// 创建一个导航控制器实例,用于在不同的页面之间进行切换
val navController = rememberNavController()
// 设置导航主机,管理页面的跳转逻辑
NavHost(navController = navController, startDestination = "calculator_screen") {
// 定义导航到第一页的路由
composable("calculator_screen") {CalculatorScreen() }
}
/**
* Log为日志打印,一共有5个级别,从低到高依次为:Verbose、Debug、Info、Warn、Error
* 即冗余信息,调试信息,信息,警告,错误
* */
// Log.d("导航", "已完成导航初始化")
}
MainActivity.kt
package com.example.test_1
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview
import com.example.test_1.ui.navigation.AppNavigator
import com.example.test_1.ui.theme.Test_1Theme
open class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Test_1Theme {
AppNavigator()
}
}
}
}
/**
* 预览函数
* ① 也是一个函数,只不过在函数名前加@Preview
* ② 函数不能含参
*/
@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
AppNavigator()
}