Android 学习记录(编辑中)

最近更新于 2024-05-10 10:05

1 前言

前面 9 月份因为接单的原因,现学了 Java,基础上应该没啥大问题了。前几天又折腾了下 ESP32,搞了个简单的时钟显示,支持联网同步时间,但是考虑到要连接的 WiFi 在程序里写死,如果后期要改连接的 WiFi 还得重写修改后下载程序,并不方便。结合家电的体验,空调、热水器等等连接 WiFi 是通过手机设置然后使用蓝牙传输过去,这样就挺方便。因此打算抽取一些空余时间,先学习一下 Kotlin,再入 Android。
现在 Android 开发的趋势是向着 Kotlin,Kotlin 本身是基于 JVM 的,和 Java 关联性较大。因此还是要先了解一下 Kotlin 的语法,再入手学习 Android 开发。我觉得学习 Android 开发,应该当作另外一门语言来学习,并不是纯粹的 Java/Kotlin… 语言的开发,用的是 Android 的库,具体开发规则也是看 Android 的规定,只是基本语法上是具体语言的。有点像 Win32,用 C/C++ 的语法没问题,但是入口函数的原型和纯 C/C++ 的标准规定是不一样的,具体内容开发上是依赖于 Windows 的规定。
中间一段时间去熟悉了一下 Kotlin 基础语法,用的 IDEA,发现用 IDEA 也可以进行 Android 开发(AS 本身就是基于 IDEA 开发的),所以就直接改用 IDEA 了,界面布局也没啥太大区别。
在准备上手 Android 的时候,又发现了界面可以使用代码编写,相当于前端后端都可以用 Kotlin 写(使用 Jetpack Compose 框架),这种写法感觉挺像 Python 的 Tkinter,界面也用 Python 写。传统的开发是用 xml 描述界面,后端用 Java/Kotlin。我是相当于业余爱好做点东西玩,也就没有必要再去学传统开发方式,直接上手 Jetpack Compose 开发就行。

Android 开发学习可以参考官方文档:https://developer.android.google.cn/guide?hl=zh-cn

2 环境

Android Studio 2022.3.1 Patch 3
IDEA 2023.1

Compile SDK 33
Compose UI 1.2.0
com.android.application 7.4.1
com.android.library 7.4.1
org.jetbrains.kotlin.android 1.7.0

测试:Android 13(子系统或 MIUI14)

3 调试

AS 提供有安卓模拟器,但是我试了一下,体验很不好,太吃资源(可能我电脑配置不行),非常卡顿。所以采用别的方法运行调试。

3.1 实体手机

基于 红米 note 13 Pro(MIUI 14,Android 13)验证

用自己的手机调试,可以有线或无线,不管哪种方式都需要开启开发者选项,有线的话还要打开 USB 调试模式,另外我手机上还有一项 USB 安装,这个也要勾选,不然调试没法安装应用到手机。
(开发者模式默认都会隐藏,连续点击“系统版本”就会在设置中出现开发者选项)
file

file

无线的话,手机通过 WiFi 连接,保证电脑在同一个局域网内(同一个路由器下面),然后在开发者选项里有个无线调试要打开,可以扫二维码或配对码连接。
file

file

运行 AS 提供的 demo
file

file

3.2 子系统

我电脑上安装了 WSA(Android 13),可以直接在电脑上安装应用包,直接运行安卓程序。注意在设置中打开开发者选项以及其中的 USB 调试
file

file

先配置一下平台工具(platform-tools)的环境变量,如果安装的时候选的标准(standard),那么 SDK 安装路径就在 C 盘。配置好环境变量,以便后续使用 adb 等工具
file

在 AS 中,点开终端
file

在子系统已经运行的情况下,执行 adb 命令连接

adb connect 127.0.0.1:58526

子系统这边同意连接就行
file

在运行设备里就能看到子系统了
file

运行 AS 提供的 demo
file

4 杂项说明

4.1 长度单位

  • dp(Density-independent Pixels):独立像素,也称为设备独立像素。dp是一种与屏幕密度无关的单位,它可以根据不同的屏幕密度自动进行适配。在布局文件中定义控件的宽、高等属性时,一般会使用dp作为单位。在160dpi的屏幕上,1dp等于1px。

  • mm(Millimeters):毫米,是一种物理尺寸单位。在Android开发中,不常用于布局,主要用于一些需要精确测量的场景。

  • pt(Points):点,也是一种物理尺寸单位。在Android开发中,同样不常用于布局,主要用于一些需要精确测量的场景。1pt等于1/72英寸。

  • px(Pixels):像素,是屏幕上的最小显示单元。在Android开发中,px表示实际的像素点。但由于不同设备的屏幕密度不同,使用px作为单位可能导致在不同设备上显示效果不一致,因此不推荐在布局中直接使用px作为单位。

  • sp(Scaled Pixels):比例像素,也称为可缩放像素。sp是一种与缩放无关的像素单位,通常用于定义字体大小。与dp类似,sp也可以根据屏幕密度进行适配,并且还可以随着系统字体大小的设置进行缩放。如果希望文字大小随着系统字体设置的改变而变化,可以使用sp作为单位。

4.2 快捷键

在设置里有一项 keymap 就是设置快捷键的,里面也提供了几种常用的快捷键方案,或者手动设置具体项目。
file

我之前搞 Python、C 语言、C++、Java 等等都是使用 VScode,已经完全习惯了 VScode 的快捷键方案,这里可以通过 Jetbrains 的插件来支持。
file

安装插件后,在 keymap 处就能选 VScode 方案的快捷键了
file

5 探索

file

5.1 hello world

package com.iyatt.greetingcard

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.iyatt.greetingcard.ui.theme.GreetingCardTheme

@Composable // 标记这是一个可组合函数(用于构建 UI 的组件)
fun Greeting(name: String)
{
    Text(
        text = "你好 $name!",
        modifier = Modifier.padding(20.dp) // 内边距
    )
}

@Preview(showBackground = true) // 支持预览
@Composable
fun DefaultPreview()
{
    GreetingCardTheme {
        Greeting("小红")
    }
}

class MainActivity : ComponentActivity()
{
    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContent {
            GreetingCardTheme { // GreetingCard 是项目名,xxxxxTheme 跟随项目名(自动创建的自定义主题)
                Surface( // 组件
                    // modifier = Modifier.fillMaxSize(), // 组件大小设置为最大
                    // color = MaterialTheme.colors.background // 将背景颜色作为组件的颜色
                    color = Color.Blue // 组件颜色设置为蓝色
                )
                {
                    Greeting("小明")
                }
            }
        }
    }
}

Android 子系统调试运行效果
file

预览
file

5.2 列布局

package com.iyatt.happybirthday

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.dp
import com.iyatt.happybirthday.ui.theme.HappyBirthdayTheme

@Composable
fun GreetingText(msg: String, from: String, modifier: Modifier = Modifier)
{
    Column( // 列布局(垂直)
        verticalArrangement = Arrangement.Center, // 垂直方向居中对齐
        modifier = modifier // 修饰符,设置大小、边距、颜色等
    )
    {
        Text(
            text = msg, // 要显示的文本内容
            fontSize = 100.sp, // 字体大小
            lineHeight = 116.sp, // 行高
            textAlign = TextAlign.Center // 文本居中对齐
        )
        Text(
            text = from,
            fontSize = 36.sp,
            modifier = Modifier
                .padding(16.dp) // 内边距
                .align(alignment = Alignment.End) // 在其父容器的末尾对齐
        )
    }
}

class MainActivity : ComponentActivity()
{
    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContent {
            HappyBirthdayTheme {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background)
                {
                    GreetingText("小明,生日快乐!", "来自小红")
                }
            }
        }
    }
}

file

5.3 添加图片

使用的图片:https://github.com/google-developer-training/basic-android-kotlin-compose-birthday-card-app/blob/main/app/src/main/res/drawable-nodpi/androidparty.png

下载后可以拖动到资源框进行导入
file

file

file

在开发中一般会把资源和业务逻辑代码分离,比如图片、字符串、颜色定义等等。
这个案例在上一个基础上添加了图片,另外将字符串分离单独保存。
java 是源码目录,res 就是资源目录,drawable 下面放置的是图片
file

values 下面默认有颜色、字符串、主题
file

这个案例中单独定义的字符串
strings.xml

<resources>
    <string name="app_name">happyBirthday</string>
    <string name="happy_birthday_text">小明,生日快乐!</string>
    <string name="signature_text">来自小红</string>
</resources>

业务逻辑代码

package com.iyatt.happybirthday

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.sp
import androidx.compose.ui.unit.dp
import com.iyatt.happybirthday.ui.theme.HappyBirthdayTheme

@Composable
fun GreetingText(msg: String, from: String, modifier: Modifier = Modifier)
{
    Column( // 列布局(垂直)
        verticalArrangement = Arrangement.Center, // 垂直方向居中对齐
        modifier = modifier // 修饰符,设置大小、边距、颜色等
    )
    {
        Text(
            text = msg, // 要显示的文本内容
            fontSize = 100.sp, // 字体大小
            lineHeight = 116.sp, // 行高
            textAlign = TextAlign.Center // 文本居中对齐
        )
        Text(
            text = from,
            fontSize = 36.sp,
            modifier = Modifier
                .padding(16.dp) // 内边距
                .align(alignment = Alignment.End) // 在其父容器的末尾对齐
        )
    }
}

@Composable
fun GreetingImage(msg: String, from: String, modifier: Modifier = Modifier)
{
    Box(modifier)
    {
        Image(
            painter = painterResource(id = R.drawable.androidparty), // 指定图像资源
            contentDescription = null, // 图像描述(可以用于支持屏幕阅读,对无障碍功能的支持)
            contentScale = ContentScale.Crop, // 图像缩放方式:剪裁
            alpha = 0.5F // 图像透明度
        )
        GreetingText( // 调用自定义的文本显示
            msg = msg,
            from = from,
            modifier = Modifier
                .fillMaxSize()
                .padding(8.dp)
        )
    }
}

class MainActivity : ComponentActivity()
{
    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContent {
            HappyBirthdayTheme {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background)
                {
                    GreetingImage(
                        stringResource(R.string.happy_birthday_text), // 传入 strings.xml 保存的字符串
                        stringResource(R.string.signature_text)
                    )
                }
            }
        }
    }
}

file

5.4 布局 – 文章

使用到的图片资源:https://github.com/google-developer-training/basic-android-kotlin-compose-training-practice-problems/blob/main/Unit%201/Pathway%203/ComposeArticle/app/src/main/res/drawable-nodpi/bg_compose_background.png

strings.xml

<resources>
    <string name="app_name">文章</string>

    <string name="title_jetpack_compose_tutorial">Jetpack Compose 教程</string>
    <string name="compose_short_desc">Jetpack Compose是一个用于构建原生Android UI的现代工具包。Compose使用更少的代码、强大的工具和直观的Kotlin API简化并加速了Android上的UI开发。</string>
    <string name="compose_long_desc">在本教程中,您将构建一个具有声明性函数的简单UI组件。你调用Compose函数来说明你想要什么元素,剩下的由Compose编译器完成。Compose是围绕可组合函数构建的。这些函数允许您以编程方式定义应用程序的UI,因为它们允许您描述它的外观并提供数据依赖关系,而不是专注于UI的构建过程,例如初始化元素,然后将其附加到父级。若要创建可组合函数,请在函数名称中添加@Composable注释。</string>
</resources>

代码

package com.iyatt.article

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.iyatt.article.ui.theme.文章Theme

@Composable
private fun ArticleCard(
    title: String, shortDescription: String, longDescription: String,
    imagePainter: Painter, modifier: Modifier = Modifier
)
{
    Column(modifier = modifier)
    {
        Image(painter = imagePainter, contentDescription = null)
        Text(text = title, modifier = Modifier.padding(16.dp), fontSize = 24.sp)
        Text(
            text = shortDescription, modifier = Modifier.padding(start = 16.dp, end = 16.dp),
            textAlign = TextAlign.Justify
        ) // 两端对齐
        Text(text = longDescription, modifier = Modifier.padding(16.dp), textAlign = TextAlign.Justify)
    }
}

@Composable
fun ArticleApp()
{
    ArticleCard(
        title = stringResource(R.string.title_jetpack_compose_tutorial),
        shortDescription = stringResource(R.string.compose_short_desc),
        longDescription = stringResource(R.string.compose_long_desc),
        imagePainter = painterResource(R.drawable.bg_compose_background)
    )
}

class MainActivity : ComponentActivity()
{
    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContent {
            文章Theme {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background)
                {
                    ArticleApp()
                }
            }
        }
    }
}

file

5.5 布局 – 任务完成页面

使用到的图片资源:https://github.com/google-developer-training/basic-android-kotlin-compose-training-practice-problems/blob/main/Unit%201/Pathway%203/TaskCompleted/app/src/main/res/drawable/ic_task_completed.png

strings.xml

<resources>
    <string name="app_name">任务完成页面</string>

    <string name="all_task_completed">所有任务已完成</string>
    <string name="nice_work">干得不错!</string>
</resources>

代码

package com.iyatt.tc

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.iyatt.tc.ui.theme.任务完成页面Theme

@Composable
fun TaskCompletedScreen()
{
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .fillMaxHeight(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    )
    {
        Image(painter = painterResource(R.drawable.ic_task_completed), contentDescription = null)
        Text(
            text = stringResource(R.string.all_task_completed),
            modifier = Modifier.padding(top = 24.dp, bottom = 8.dp),
            fontWeight = FontWeight.Bold // 粗体
        )
        Text(
            text = stringResource(R.string.nice_work),
            fontSize = 16.sp
        )
    }
}

class MainActivity : ComponentActivity()
{
    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContent {
            任务完成页面Theme {
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background)
                {
                    TaskCompletedScreen()
                }
            }
        }
    }
}

file

5.6 布局 – 象限

strings.xml

<resources>
    <string name="app_name">象限</string>

    <string name="first_title">文本</string>
    <string name="first_description">显示文本并遵循推荐的 Material Design 准则。</string>
    <string name="second_title">图片</string>
    <string name="second_description">创建一个可组合的布局和绘制给定的 Painter 对象。</string>
    <string name="third_title">行</string>
    <string name="third_description">子项按水平方向放置的可组合布局。</string>
    <string name="fourth_title">列</string>
    <string name="fourth_description">子项按垂直方向放置的可组合布局。</string>
</resources>

代码

package com.iyatt.quadrant

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.iyatt.quadrant.ui.theme.象限Theme

@Composable
private fun ComposableInfoCard(
    title: String,
    description: String,
    backgroundColor: Color,
    modifier: Modifier = Modifier
)
{
    Column(
        modifier = modifier
            .fillMaxSize()
            .background(backgroundColor)
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    )
    {
        Text(text = title, modifier = Modifier.padding(bottom = 16.dp))
        Text(text = description, textAlign = TextAlign.Justify)
    }
}

@Composable
fun QuadrantApp()
{
    Column(Modifier.fillMaxSize())
    {
        Row(Modifier.weight(1F))
        {
            ComposableInfoCard(
                title = stringResource(R.string.first_title),
                description = stringResource(R.string.first_description),
                backgroundColor = Color(0xFFEADDFF),
                modifier = Modifier.weight(1F)
            )
            ComposableInfoCard(
                title = stringResource(R.string.second_title),
                description = stringResource(R.string.second_description),
                backgroundColor = Color(0xFFD0BCFF),
                modifier = Modifier.weight(1F)
            )
        }
        Row(Modifier.weight(1F))
        {
            ComposableInfoCard(
                title = stringResource(R.string.third_title),
                description = stringResource(R.string.third_description),
                backgroundColor = Color(0xFFB69DF8),
                modifier = Modifier.weight(1F)
            )
            ComposableInfoCard(
                title = stringResource(R.string.fourth_title),
                description = stringResource(R.string.fourth_description),
                backgroundColor = Color(0xFFF6EDFF),
                modifier = Modifier.weight(1F)
            )
        }
    }
}

class MainActivity : ComponentActivity()
{
    override fun onCreate(savedInstanceState: Bundle?)
    {
        super.onCreate(savedInstanceState)
        setContent {
            象限Theme {
                // A surface container using the 'background' color from the theme
                Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background)
                {
                    QuadrantApp()
                }
            }
        }
    }
}

file

Android 学习记录(编辑中)
Scroll to top