Files
aerologic-app/CLAUDE.md
2025-11-26 22:46:21 +08:00

857 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md
项目开发指南 - 航空物流App
## 项目概述
**AirLogistics** - Android原生应用航空物流全流程管理
- **包名**: com.lukouguoji.aerologic
- **版本**: 1.7.9 (API 24-30)
- **架构**: MVVM + 组件化 + Kotlin + DataBinding
- **屏幕**: 横屏 1152dp × 720dp
## 快速构建
```bash
./gradlew assembleDebug # 构建Debug版本
./gradlew clean # 清理构建
```
## 核心架构
### MVVM层级
```
Activity → BaseBindingActivity → ViewModel → BaseViewModel/BasePageViewModel → API
```
### 关键基类
- **BaseBindingActivity**: DataBinding + ViewModel自动绑定
- **BaseViewModel**: Loading管理、协程支持
- **BasePageViewModel**: 分页列表(含PageModel)
- **CommonAdapter + BaseViewHolder**: 列表适配器
- **PadSearchLayout**: 搜索区域输入控件
- **PadDataLayout**: 数据展示/编辑控件
### 标准Activity模板
```kotlin
@Route(path = ARouterConstants.ACTIVITY_URL_XXX)
class XxxActivity : BaseBindingActivity<ActivityXxxBinding, XxxViewModel>() {
override fun layoutId() = R.layout.activity_xxx
override fun viewModelClass() = XxxViewModel::class.java
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("页面标题")
binding.viewModel = viewModel
// 初始化UI
}
}
```
### 标准ViewModel模板
**列表页ViewModel:**
```kotlin
class XxxListViewModel : BasePageViewModel() {
val searchText = MutableLiveData<String>()
val itemLayoutId = R.layout.item_xxx
val itemViewHolder = XxxViewHolder::class.java
override fun getData() {
val params = mapOf(
"page" to pageModel.page,
"limit" to pageModel.limit,
"searchText" to searchText.value
).toRequestBody()
launchLoadingCollect({ NetApply.api.getXxxList(params) }) {
onSuccess = { pageModel.handleListBean(it) }
}
}
override fun onItemClick(position: Int, type: Int) {
val bean = pageModel.rv!!.commonAdapter()!!.getItem(position) as XxxBean
// 跳转详情
}
}
```
**详情页ViewModel:**
```kotlin
class XxxDetailsViewModel : BaseViewModel() {
var id = ""
val dataBean = MutableLiveData<XxxBean>()
fun initOnCreated(intent: Intent) {
id = intent.getStringExtra(Constant.Key.ID) ?: ""
getData()
}
private fun getData() {
launchLoadingCollect({ NetApply.api.getXxxDetails(id) }) {
onSuccess = { dataBean.value = it.data ?: XxxBean() }
}
}
}
```
**编辑页ViewModel:**
```kotlin
class XxxAddViewModel : BaseViewModel() {
val pageType = MutableLiveData(DetailsPageType.Add) // 必须用LiveData
var id = ""
val dataBean = MutableLiveData(XxxBean())
fun initOnCreated(intent: Intent) {
pageType.value = DetailsPageType.valueOf(
intent.getStringExtra(Constant.Key.PAGE_TYPE) ?: DetailsPageType.Add.name
)
if (pageType.value != DetailsPageType.Add) {
id = intent.getStringExtra(Constant.Key.ID) ?: ""
loadData()
}
}
fun submit() {
val bean = dataBean.value ?: return
if (bean.name.verifyNullOrEmpty("请输入名称")) return
launchLoadingCollect({
val params = mapOf("id" to id, "name" to bean.name)
.toRequestBody(removeEmptyOrNull = true)
NetApply.api.saveXxx(params)
}) {
onSuccess = {
showToast("保存成功")
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
getTopActivity().finish()
}
}
}
}
```
## 网络请求
### 请求方法
```kotlin
// 带Loading请求
launchLoadingCollect({ NetApply.api.saveXxx(params) }) {
onSuccess = { /* 成功处理 */ }
onFailed = { code, msg -> /* 失败处理 */ }
}
// 无Loading请求(后台刷新)
launchCollect({ NetApply.api.getXxx() }) {
onSuccess = { /* 成功处理 */ }
}
// 参数转换
val params = mapOf("key" to "value").toRequestBody(removeEmptyOrNull = true)
```
### API接口定义
```kotlin
// 位置: module_base/.../http/net/Api.kt
@POST("api/xxx/list")
suspend fun getXxxList(@Body data: RequestBody): BaseListBean<XxxBean>
@POST("api/xxx/details")
suspend fun getXxxDetails(@Query("id") id: String): BaseResultBean<XxxBean>
@POST("api/xxx/save")
suspend fun saveXxx(@Body data: RequestBody): BaseResultBean<SimpleResultBean>
```
## DataBinding + LiveData + ViewModel 核心知识
### 🎯 最关键的设置(最常见错误)
**必须在 Activity 中设置 lifecycleOwner否则 XML 中的 LiveData 不会自动更新 UI**
```kotlin
override fun initOnCreate(savedInstanceState: Bundle?) {
setBackArrow("页面标题")
binding.viewModel = viewModel
// ⚠️ 关键:必须设置,否则 LiveData 无法自动更新 UI
binding.lifecycleOwner = this
}
```
**BaseBindingActivity 已自动设置**,但如果手动使用 DataBinding 时务必记住!
### 📖 ViewModel 中 LiveData 的定义规范
```kotlin
class XxxViewModel : BaseViewModel() {
// ✅ 推荐:对外暴露不可变的 LiveData
private val _dataBean = MutableLiveData<XxxBean>()
val dataBean: LiveData<XxxBean> = _dataBean
// ✅ 简化写法:直接使用 MutableLiveData项目常用
val searchText = MutableLiveData<String>()
val pageType = MutableLiveData(DetailsPageType.Add)
fun loadData() {
// 主线程更新
_dataBean.value = XxxBean()
// 子线程更新(协程中不需要,已在主线程)
// _dataBean.postValue(XxxBean())
}
}
```
### 📝 XML 中 LiveData 的绑定方式
#### 1. 单向绑定 `@{}`只显示ViewModel → UI
```xml
<layout>
<data>
<variable
name="viewModel"
type="com.lukouguoji.xxx.XxxViewModel" />
</data>
<!-- LiveData 自动解包:直接访问 value -->
<TextView
android:text="@{viewModel.dataBean.name}" />
<!-- 条件判断 -->
<View
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" />
<!-- 空值处理 -->
<TextView
android:text="@{viewModel.dataBean.name ?? `默认值`}" />
<!-- 字符串拼接(使用反引号) -->
<TextView
android:text="@{`姓名:` + viewModel.dataBean.name}" />
</layout>
```
#### 2. 双向绑定 `@={}`可编辑UI ↔ ViewModel
```xml
<!-- EditText 双向绑定 -->
<EditText
android:text="@={viewModel.searchText}" />
<!-- PadSearchLayout 双向绑定 -->
<PadSearchLayout
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.waybillNo}" />
<!-- PadDataLayout 双向绑定 -->
<PadDataLayout
type="@{DataLayoutType.INPUT}"
value="@={viewModel.dataBean.name}" />
```
**双向绑定要求**
- 字段必须是 `MutableLiveData`
- 用户输入时自动更新 ViewModel 的值
- ViewModel 更新值时自动更新 UI
#### 3. 点击事件绑定
```xml
<!-- Lambda 表达式(推荐) -->
<Button
android:onClick="@{() -> viewModel.submit()}" />
<!-- 带参数 -->
<Button
android:onClick="@{(v) -> viewModel.onItemClick(v, 1)}" />
<!-- 自定义监听器 -->
<PadSearchLayout
setOnIconClickListener="@{(v) -> viewModel.scanWaybill()}" />
```
### ⚠️ DataBinding 常见错误与解决方法
#### 错误 1忘记设置 lifecycleOwner
```kotlin
// ❌ 错误LiveData 变化但 UI 不更新
override fun initOnCreate(savedInstanceState: Bundle?) {
binding.viewModel = viewModel
// 忘记设置 lifecycleOwner
}
// ✅ 正确:必须设置
override fun initOnCreate(savedInstanceState: Bundle?) {
binding.viewModel = viewModel
binding.lifecycleOwner = this // 关键!
}
```
#### 错误 2在 XML 中传递 LiveData 而非值
```xml
<!-- ❌ 错误:某些属性只接受值,不接受 LiveData -->
<View
android:visibility="@{viewModel.isVisible}" />
<!-- 如果 isVisible 是 MutableLiveData<Boolean>,可能报错 -->
<!-- ✅ 正确DataBinding 会自动解包 LiveData -->
<View
android:visibility="@{viewModel.isVisible ? View.VISIBLE : View.GONE}" />
<!-- 三元表达式会自动解包 -->
```
#### 错误 3import 类型错误
```xml
<!-- ❌ 错误 -->
<import type="android.view.View.VISIBLE" />
<!-- ✅ 正确 -->
<import type="android.view.View" />
```
#### 错误 4字符串未使用反引号
```xml
<!-- ❌ 错误:普通引号会被识别为 XML 属性 -->
<TextView
android:text="@{"姓名:" + viewModel.name}" />
<!-- ✅ 正确:使用反引号 ` -->
<TextView
android:text="@{`姓名:` + viewModel.name}" />
```
#### 错误 5访问 LiveData 的 value 属性
```xml
<!-- ❌ 错误DataBinding 会自动解包,不需要 .value -->
<TextView
android:text="@{viewModel.dataBean.value.name}" />
<!-- ✅ 正确:直接访问 -->
<TextView
android:text="@{viewModel.dataBean.name}" />
```
#### 错误 6修改对象属性后 UI 不更新
```kotlin
// ❌ 错误修改对象内部属性LiveData 不会触发更新
val bean = dataBean.value
bean?.name = "新名称"
// UI 不会更新,因为 LiveData 的引用没变
// ✅ 正确:重新赋值 LiveData
val bean = dataBean.value?.copy(name = "新名称")
dataBean.value = bean
// ✅ 或者:使用 MutableLiveData + ObservableField
// 但项目中更推荐上面的方式
```
#### 错误 7在 XML 中调用 suspend 函数
```xml
<!-- ❌ 错误suspend 函数不能直接在 XML 中调用 -->
<Button
android:onClick="@{() -> viewModel.loadDataSuspend()}" />
<!-- ✅ 正确:在 ViewModel 中包装 -->
```
```kotlin
// ViewModel 中
fun loadData() { // 普通函数
launchLoadingCollect({ NetApply.api.getXxx() }) {
onSuccess = { dataBean.value = it.data }
}
}
```
### 🔍 XML DataBinding 调试技巧
#### 1. 检查 Binding 类是否生成
```bash
# 清理重新构建
./gradlew clean
./gradlew assembleDebug
```
#### 2. 查看 DataBinding 错误
- XML 中的错误可能不会立即显示
- 需要 Build 项目才能看到详细错误信息
- 错误信息通常在 Build Output 中
#### 3. 常见错误提示
```
Cannot find the setter for attribute 'android:text' with parameter type...
→ 检查属性类型是否匹配
Unresolved reference: viewModel
→ 检查 <variable> 声明和 import
cannot generate view binders
→ 检查 XML 语法错误,特别是 @{} 表达式
```
### 📋 DataBinding 开发检查清单
- ✅ Activity 中设置 `binding.lifecycleOwner = this`
- ✅ ViewModel 中需要双向绑定的字段使用 `MutableLiveData`
- ✅ XML 中字符串使用反引号 `` ` ``
- ✅ XML 中不访问 LiveData 的 `.value` 属性
- ✅ 修改对象属性后重新赋值 LiveData触发更新
- ✅ 点击事件使用 Lambda 表达式
- ✅ 正确 import 枚举和常量类
- ✅ XML 错误需要 Build 项目才能看到
## 核心UI组件
### PadSearchLayout - 搜索输入框
```xml
<!-- 文本输入+扫码 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
type="@{SearchLayoutType.INPUT}"
value="@={viewModel.waybillNo}"
hint="@{`请输入运单号`}"
icon="@{@mipmap/scan_code}"
setOnIconClickListener="@{(v)-> viewModel.scanWaybill()}" />
<!-- 日期选择 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
type="@{SearchLayoutType.DATE}"
value="@={viewModel.date}"
icon="@{@mipmap/calendar}" />
<!-- 下拉选择 -->
<com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout
type="@{SearchLayoutType.SPINNER}"
list="@{viewModel.statusList}"
value="@={viewModel.status}" />
```
**类型**: `INPUT` / `INTEGER` / `SPINNER` / `DATE`
### PadDataLayout - 数据展示/编辑
```xml
<!-- 文本输入 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
type="@{DataLayoutType.INPUT}"
title='@{"运单号:"}'
titleLength="@{5}"
value='@={viewModel.bean.waybillNo}'
enable="@{viewModel.pageType != DetailsPageType.Details}"
required="@{true}"
maxLength="@{11}" />
<!-- 下拉选择 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
type="@{DataLayoutType.SPINNER}"
title='@{"状态:"}'
list="@{viewModel.statusList}"
value='@={viewModel.bean.status}' />
<!-- 多行输入 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayout
type="@{DataLayoutType.INPUT}"
inputHeight="@{100}"
value='@={viewModel.bean.remark}' />
```
**类型**: `INPUT` / `SPINNER` / `DATE`
## 开发检查清单
### ⚠️ 重要提醒
**新建Activity后必须在AndroidManifest.xml中注册否则会报ActivityNotFoundException错误**
### 列表页开发(8步)
1. **创建Bean** (`module_base/.../bean/XxxBean.kt`)
2. **添加API接口** (`Api.kt``getXxxList()`)
3. **创建ViewHolder** (继承`BaseViewHolder`)
4. **创建ViewModel** (继承`BasePageViewModel`)
5. **创建Activity** (继承`BaseBindingActivity`)
6. **创建Layout** (`activity_xxx_list.xml` + `item_xxx.xml`)
7. **注册路由** (`ARouterConstants`)
8. **⚠️ 在AndroidManifest.xml中注册Activity** (`app/src/main/AndroidManifest.xml`)
**AndroidManifest.xml注册示例:**
```xml
<!-- 在app/src/main/AndroidManifest.xml的<application>标签内添加 -->
<activity
android:name="com.lukouguoji.gnc.page.xxx.XxxActivity"
android:configChanges="orientation|keyboardHidden"
android:exported="false"
android:screenOrientation="userLandscape" />
```
**关键代码:**
```kotlin
// Activity中绑定分页
viewModel.pageModel.bindSmartRefreshLayout(
binding.srl, binding.recyclerView, viewModel, this
)
binding.recyclerView.addOnItemClickListener(viewModel)
```
### 详情页开发(5步)
1. **添加API接口** (`getXxxDetails()`)
2. **创建ViewModel** (继承`BaseViewModel`)
3. **创建Activity** (含`companion object`静态start方法)
4. **创建Layout**
5. **⚠️ 在AndroidManifest.xml中注册Activity**
**静态启动方法:**
```kotlin
companion object {
@JvmStatic
fun start(context: Context, id: String) {
val starter = Intent(context, XxxDetailsActivity::class.java)
.putExtra(Constant.Key.ID, id)
context.startActivity(starter)
}
}
```
### 编辑页开发(6步)
1. **添加API接口** (`saveXxx()` + `getXxxDetails()`)
2. **创建ViewModel** (`pageType`使用`MutableLiveData`)
3. **创建Activity** (多个静态start方法: `startForAdd/Edit/Details`)
4. **创建Layout** (根据`pageType`控制enable)
5. **FlowBus发送刷新事件**
6. **⚠️ 在AndroidManifest.xml中注册Activity**
**Activity多入口:**
```kotlin
companion object {
@JvmStatic
fun startForAdd(context: Context) {
val starter = Intent(context, XxxAddActivity::class.java)
.putExtra(Constant.Key.PAGE_TYPE, DetailsPageType.Add.name)
context.startActivity(starter)
}
@JvmStatic
fun startForEdit(context: Context, id: String) {
/* ... DetailsPageType.Modify ... */
}
}
```
## 常见业务场景
### 扫码
```kotlin
fun scanWaybill() {
ScanModel.startScan(getTopActivity(), Constant.RequestCode.WAYBILL)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == Constant.RequestCode.WAYBILL && resultCode == Activity.RESULT_OK) {
waybillNo.value = data?.getStringExtra(Constant.Result.CODED_CONTENT)
search()
}
}
```
### 图片上传
```kotlin
val result = UploadUtil.upload(filePath)
if (result.verifySuccess()) {
val imageUrl = result.data?.newName ?: "" // 注意是newName不是url
}
```
### 列表刷新事件
```kotlin
// 发送事件(在ViewModel中)
viewModelScope.launch {
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).emit("refresh")
}
// 接收事件(在Activity中)
import com.lukouguoji.module_base.impl.observe // 必须导入
FlowBus.with<String>(ConstantEvent.EVENT_REFRESH).observe(this) {
viewModel.refresh()
}
```
## 常用扩展函数
```kotlin
// Toast
showToast("提示信息")
// 验证非空
if (text.verifyNullOrEmpty("请输入内容")) return
// 空处理
val text = nullableString.noNull("默认值")
// 日期格式化
val dateStr = Date().formatDate() // "2025-11-12"
// 权限申请
permission(Manifest.permission.CAMERA) { openCamera() }
```
## 常见编译错误
### 1. DetailsPageType包名错误
```xml
<!-- ❌ 错误 -->
<import type="com.lukouguoji.module_base.constant.DetailsPageType" />
<!-- ✅ 正确 -->
<import type="com.lukouguoji.module_base.common.DetailsPageType" />
```
### 2. DataLayoutType枚举值错误
```xml
<!-- ❌ 错误: INTEGER不存在 -->
type="@{DataLayoutType.INTEGER}"
<!-- ✅ 正确: 使用INPUT -->
type="@{DataLayoutType.INPUT}"
```
**可用类型**: `INPUT` / `SPINNER` / `DATE`
### 3. DetailsPageType枚举值错误
```kotlin
// ❌ 错误: Edit不存在
DetailsPageType.Edit
// ✅ 正确: 使用Modify
DetailsPageType.Modify
```
**可用类型**: `Add` / `Modify` / `Details`
### 4. IOnItemClickListener包名错误
```kotlin
// ❌ 错误
import com.lukouguoji.module_base.impl.IOnItemClickListener
// ✅ 正确
import com.lukouguoji.module_base.interfaces.IOnItemClickListener
```
### 5. FlowBus使用错误
```kotlin
// ❌ 错误: observe需要单独导入
import com.lukouguoji.module_base.impl.FlowBus
// ✅ 正确
import com.lukouguoji.module_base.impl.FlowBus
import com.lukouguoji.module_base.impl.observe
// ❌ 错误: emit必须在协程中
FlowBus.with<String>(event).emit("data")
// ✅ 正确
viewModelScope.launch {
FlowBus.with<String>(event).emit("data")
}
```
### 6. 图片上传字段错误
```kotlin
// ❌ 错误: UploadBean没有url字段
val imageUrl = result.data?.url
// ✅ 正确: 使用newName字段
val imageUrl = result.data?.newName
```
### 7. pageType必须用LiveData
```kotlin
// ❌ 错误: DataBinding无法绑定
var pageType: DetailsPageType = DetailsPageType.Add
// ✅ 正确: 使用LiveData
val pageType = MutableLiveData(DetailsPageType.Add)
```
### 8. RecyclerView不支持items属性
```xml
<!-- ❌ 错误: items属性会导致编译错误 -->
<RecyclerView
items="@{viewModel.list}" />
<!-- ✅ 正确: 在Activity中手动更新 -->
<RecyclerView android:id="@+id/recyclerView" />
```
```kotlin
// Activity中
viewModel.list.observe(this) { data ->
binding.recyclerView.commonAdapter()?.refresh(data)
}
```
### 9. Constant.Key.PAGE_TYPE未定义
`module_base/.../common/Constant.kt`中添加:
```kotlin
object Key {
const val ID = "id"
const val PAGE_TYPE = "pageType" // 添加这个
}
```
### 10. 资源引用错误(最常见的编译失败原因)
```xml
<!-- ❌ 错误: 引用不存在的资源会导致资源合并失败 -->
<TextView
android:background="@drawable/bg_custom"
android:textColor="@color/custom_color"
android:text="@string/custom_text" />
```
**问题原因**:
- 在布局文件中引用了项目中不存在的 `drawable``color``string` 等资源
- 导致构建时资源合并失败无法生成R文件
- 报错信息: `Resource compilation failed``AAPT: error: resource ... not found`
**正确做法**:
1. **使用已存在的资源** - 先检查资源是否存在
```bash
# 查找drawable资源
find module_base/src/main/res/drawable -name "bg_custom*"
# 查找color定义
grep "custom_color" module_base/src/main/res/values/colors.xml
# 查找string定义
grep "custom_text" module_base/src/main/res/values/strings.xml
```
2. **主动创建缺失的资源** - 如果不存在则创建
```xml
<!-- 创建 drawable: module_base/src/main/res/drawable/bg_custom.xml -->
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white"/>
<corners android:radius="4dp"/>
</shape>
<!-- 添加 color: module_base/src/main/res/values/colors.xml -->
<color name="custom_color">#333333</color>
<!-- 添加 string: module_base/src/main/res/values/strings.xml -->
<string name="custom_text">自定义文本</string>
```
3. **使用项目现有资源** - 避免重复创建
常用资源列表:
- **背景**: `bg_white_radius_8`, `bg_gray_radius_4`, `bg_primary_radius_4`
- **颜色**: `white`, `black`, `colorPrimary`, `text_normal`, `text_gray`, `text_red`
- **文字**: 优先直接写中文字符串,少用 string 资源
**检查清单**:
- ✅ 创建/修改布局文件前,确保引用的资源都存在
- ✅ 新增drawable时在正确的module下创建通常是`module_base`
- ✅ 新增color/string时添加到对应的values文件中
- ✅ 使用IDE的自动补全和资源预览功能避免拼写错误
- ✅ 构建失败时,优先检查资源引用问题
## 错误排查流程
1. **资源引用错误** → 检查drawable/color/string是否存在主动创建缺失资源
2. **DataBinding错误** → 检查import包名、枚举值
3. **Unresolved reference** → 检查import语句、常量定义
4. **suspend function错误** → 在`viewModelScope.launch`中调用
5. **仍有问题**`./gradlew clean` 后重新构建
## 快速修复命令
```bash
# 查找DetailsPageType位置
grep -r "enum class DetailsPageType" module_base/src --include="*.kt"
# 查找IOnItemClickListener位置
find module_base/src -name "IOnItemClickListener.kt"
# 查找DataLayoutType枚举值
grep -A 5 "enum class DataLayoutType" module_base/src --include="*.kt"
```
## 开发原则
-**资源引用必须存在** - 创建/修改布局前确保drawable/color/string资源真实存在或主动创建
-**必须设置 lifecycleOwner** - Activity 中 `binding.lifecycleOwner = this`BaseBindingActivity 已自动设置)
- ✅ 优先使用项目现有基类和封装
- ✅ 充分利用PadDataLayout和PadSearchLayout组件
- ✅ 遵循统一命名规范
- ✅ pageType用LiveData不用普通变量
- ✅ XML中字符串拼接使用反引号不访问LiveData的.value属性
- ✅ 修改对象属性后重新赋值LiveData才能触发UI更新
- ✅ FlowBus.emit()必须在协程中调用
- ✅ 图片上传使用newName字段
- ✅ RecyclerView手动更新adapter不用items属性
- ✅ 新建Activity后必须在AndroidManifest.xml中注册
## 技术栈
- Kotlin + 协程 1.6.0
- Retrofit 2.6.1 + OkHttp 3.12.12
- DataBinding + LiveData
- ARouter 1.5.2
- SmartRefreshLayout 2.0.3
- Glide 4.15.1
---
**签名配置**: `key.jks` / 密码: `123321` / 别名: `key`
- 当使用PadDataLayoutNew 控件的时候titleLength 通常设置为5
- 在每个页面布局时,我会给你截图,请务必尽可能还原图片上的页面设计,而不是推测、假想。如果有困难(例如图片看不清,不明白的地方)一律要询问我,禁止自己想象。
- layout xml 最佳布局实践参考:
- module_gjc/src/main/res/layout/activity_gjc_weighing_record_details.xml
- module_gjc/src/main/res/layout/item_gjc_check_in_record.xml
- module_gjc/src/main/res/layout/activity_gjc_box_weighing_details.xml
- module_gjc/src/main/res/layout/activity_gjc_inspection.xml