# 充值系统 (Recharge System)
这是一个基于Android的充值系统项目,采用模块化架构设计,使用Room数据库进行数据持久化存储。
## 项目结构
项目包含以下模块:
- `app`: 主应用模块
- `baselibrary`: 基础库,包含基本工具类和通用组件
- `generallibrary`: 通用功能库,包含数据库操作等通用功能
- `henanlibrary`: 河南地区特定功能模块
- `qihealonelibrary`: 齐河单机版功能模块
- `qiheonlinelibrary`: 齐河在线版功能模块
- `ocridcardlibrary`: 身份证识别模块
- `easysocket`: Socket通信模块
- `pickerviewlibrary`: 选择器视图库
## 技术栈
- 开发语言:Kotlin & Java
- 数据库:Room
- 网络通信:Retrofit & Socket
- 依赖注入:未使用
- 异步处理:RxJava
- 权限管理:XXPermissions
- 视图绑定:
- ViewBinding:用于安全高效地访问视图
- DataBinding:用于数据驱动UI的MVVM架构实现
## 视图绑定配置
### ViewBinding
在模块级build.gradle中启用ViewBinding:
```gradle
android {
buildFeatures {
viewBinding true
}
}
```
使用示例:
```kotlin
class ExampleActivity : AppCompatActivity() {
private lateinit var binding: ActivityExampleBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityExampleBinding.inflate(layoutInflater)
setContentView(binding.root)
// 直接访问视图
binding.textView.text = "Hello ViewBinding"
}
}
```
### DataBinding
在模块级build.gradle中启用DataBinding:
```gradle
android {
buildFeatures {
dataBinding true
}
}
```
使用示例:
1. 布局文件:
```xml
```
2. Activity中使用:
```kotlin
class ExampleActivity : AppCompatActivity() {
private lateinit var binding: ActivityExampleBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_example)
binding.user = User("张三")
binding.lifecycleOwner = this
}
}
```
### 最佳实践
- ViewBinding:
- 用于简单的视图访问场景
- 替代findViewById,避免空指针异常
- 编译时类型安全
- DataBinding:
- 用于实现MVVM架构
- 数据驱动UI更新
- 双向绑定支持
- 自定义绑定适配器
- 表达式支持
### RecyclerView 列表为空时的实现
在 RecyclerView 适配器中,当列表数据为空时,显示一个空视图(EmptyView)的实现方式:
1. 继承 BaseRecycleAdapter:
```kotlin
class YourAdapter : BaseRecycleAdapter() {
// 实现必要的方法
}
```
2. 在适配器中定义视图类型常量(已在 BaseRecycleAdapter 中定义):
```kotlin
companion object {
const val VIEW_TYPE_ITEM = 1
const val VIEW_TYPE_EMPTY = 0
}
```
3. 重写 getItemViewType 方法:
```kotlin
override fun getItemViewType(position: Int): Int {
if (dataList.isEmpty()) {
return VIEW_TYPE_EMPTY
}
return VIEW_TYPE_ITEM
}
```
4. 重写 getItemCount 方法:
```kotlin
override fun getItemCount(): Int {
if (dataList.isEmpty()) {
return 1 // 返回1表示显示空视图
}
return dataList.size
}
```
5. 在 onCreateViewHolder 中处理不同类型的视图:
```kotlin
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (viewType == VIEW_TYPE_EMPTY) {
val emptyView: ItemNoMoreBinding = DataBindingUtil.inflate(
(parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)!!,
R.layout.item_no_more,
parent,
false
)
return ViewHolderEmpty(emptyView)
} else {
val binding = ItemListBinding.inflate(
parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater,
parent,
false
)
return ItemViewHolder(binding.root)
}
}
```
6. 重写 onBindViewHolder 方法:
```kotlin
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ViewHolderEmpty) {
// 空视图不需要绑定数据
return
}
// 绑定列表项数据
if (holder is ItemViewHolder) {
val item = dataList[position]
holder.bind(item)
}
}
```
7. 空视图的布局文件示例(item_no_more.xml):
```xml
```
这种实现方式的优点:
1. 统一的空视图处理逻辑
2. 支持自定义空视图样式
3. 不影响列表正常数据的显示
4. 便于维护和扩展
## 数据库结构
### GeneralLibrary 数据库
```kotlin
@Database(entities = [PassWordCardBean::class, CardData::class, ProjectDataBean::class], version = 1)
```
主要实体:
- PassWordCardBean: 密码卡数据
- CardData: 卡片数据
- ProjectDataBean: 项目数据
## 开发环境要求
- Android Studio Arctic Fox或更高版本
- JDK 8或更高版本
- Android SDK API 23或更高版本
- Gradle 7.0或更高版本
## 构建与运行
1. 克隆项目:
```bash
git clone [项目地址]
```
2. 在Android Studio中打开项目
3. 同步Gradle文件
4. 构建项目:
```bash
./gradlew build
```
5. 运行应用:
- 通过Android Studio运行
- 或使用命令行:`./gradlew installDebug`
## 模块说明
### BaseLibrary
基础功能库,提供:
- 基础Activity
- 通用工具类
- 基础UI组件
### GeneralLibrary
通用功能模块,包含:
- Room数据库实现
- 数据访问对象(DAO)
- 实体类定义
### 其他模块
- HenanlLibrary: 河南地区特定功能
- QiheAloneLibrary: 齐河单机版特定功能
- QiheOnlineLibrary: 齐河在线版特定功能
- OCRIDCardLibrary: 身份证识别功能
- EasySocket: Socket通信实现
- PickerViewLibrary: 选择器控件
## 自定义组件使用说明
### TitleBar 标题栏组件
TitleBar是一个自定义的标题栏组件,提供了左中右三个位置的文本和图片设置功能。
#### XML属性配置
```xml
```
#### 代码中使用
1. 设置文本和图片
```kotlin
titleBar.apply {
setLeftText("返回")
setLeftImage(R.drawable.ic_back)
setCenterText("标题")
setRightText("更多")
setRightImage(R.drawable.ic_more)
}
```
2. 点击事件监听
```kotlin
// 方式1:使用类型常量(推荐)
titleBar.setOnItemclickListner(TitleBar.ClickType_LEFT_IMAGE) {
finish()
}
titleBar.setOnItemclickListner(TitleBar.ClickType_RIGHT_TEXT) {
showMenu()
}
// 或者使用完整的OnClickListener
titleBar.setOnItemclickListner(TitleBar.ClickType_LEFT_IMAGE, View.OnClickListener {
finish()
})
// 方式2:使用位置和类型常量(已废弃)
titleBar.setOnItemclickListner(TitleBar.IMAGE, TitleBar.LEFT) { finish() }
```
#### 点击类型常量说明
- `ClickType_LEFT_TEXT`: 左侧文本点击
- `ClickType_LEFT_IMAGE`: 左侧图片点击
- `ClickType_CENTER_TEXT`: 中间文本点击
- `ClickType_CENTER_IMAGE`: 中间图片点击
- `ClickType_RIGHT_TEXT`: 右侧文本点击
- `ClickType_RIGHT_IMAGE`: 右侧图片点击
#### 其他功能
```kotlin
// 设置右侧按钮状态
titleBar.setRightStatus(false) // 禁用右侧按钮
// 设置右侧图片可见性
titleBar.setRightIMGVisibility(View.GONE)
// 获取中间的TextView
val titleTextView = titleBar.getTitleTextView()
// 获取右侧布局
val rightLayout = titleBar.getLlRight()
```
#### 注意事项
1. 点击事件只有在对应的视图可见时才会生效
2. 设置文本或图片时,如果传入null或0,对应的视图将被隐藏
3. 组件默认使用垂直线性布局,确保在布局文件中设置合适的高度
#### 常见使用示例
```kotlin
// 在Activity的initView方法中设置
private fun initView() {
// 设置返回按钮点击事件
binding.titleBar.setOnItemclickListner(TitleBar.ClickType_LEFT_IMAGE) {
finish()
}
// 设置右侧文本按钮点击事件
binding.titleBar.setOnItemclickListner(TitleBar.ClickType_RIGHT_TEXT) {
// 处理右侧按钮点击逻辑
handleRightButtonClick()
}
}
```
## 支付方式动态获取功能
项目中实现了支付方式的动态获取和显示功能,支持从服务器获取支付方式列表并动态创建RadioButton。
### 数据结构
```kotlin
// 支付方式数据类
data class PaymentMethod(
val id: Long,
val name: String,
val remarks: String,
val deleted: Int
)
// 支付方式接口返回数据类
data class PaymentMethodResponse(
val itemTotal: Any?,
val obj: List,
val pageCurr: Any?,
val pageSize: Any?,
val pageTotal: Any?
)
```
### 使用方式
#### 1. 在Activity中添加支付方式相关属性
```kotlin
class YourActivity : AppCompatActivity() {
// 支付方式相关属性
private var paymentMethod: String = "现金"
private var paymentId: Long = 0
private var paymentMethodList: List = listOf()
// ... 其他代码
}
```
#### 2. 获取支付方式列表
```kotlin
/**
* 获取支付方式列表
*/
private fun getPaymentMethods() {
ApiManager.getInstance().requestGetLoading(
this,
"sell/paymentmethod/get",
PaymentMethodResponse::class.java,
null,
object : SubscriberListener>() {
override fun onNext(response: BaseResponse) {
if (response.success) {
val paymentMethods = response.content?.obj ?: listOf()
if (paymentMethods.isNotEmpty()) {
paymentMethodList = paymentMethods
updatePaymentMethodRadioGroup()
}
} else {
Toast.makeText(this@YourActivity, "获取支付方式失败: ${response.msg}", Toast.LENGTH_SHORT).show()
}
}
override fun onError(e: Throwable?) {
super.onError(e)
Toast.makeText(this@YourActivity, "获取支付方式失败: ${e?.message ?: "网络异常"}", Toast.LENGTH_SHORT).show()
}
}
)
}
```
#### 3. 动态创建RadioButton
```kotlin
/**
* 更新支付方式RadioGroup
*/
private fun updatePaymentMethodRadioGroup() {
// 清空原有RadioButton
binding.paymentMethodRadioGroup.removeAllViews()
// 动态添加RadioButton
paymentMethodList.forEachIndexed { index, method ->
val radioButton = RadioButton(this)
radioButton.id = View.generateViewId()
radioButton.layoutParams = LinearLayout.LayoutParams(0, resources.getDimensionPixelSize(R.dimen.dimen_40), 1.0f)
// 设置样式
radioButton.text = method.name
radioButton.background = resources.getDrawable(R.drawable.radio_selector)
radioButton.buttonDrawable = null
radioButton.gravity = Gravity.CENTER
radioButton.setTextColor(resources.getColorStateList(R.color.radio_button_text_color))
radioButton.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)
// 添加到RadioGroup
binding.paymentMethodRadioGroup.addView(radioButton)
// 默认选中第一个
if (index == 0) {
radioButton.isChecked = true
paymentMethod = method.name
paymentId = method.id
}
}
// 设置选择监听
binding.paymentMethodRadioGroup.setOnCheckedChangeListener { group, checkedId ->
for (i in 0 until group.childCount) {
val radioButton = group.getChildAt(i) as RadioButton
if (radioButton.id == checkedId) {
paymentMethod = radioButton.text.toString()
paymentId = paymentMethodList[i].id
break
}
}
}
}
```
#### 4. 布局文件配置
```xml
```
### 功能特点
1. **动态获取**: 从服务器动态获取支付方式列表,支持后台配置
2. **自动布局**: 根据支付方式数量自动调整RadioButton布局
3. **样式统一**: 所有动态创建的RadioButton使用统一的样式
4. **默认选择**: 自动选中第一个支付方式作为默认选项
5. **事件处理**: 支持选择变化监听,实时更新当前选中的支付方式
### 注意事项
1. 确保在调用`getPaymentMethods()`前已经初始化了相关的UI组件
2. 动态创建的RadioButton需要设置唯一的ID,使用`View.generateViewId()`
3. 在Activity销毁时注意清理相关资源,避免内存泄漏
4. 网络请求失败时要有相应的错误处理机制
## 注意事项
1. 数据库迁移
- 当修改数据库结构时,需要更新版本号
- 提供相应的Migration策略
2. 权限处理
- 确保在使用相关功能前申请必要权限
- 使用XXPermissions进行权限管理
3. 数据安全
- 敏感数据需要加密存储
- 注意用户数据的安全处理
4. 异常处理
- 所有try catch块中必须使用`CrashReport.postCatchedException(e)`上报异常
- 确保异常信息被正确记录和上报
- 避免异常信息泄露敏感数据
5. API异常处理最佳实践
- 针对特定错误码提供友好的用户提示
- 区分网络异常、业务异常和系统异常
- 异常发生后及时重置UI状态,允许用户重试
- 提供明确的操作指引,如"请先进行开卡操作"
### API异常处理示例
```kotlin
// 在API回调中处理不同类型的异常
object : SubscriberListener>() {
override fun onNext(response: BaseResponse) {
if (response.success) {
// 处理成功情况
handleSuccess(response.content)
} else {
// 处理业务异常
handleBusinessError(response.code, response.msg)
}
}
override fun onError(e: Throwable?) {
super.onError(e)
// 处理网络异常
handleNetworkError(e)
// 重置UI状态
resetViewState()
}
}
// 业务异常处理方法
private fun handleBusinessError(code: String?, msg: String?) {
when (code) {
"1081" -> ToastUtil.show("该卡片未在系统中注册,请先进行开卡操作")
"1001" -> ToastUtil.show("权限不足,请联系管理员")
"1002" -> ToastUtil.show("账户余额不足")
else -> {
val errorMsg = when {
msg.isNullOrBlank() -> "操作失败,请重试"
msg.contains("数据不存在") -> "数据不存在,请检查输入信息"
msg.contains("网络") -> "网络连接异常,请检查网络后重试"
msg.contains("超时") -> "请求超时,请重试"
else -> "操作失败: $msg"
}
ToastUtil.show(errorMsg)
}
}
// 重置界面状态
resetViewState()
}
// 网络异常处理方法
private fun handleNetworkError(e: Throwable?) {
val errorMsg = when {
e?.message?.contains("timeout") == true -> "网络请求超时,请检查网络连接"
e?.message?.contains("network") == true -> "网络连接失败,请检查网络设置"
e?.message?.contains("host") == true -> "服务器连接失败,请稍后重试"
else -> "网络异常: ${e?.message ?: "未知错误"}"
}
ToastUtil.show(errorMsg)
}
### Dialog弹窗使用最佳实践
项目中提供了多种Dialog组件,用于不同的交互场景。推荐使用项目已有的Dialog组件来保持UI风格的一致性。
#### 常用Dialog组件
1. **ConfirmDialog**: 确认对话框,用于重要操作的二次确认
2. **TipDialog**: 提示对话框,用于显示提示信息
3. **EdtDialog**: 输入对话框,用于获取用户输入
4. **自定义Dialog**: 继承Dialog类实现特定功能
#### ConfirmDialog使用示例
```kotlin
// 基本用法 - 只显示消息
val dialog = ConfirmDialog(context, "操作成功")
dialog.show()
// 带标题的用法
val dialog = ConfirmDialog(context, "提示", "确认要删除这条记录吗?") {
// 点击确认按钮的回调
deleteRecord()
dialog.dismiss()
}
dialog.show()
// 在异常处理中使用
private fun handleError(title: String, message: String) {
activity?.let { activity ->
val confirmDialog = ConfirmDialog(activity, title, message) {
// 点击确认后的操作
resetViewState()
}
confirmDialog.show()
}
}
```
#### TipDialog使用示例
```kotlin
// 简单提示
val tipDialog = TipDialog(context, "操作完成")
tipDialog.show()
// 带回调的提示
val tipDialog = TipDialog(context, "确认退出应用?", object : TipUtil.TipListener {
override fun onCancle() {
// 取消操作
}
})
tipDialog.show()
```
#### 自定义Dialog最佳实践
```kotlin
class CustomDialog(context: Context) : Dialog(context, R.style.ws_pay_showSelfDialog) {
private lateinit var binding: DialogCustomBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DialogCustomBinding.inflate(layoutInflater)
setContentView(binding.root)
// 设置对话框属性
setupDialog()
// 初始化视图
initViews()
}
private fun setupDialog() {
// 设置对话框宽度为屏幕宽度的85%
window?.apply {
val params = attributes
params.width = (context.resources.displayMetrics.widthPixels * 0.85).toInt()
params.height = ViewGroup.LayoutParams.WRAP_CONTENT
params.gravity = Gravity.CENTER
attributes = params
setBackgroundDrawableResource(android.R.color.transparent)
}
// 设置点击外部不取消
setCanceledOnTouchOutside(false)
}
private fun initViews() {
binding.btnConfirm.setOnClickListener {
// 处理确认逻辑
dismiss()
}
binding.btnCancel.setOnClickListener {
dismiss()
}
}
}
```
#### Dialog使用注意事项
1. **内存泄漏防护**: 确保在Activity销毁时关闭Dialog
```kotlin
override fun onDestroy() {
super.onDestroy()
dialog?.dismiss()
}
```
2. **生命周期管理**: 在Fragment中使用Dialog时注意生命周期
```kotlin
// 在Fragment中安全显示Dialog
activity?.let { activity ->
if (!activity.isFinishing && !activity.isDestroyed) {
dialog.show()
}
}
```
3. **样式一致性**: 使用项目统一的Dialog样式
```kotlin
// 使用项目定义的Dialog样式
super(context, R.style.ws_pay_showSelfDialog)
```
4. **用户体验优化**:
- 重要操作使用ConfirmDialog进行二次确认
- 错误信息使用带标题的Dialog,提供清晰的错误分类
- 长时间操作显示加载Dialog,避免用户误操作
## 贡献指南
1. Fork 项目
2. 创建特性分支
3. 提交更改
4. 推送到分支
5. 创建Pull Request
## 许可证
[添加许可证信息]
## 联系方式
[添加联系方式]