# 充值系统 (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 ## 许可证 [添加许可证信息] ## 联系方式 [添加联系方式]