食来运转 (WhatFood)

一个完整的 Android 随机食物推荐应用,帮助你决定「今天吃什么」。采用 Kotlin + Jetpack Compose + Room 构建,纯离线可用,同时支持云端数据同步。

🎰 老虎机抽选

加权随机算法 + 三列卷轴动画 + 长按拉杆触发,完全模拟真实老虎机体验

🔍 自由探索

六大餐别横向卡片浏览 + 搜索过滤 + 左右滑动拉黑/喜欢

👤 云端同步

登录后自动同步口味偏好和食物喜厌到云端,换设备无缝恢复

🔐 两步验证

TOTP 标准两步验证,保护账号安全并支持找回密码

🎨 液态玻璃

参考 iOS 26 设计的浮动胶囊导航栏,毛玻璃模糊 + 弹性动画

📦 离线优先

168 种食物数据内置在 APK 中,无网络也能完整使用

技术栈

Kotlin 2.0.21 Jetpack Compose Room 2.6.1 Spring Boot 3.2.5 MySQL 8.0 Retrofit 2.9 JWT TOTP

环境搭建

在开始学习代码之前,需要先准备好开发环境。以下是必需的工具清单。

工具版本用途
Android Studio2024.2+IDE,编写和调试代码
JDK17+编译 Kotlin 代码
Gradle8.7构建工具(自动下载依赖、打包APK)
Android SDKAPI 35Android 平台开发包
MySQL8.0后端数据库(可选)
提示
如果只是学习前端 Android 代码,不需要安装 MySQL。只有想运行完整的登录/同步功能时才需要。
注意
确保 Android Studio 中 SDK Manager 已下载 API 35 的 SDK Platform 和 Intel x86_64 模拟器镜像。

项目结构

整个项目有 31 个 Kotlin 源文件,按功能分层组织。理解这个结构是读懂代码的第一步。

目录树
ShiLaiYunZhuan/
├── build.gradle.kts          # 项目构建配置
├── app/
│   ├── build.gradle.kts      # 模块构建 + 依赖声明
│   └── src/main/
│       ├── AndroidManifest.xml
│       ├── res/              # 图片、字体、颜色等资源
│       └── java/com/example/whatfood/
│           ├── MainActivity.kt          # 应用入口
│           ├── viewmodel/
│           │   └── MainViewModel.kt     # 核心业务逻辑
│           ├── ui/
│           │   ├── WhatFoodApp.kt       # 导航骨架
│           │   ├── auth/AuthPages.kt    # 登录注册页
│           │   ├── components/          # 通用组件
│           │   ├── theme/              # 主题配色
│           │   └── navigation/         # 路由定义
│           ├── data/
│           │   ├── entity/             # 数据库表
│           │   ├── dao/                # 数据库操作
│           │   ├── database/           # 数据库实例
│           │   ├── repository/         # 数据仓库
│           │   └── preferences/        # 本地偏好存储
│           ├── network/               # 网络通信
│           └── pageone_shilaiyunzhuan/ # 老虎机页面
│               └── engine/            # 抽取算法
层级目录职责
UI 层ui/, page*/界面渲染、用户交互
状态层viewmodel/持有状态、处理业务逻辑
数据层data/数据库操作、JSON解析
网络层network/HTTP请求、API调用

Android 基础

如果你只有 Java 基础,这一节帮你理解 Android 的核心概念。

Kotlin 入门

Kotlin 是 Android 的官方语言。以下对照 Java 解释常用语法。

概念KotlinJava 等价
变量val x = 5(不可变)
var y = 5(可变)
final int x = 5;
int y = 5;
空安全val s: String? = nullString s = null;(无编译期保护)
安全调用s?.length(null时返回null)s != null ? s.length() : null
Elviss ?: "默认"s != null ? s : "默认"
数据类data class User(val name: String)手写 getter/setter/equals/hashCode
whenwhen(x){1->"一" else->"其他"}switch(x){case 1:return"一";default:return"其他";}
Lambdalist.filter{it>0}list.stream().filter(x->x>0)
协程suspend fun fetch()Future/Callback(异步更复杂)
FlowFlow<List<T>>LiveData/RxJava Observable
学习建议
如果你有 Java 基础,花 1-2 天看 Kotlin 官方文档就能上手本项目的代码。重点理解 val/var、空安全、data class、suspend 函数。

Jetpack Compose

Compose 是 Android 的现代 UI 框架,,用 Kotlin 代码写界面,不再需要 XML 布局文件。

@Composable 函数

用 @Composable 注解标记的函数会被 Compose 编译器特殊处理,,自动追踪状态变化并更新 UI。

Kotlin
@Composable
fun Greeting(name: String) {
    Text("你好, $name!")
}

Modifier 修饰符链

Modifier 是 Compose 的核心概念,,控制组件的大小、位置、间距、背景、点击等所有外观和行为。

Kotlin
Text("Hello",
    modifier = Modifier
        .padding(16.dp)
        .fillMaxWidth()
        .background(Color.White)
        .clickable { onClick() }
)

State , 响应式状态

使用 remember 和 mutableStateOf 创建状态变量。当状态变化时,所有依赖它的 UI 自动重新渲染。

Kotlin
var count by remember { mutableIntStateOf(0) }
Button(onClick = { count++ }) {
    Text("点击了 $count 次")
}

布局组件

Compose 提供三大基础布局:

  • Box , 层叠布局(子元素可互相覆盖)
  • Column , 垂直排列
  • Row , 水平排列

本项目的老虎机界面就是 Box 中叠了 5 层图片实现的。

Room 数据库

Room 是 Android 官方的 SQLite 抽象层,,让你用 Kotlin 对象操作数据库,不用手写 SQL。

Entity , 数据库表

@Entity 标记的类会自动映射为 SQLite 表。每个属性对应一列。

Kotlin
@Entity(tableName = "foods")
data class FoodEntity(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val name: String,          // 食物名称
    val mainType: String,      // 早餐/正餐/...
    val baseWeight: Float,     // 基础权重
    val isFavorite: Boolean    // 是否喜欢
)

DAO , 数据访问对象

@Dao 接口定义所有数据库操作。Room 在编译时自动生成实现代码。

Kotlin
@Dao
interface FoodDao {
    @Query("SELECT * FROM foods ORDER BY name ASC")
    fun observeAllFoods(): Flow<List<FoodEntity>>  // 响应式查询

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertFood(food: FoodEntity)        // 插入或替换
}

Database , 数据库实例

使用单例模式确保全局只有一个数据库连接。

Kotlin
@Database(entities = [FoodEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun foodDao(): FoodDao

    companion object {
        @Volatile private var INSTANCE: AppDatabase? = null
        fun getInstance(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                INSTANCE ?: Room.databaseBuilder(
                    context, AppDatabase::class.java, "shilaiyunzhuan.db"
                ).build().also { INSTANCE = it }
            }
        }
    }
}

网络通信

本项目使用 Retrofit + OkHttp 与 Spring Boot 后端通信,实现登录注册和数据云端同步。

核心架构

Kotlin
// ApiService.kt , 定义所有 API 端点
interface ApiService {
    @POST("api/auth/login")
    suspend fun login(@Body body: AuthRequest): AuthResponse

    @POST("api/sync/upload")
    suspend fun syncUpload(
        @Header("Authorization") token: String,
        @Body data: SyncUploadData
    ): Map<String, Any>
}

// HttpClient.kt , 创建 Retrofit 实例
object HttpClient {
    val api: ApiService = Retrofit.Builder()
        .baseUrl("http://23.95.190.125:8850/")
        .addConverterFactory(GsonConverterFactory.create())
        .build().create(ApiService::class.java)
}

Retrofit 将其接口方法自动转换为 HTTP 请求。Gson 自动将 JSON ↔ Kotlin 对象互转。

老虎机动画

这是本项目的核心体验,,三列食材图片纵向滚动,最终停在随机抽取的食物上。

动画架构(5层叠加)

从底到顶依次渲染:背景图 → 三列卷轴 → 机身装饰框 → 结果文字 → 拉杆

关键参数
卷轴动画时长 2400ms,列间错开 300ms(依次启动),拉杆动画 23 帧序列

抽取算法

加权随机轮盘赌算法,,不是等概率随机,而是考虑了口味偏好、收藏加权、动态补偿。

算法流程

  1. 4层过滤:mainType → 去黑名单 → 排除上次同组 → 餐段偏好
  2. 加权计算:有效权重 = (baseWeight + dynamicWeight) × 口味倍率 × 收藏倍率
  3. 轮盘赌:按权重比例随机选择(权重大的食物概率更高)
  4. 动态补偿:未抽中的食物 dynamicWeight+1(下次更容易被抽到)

自由探索

用户可以浏览所有食物,搜索特定食物,左右滑动标记喜欢或拉黑。

马卡龙色系

六大餐别各有专属配色:早餐浅粉、正餐浅绿、下午茶浅蓝、夜宵浅橙、零食浅棕、饮品浅紫

滑动手势分离
在大类名称区域左右滑动→切换卡片。在食物条目上左右滑动→拉黑/喜欢。两者不冲突。

登录与同步

用户注册→两步验证初始化→登录→自动从云端下载口味和喜好数据。

数据流

流程
注册 → 两步验证(可选) → 登录 → 获取 JWT Token
    → 清除本地数据 → 从云端下载 → 覆盖本地
    → 用户修改口味/喜好 → 静默上传云端
    → 下载锁(3秒)防止循环覆盖

后端架构

Spring Boot 3.2.5 后端,部署在 RackNerd VPS (Ubuntu 24.04, 1GB RAM)。

API 端点方法说明
/api/auth/registerPOST注册
/api/auth/loginPOST登录(返回JWT)
/api/auth/totpPOST两步验证操作
/api/auth/forgot-passwordPOST忘记密码
/api/sync/uploadPOST上传数据
/api/sync/downloadPOST下载数据

学习资源

推荐的学习路径和参考资料。

7 天学习计划

天数内容
Day 1阅读 docs/00_read_me_first.md,了解项目全貌
Day 2-3学习 Kotlin 基础语法,理解 data class、Flow、协程
Day 4学习 Compose 核心概念(@Composable、Modifier、State)
Day 5理解 MVVM 架构,通读 MainViewModel
Day 6研究老虎机动画和抽取算法
Day 7理解网络层和后端架构
推荐资料
Kotlin 官方文档 (kotlinlang.org)、Compose 官方文档 (developer.android.com)、本项目 docs/ 目录下的13份文档