feat: 完成国际出港移库/出库交接页面5.5寸手机端适配
- 落地手机端双形态适配框架:同名布局+资源限定符(layout/手机、 layout-sw600dp/平板)自动切换,DeviceUtil 设备形态判定,7个 手机版公共组件(PhoneSearchBar/StatusTab/DataLayout/FilterPanel/ BottomBar/StatBox/KvItem) - 完成「国际出港移库」「国际出港出库交接」两页手机端适配,新增 各自详情页(IntExpMoveDetailActivity/IntExpOutHandoverDetailActivity) - 手机端首页菜单接入"出港移库""出库交接"入口 - 修复手机端进入页面先横屏后转竖屏的闪屏问题:Manifest 全项目 192 处改为 screenOrientation="unspecified",由 BaseActivity 按设备形态运行时锁定方向 - 修复该方案的中间版本在平板端引入的回归(先竖后横 + UI放大1.6倍) - AutoSize 补充手机竖屏 390×844 设计基准 - 修复 CHANGELOG.md 因脚本异常导致的内容重复损坏(膨胀至12万行), 恢复正常结构并补充本次变更记录 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,33 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.lukouguoji.module_base">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/logot"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/logot"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Aerologic">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:screenOrientation="userLandscape"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- 屏幕适配 -->
|
||||
<meta-data
|
||||
android:name="design_width_in_dp"
|
||||
android:value="1152" />
|
||||
<meta-data
|
||||
android:name="design_height_in_dp"
|
||||
android:value="720" />
|
||||
</application>
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.lukouguoji.module_base">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/logot"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/logot"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Aerologic">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:screenOrientation="unspecified"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- 屏幕适配 -->
|
||||
<meta-data
|
||||
android:name="design_width_in_dp"
|
||||
android:value="1152" />
|
||||
<meta-data
|
||||
android:name="design_height_in_dp"
|
||||
android:value="720" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -2,6 +2,7 @@ package com.lukouguoji.module_base
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
@@ -26,6 +27,7 @@ import com.yanzhenjie.permission.runtime.Permission
|
||||
import com.yzq.zxinglibrary.android.CaptureActivity
|
||||
import com.yzq.zxinglibrary.bean.ZxingConfig
|
||||
import com.yzq.zxinglibrary.common.Constant
|
||||
import com.lukouguoji.module_base.util.DeviceUtil
|
||||
import dev.utils.app.HandlerUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -49,15 +51,48 @@ open class BaseActivity : AppCompatActivity(), CoroutineScope {
|
||||
private var timerTask: TimerTask? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// 必须在 super.onCreate 之前设置,避免进入页面后再旋转造成闪屏与重建
|
||||
applyDeviceOrientation()
|
||||
super.onCreate(savedInstanceState)
|
||||
// requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
setContentView(R.layout.activity_base)
|
||||
Log.d("BaseActivity", javaClass.simpleName)
|
||||
Log.d("BaseActivity", "${javaClass.simpleName} ${DeviceUtil.describe()}")
|
||||
ActivityCollector.addActivity(this)
|
||||
ARouter.getInstance().inject(this); /////////必须要写这行,自动注入解析
|
||||
loadingDialog = LoadingDialog(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* 按设备形态锁定屏幕方向 —— **兜底手段**
|
||||
*
|
||||
* 方向的主控权在 Manifest:各 Activity 统一写
|
||||
* `android:screenOrientation="@integer/screen_orientation"`,
|
||||
* 由资源限定符取值(`values/` = portrait,`values-sw600dp/` = userLandscape)。
|
||||
* 这样方向在**窗口创建阶段**就已确定,不会出现"先横屏、约 1 秒后转竖屏"的闪屏。
|
||||
*
|
||||
* 本方法只作为安全网:万一某个 Activity 的 Manifest 漏配(仍写死 userLandscape),
|
||||
* 至少还能在手机上纠正为竖屏——但那种情况下闪屏依旧存在,应当去补 Manifest。
|
||||
*
|
||||
* 子类如需特殊方向(如扫码页),覆写 [lockOrientationByDevice] 返回 false 即可跳过。
|
||||
*/
|
||||
protected open fun applyDeviceOrientation() {
|
||||
if (!lockOrientationByDevice()) return
|
||||
try {
|
||||
requestedOrientation = if (DeviceUtil.isPhone(this)) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// 半透明 / 悬浮类 Activity 不允许指定方向,忽略即可
|
||||
Log.w("BaseActivity", "setRequestedOrientation failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否由基类按设备形态自动锁定方向,默认开启
|
||||
*/
|
||||
protected open fun lockOrientationByDevice(): Boolean = true
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
job.cancel()
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.cretin.www.cretinautoupdatelibrary.model.UpdateConfig
|
||||
import com.cretin.www.cretinautoupdatelibrary.utils.AppUpdateUtils
|
||||
import com.cretin.www.cretinautoupdatelibrary.utils.SSLUtils.TrustAllHostnameVerifier
|
||||
import com.lukouguoji.module_base.handler.MyCrashHandler
|
||||
import com.lukouguoji.module_base.util.DeviceUtil
|
||||
import com.lukouguoji.module_base.util.OkHttp3Connection
|
||||
import com.scwang.smart.refresh.footer.ClassicsFooter
|
||||
import com.scwang.smart.refresh.header.ClassicsHeader
|
||||
@@ -79,20 +80,26 @@ class MyApplication : Application() {
|
||||
.setScreenHeight(activity.resources.displayMetrics.heightPixels)
|
||||
//根据屏幕方向,设置设计尺寸
|
||||
if (activity.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
//设置横屏设计尺寸
|
||||
//设置横屏设计尺寸(平板)
|
||||
AutoSizeConfig.getInstance()
|
||||
.setDesignWidthInDp(1152)
|
||||
.setDesignHeightInDp(720)
|
||||
.setDesignWidthInDp(DeviceUtil.TABLET_DESIGN_WIDTH_DP_LAND)
|
||||
.setDesignHeightInDp(DeviceUtil.TABLET_DESIGN_HEIGHT_DP_LAND)
|
||||
} else {
|
||||
if(activity.javaClass.simpleName.startsWith("PictureSelectorSupporterActivity")){
|
||||
AutoSizeConfig.getInstance()
|
||||
.setDesignWidthInDp(360)
|
||||
.setDesignHeightInDp(480)
|
||||
}else{
|
||||
//设置竖屏设计尺寸
|
||||
} else if (DeviceUtil.isPhone(activity)) {
|
||||
//手机竖屏:按 5.5 寸手持端设计稿画布 390 × 844 换算
|
||||
//若沿用平板竖屏的 720 × 1280 基准,手机上所有 dp 会被压缩约一半,布局全部错位
|
||||
AutoSizeConfig.getInstance()
|
||||
.setDesignWidthInDp(720)
|
||||
.setDesignHeightInDp(1280)
|
||||
.setDesignWidthInDp(DeviceUtil.PHONE_DESIGN_WIDTH_DP)
|
||||
.setDesignHeightInDp(DeviceUtil.PHONE_DESIGN_HEIGHT_DP)
|
||||
} else {
|
||||
//设置竖屏设计尺寸(平板)
|
||||
AutoSizeConfig.getInstance()
|
||||
.setDesignWidthInDp(DeviceUtil.TABLET_DESIGN_WIDTH_DP_PORT)
|
||||
.setDesignHeightInDp(DeviceUtil.TABLET_DESIGN_HEIGHT_DP_PORT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,17 @@ abstract class BaseBindingActivity<T : ViewDataBinding, VM : ViewModel> : BaseAc
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
//dataBinding初始化
|
||||
/*
|
||||
* dataBinding初始化
|
||||
*
|
||||
* 平板 / 手机双形态的布局切换由 Android 资源限定符自动完成,此处无需分支:
|
||||
* res/layout-sw600dp/xxx.xml → 平板(最小宽度 >= 600dp)
|
||||
* res/layout/xxx.xml → 手机(兜底)
|
||||
* 两个变体同名,DataBinding 会生成同一个 Binding 类,因此 Activity 与 ViewModel 无需改动。
|
||||
*
|
||||
* 注意:不要按「不同文件名 + 代码判断」的方式切换布局,
|
||||
* 那样会生成两个不同的 Binding 类,泛型擦除下编译期无法发现,运行时必然 ClassCastException。
|
||||
*/
|
||||
binding = DataBindingUtil.setContentView(this, layoutId())
|
||||
binding.lifecycleOwner = this
|
||||
|
||||
@@ -37,6 +47,9 @@ abstract class BaseBindingActivity<T : ViewDataBinding, VM : ViewModel> : BaseAc
|
||||
|
||||
/**
|
||||
* 布局ID
|
||||
*
|
||||
* 双形态适配时保持同一个 id 即可,具体加载哪份布局由资源限定符决定:
|
||||
* res/layout-sw600dp/ 平板 res/layout/ 手机
|
||||
*/
|
||||
abstract fun layoutId(): Int
|
||||
|
||||
|
||||
@@ -1,8 +1,19 @@
|
||||
package com.lukouguoji.module_base.bean
|
||||
|
||||
class ActionBean(
|
||||
val title: String,
|
||||
val icon: Int,
|
||||
val key: String = ""
|
||||
) {
|
||||
}
|
||||
package com.lukouguoji.module_base.bean
|
||||
|
||||
/**
|
||||
* 手持端(手机版)首页菜单项
|
||||
*
|
||||
* @param title 菜单显示名称
|
||||
* @param icon 菜单图标
|
||||
* @param key 权限串(取 [com.lukouguoji.module_base.common.Constant.AuthName] 中的常量)。
|
||||
* 菜单按登录返回的 authList 是否包含该串来决定显不显示
|
||||
* @param route ARouter 路由路径。填了就由通用逻辑直接跳转,无需再写 when 分支;
|
||||
* 留空表示由所在 ViewModel 的 onItemClick 自行处理(module_p 内的 PDA 页面走这条)
|
||||
*/
|
||||
class ActionBean(
|
||||
val title: String,
|
||||
val icon: Int,
|
||||
val key: String = "",
|
||||
val route: String = ""
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -56,4 +56,21 @@ data class GjcMove(
|
||||
var isSelected: Boolean
|
||||
get() = checked.get()
|
||||
set(value) = checked.set(value)
|
||||
|
||||
// ========== 5.5 寸手持端展示辅助字段(仅用于手机版布局,不参与序列化) ==========
|
||||
|
||||
/** 是否已移库(moveState:0-未移交,1-已移交) */
|
||||
val isMoved: Boolean
|
||||
get() = moveState == 1
|
||||
|
||||
/** 品名:优先中文,为空回退英文 */
|
||||
val goodsName: String
|
||||
get() = goodsCn.ifEmpty { goods }
|
||||
|
||||
/**
|
||||
* 详情页字段统一空值占位。
|
||||
* 接口对 businessType / opdate / opId 等字段常返回 null,
|
||||
* 直接绑会显示成 "null" 或空白,这里统一成 "-"。
|
||||
*/
|
||||
fun orDash(value: String?): String = if (value.isNullOrEmpty()) "-" else value
|
||||
}
|
||||
|
||||
@@ -108,4 +108,19 @@ class GjcUldUseBean : Serializable {
|
||||
// 复磅状态文本
|
||||
val wtStatusText: String
|
||||
get() = if (wtDate.isNotEmpty()) "已复磅" else "未复磅"
|
||||
|
||||
// ========== 5.5 寸手持端展示辅助字段(仅用于手机版布局,不参与序列化) ==========
|
||||
|
||||
/** 是否已交接:有交接时间即视为已交接 */
|
||||
val isHandovered: Boolean
|
||||
get() = hoDate.isNotEmpty()
|
||||
|
||||
/**
|
||||
* IMP 代码是否属于危险品类 —— 决定手机版卡片上 IMP 代码标签是红底还是绿底。
|
||||
*
|
||||
* 依据 IATA IMP 特货代码惯例:以 R 开头(RFL/RRY/REX 等)及 ICE 归为危险品。
|
||||
* 若后端另有判定口径,只需改这一处。
|
||||
*/
|
||||
val isDgrDangerous: Boolean
|
||||
get() = dgrCode.startsWith("R", ignoreCase = true) || dgrCode.equals("ICE", ignoreCase = true)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
package com.lukouguoji.module_base.ui.weight.phone
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import android.util.AttributeSet
|
||||
import android.util.SparseArray
|
||||
import android.view.Gravity
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.lukouguoji.module_base.R
|
||||
|
||||
/**
|
||||
* 手机版底部操作条(5.5 寸手持端公共组件)
|
||||
*
|
||||
* 结构:全选(圆形勾选框 + 文字)+ 已选统计 + 主操作按钮。
|
||||
* 对应设计稿各多选列表页底部的白色操作条。
|
||||
*
|
||||
* XML 用法:
|
||||
* ```xml
|
||||
* <com.lukouguoji.module_base.ui.weight.phone.PhoneBottomBar
|
||||
* android:layout_width="match_parent"
|
||||
* android:layout_height="wrap_content"
|
||||
* allChecked="@{viewModel.isAllChecked}"
|
||||
* countText='@{"已选 " + viewModel.selectedCount + " 项"}'
|
||||
* actionText="@{`交接`}"
|
||||
* setOnAllCheckClick="@{(v)-> viewModel.checkAllClick()}"
|
||||
* setOnActionClick="@{(v)-> viewModel.completeHandover()}" />
|
||||
* ```
|
||||
*/
|
||||
class PhoneBottomBar @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
val llCheckAll: LinearLayout
|
||||
val ivCheckAll: ImageView
|
||||
val tvCount: TextView
|
||||
val tvAction: TextView
|
||||
|
||||
/** 全选状态(驱动勾选框图标切换) */
|
||||
var allChecked: Boolean = false
|
||||
set(v) {
|
||||
field = v
|
||||
ivCheckAll.setImageResource(
|
||||
if (v) R.drawable.ic_phone_check_round_checked
|
||||
else R.drawable.ic_phone_check_round_unchecked
|
||||
)
|
||||
}
|
||||
|
||||
/** 已选统计文案,如「已选 3 项」 */
|
||||
var countText: String = ""
|
||||
set(v) {
|
||||
field = v
|
||||
tvCount.text = v
|
||||
}
|
||||
|
||||
/** 主按钮文字 */
|
||||
var actionText: String = ""
|
||||
set(v) {
|
||||
field = v
|
||||
tvAction.text = v
|
||||
}
|
||||
|
||||
init {
|
||||
orientation = HORIZONTAL
|
||||
gravity = Gravity.CENTER_VERTICAL
|
||||
setBackgroundColor(ContextCompat.getColor(context, R.color.white))
|
||||
elevation = 8 * resources.displayMetrics.density
|
||||
val padH = (16 * resources.displayMetrics.density).toInt()
|
||||
val padV = (14 * resources.displayMetrics.density).toInt()
|
||||
setPadding(padH, padV, padH, padV)
|
||||
|
||||
inflate(context, R.layout.layout_phone_bottom_bar, this)
|
||||
|
||||
llCheckAll = findViewById(R.id.ll_check_all)
|
||||
ivCheckAll = findViewById(R.id.iv_check_all)
|
||||
tvCount = findViewById(R.id.tv_count)
|
||||
tvAction = findViewById(R.id.tv_action)
|
||||
}
|
||||
|
||||
/** 只保存/恢复自身状态,避免内部子控件 id 与其他复合组件实例串档 */
|
||||
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
|
||||
dispatchFreezeSelfOnly(container)
|
||||
}
|
||||
|
||||
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
|
||||
dispatchThawSelfOnly(container)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
package com.lukouguoji.module_base.ui.weight.phone
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import android.util.AttributeSet
|
||||
import android.util.SparseArray
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import com.lukouguoji.module_base.R
|
||||
import com.lukouguoji.module_base.adapter.bindAdapter
|
||||
import com.lukouguoji.module_base.adapter.bindOnSelected
|
||||
import com.lukouguoji.module_base.interfaces.IOnSpinnerSelected
|
||||
import com.lukouguoji.module_base.ktx.formatDate
|
||||
import com.lukouguoji.module_base.ktx.getActivity
|
||||
import com.lukouguoji.module_base.util.Common
|
||||
import dev.utils.app.info.KeyValue
|
||||
import java.util.Calendar
|
||||
|
||||
/**
|
||||
* 手机版表单 / 筛选项(5.5 寸手持端公共组件)
|
||||
*
|
||||
* 结构:上方标题(14sp #374151)+ 下方 40dp 高控件框(白底 + 1px 边框 + 8dp 圆角)。
|
||||
* 支持三种形态,由 [type] 决定:
|
||||
*
|
||||
* | type | 控件 | 右侧图标 |
|
||||
* |-----------|-----------|----------|
|
||||
* | `INPUT` | EditText | 无 |
|
||||
* | `DATE` | TextView(点击弹日历)| 日历 |
|
||||
* | `SPINNER` | Spinner | 下箭头 |
|
||||
*
|
||||
* XML 用法:
|
||||
* ```xml
|
||||
* <com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout
|
||||
* android:layout_width="match_parent"
|
||||
* android:layout_height="wrap_content"
|
||||
* title="@{`航班日期`}"
|
||||
* hint="@{`请选择航班日期`}"
|
||||
* type="@{PhoneDataLayoutType.DATE}"
|
||||
* value="@={viewModel.flightDate}" />
|
||||
* ```
|
||||
*
|
||||
* 手机端专用,与平板端 PadDataLayoutNew 并列存在,互不影响。
|
||||
*/
|
||||
class PhoneDataLayout @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
val tvTitle: TextView
|
||||
val tvMust: TextView
|
||||
val llContent: LinearLayout
|
||||
val et: EditText
|
||||
val tv: TextView
|
||||
val spinner: Spinner
|
||||
val iv: ImageView
|
||||
|
||||
/** 双向绑定回调 */
|
||||
var onChangeListener: InverseBindingListener? = null
|
||||
|
||||
/** 输入完成 / 选择完成回调 */
|
||||
var refreshCallBack: (() -> Unit)? = null
|
||||
|
||||
var type: PhoneDataLayoutType = PhoneDataLayoutType.INPUT
|
||||
set(v) {
|
||||
field = v
|
||||
applyType()
|
||||
applyValue()
|
||||
}
|
||||
|
||||
var title: String = ""
|
||||
set(v) {
|
||||
field = v
|
||||
tvTitle.text = v
|
||||
}
|
||||
|
||||
var hint: String = ""
|
||||
set(v) {
|
||||
field = v
|
||||
et.hint = v
|
||||
tv.hint = v
|
||||
bindAdapter(spinner, list, v, R.layout.item_spinner_list_14sp)
|
||||
}
|
||||
|
||||
var value: String = ""
|
||||
set(v) {
|
||||
if (field == v) return
|
||||
field = v
|
||||
applyValue()
|
||||
onChangeListener?.onChange()
|
||||
}
|
||||
|
||||
var list: List<KeyValue> = emptyList()
|
||||
set(v) {
|
||||
field = v
|
||||
bindAdapter(spinner, v, hint, R.layout.item_spinner_list_14sp)
|
||||
applyValue()
|
||||
}
|
||||
|
||||
var required: Boolean = false
|
||||
set(v) {
|
||||
field = v
|
||||
tvMust.visibility = if (v) VISIBLE else GONE
|
||||
}
|
||||
|
||||
var enable: Boolean = true
|
||||
set(v) {
|
||||
field = v
|
||||
llContent.isEnabled = v
|
||||
et.isEnabled = v
|
||||
spinner.isEnabled = v
|
||||
}
|
||||
|
||||
init {
|
||||
orientation = VERTICAL
|
||||
inflate(context, R.layout.layout_phone_data_layout, this)
|
||||
|
||||
tvTitle = findViewById(R.id.tv_title)
|
||||
tvMust = findViewById(R.id.tv_must)
|
||||
llContent = findViewById(R.id.ll_content)
|
||||
et = findViewById(R.id.et)
|
||||
tv = findViewById(R.id.tv)
|
||||
spinner = findViewById(R.id.spinner)
|
||||
iv = findViewById(R.id.iv)
|
||||
|
||||
et.doOnTextChanged { text, _, _, _ ->
|
||||
// 仅 INPUT 形态才回写,避免隐藏的 EditText 被系统状态恢复时误清空值
|
||||
if (type == PhoneDataLayoutType.INPUT) {
|
||||
value = text.toString()
|
||||
}
|
||||
}
|
||||
|
||||
bindOnSelected(spinner, object : IOnSpinnerSelected {
|
||||
override fun onSelected(position: Int) {
|
||||
value = list.getOrNull(position)?.value ?: ""
|
||||
refreshCallBack?.invoke()
|
||||
}
|
||||
})
|
||||
|
||||
applyType()
|
||||
}
|
||||
|
||||
private val dateClick: (v: View) -> Unit = {
|
||||
if (enable) {
|
||||
Common.onYearMonthDay(context.getActivity(), value) { year, month, day ->
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.set(year, month - 1, day)
|
||||
value = calendar.time.formatDate()
|
||||
refreshCallBack?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyType() {
|
||||
when (type) {
|
||||
PhoneDataLayoutType.INPUT -> {
|
||||
et.visibility = VISIBLE
|
||||
tv.visibility = GONE
|
||||
spinner.visibility = GONE
|
||||
iv.visibility = GONE
|
||||
llContent.setOnClickListener(null)
|
||||
llContent.isClickable = false
|
||||
}
|
||||
|
||||
PhoneDataLayoutType.DATE -> {
|
||||
et.visibility = GONE
|
||||
tv.visibility = VISIBLE
|
||||
spinner.visibility = GONE
|
||||
iv.visibility = VISIBLE
|
||||
iv.setImageResource(R.drawable.ic_phone_calendar)
|
||||
llContent.setOnClickListener(dateClick)
|
||||
}
|
||||
|
||||
PhoneDataLayoutType.SPINNER -> {
|
||||
et.visibility = GONE
|
||||
tv.visibility = GONE
|
||||
spinner.visibility = VISIBLE
|
||||
iv.visibility = VISIBLE
|
||||
iv.setImageResource(R.drawable.ic_phone_chevron_down)
|
||||
llContent.setOnClickListener(null)
|
||||
llContent.isClickable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyValue() {
|
||||
when (type) {
|
||||
PhoneDataLayoutType.INPUT -> {
|
||||
if (et.text.toString() != value) et.setText(value)
|
||||
}
|
||||
|
||||
PhoneDataLayoutType.DATE -> {
|
||||
tv.text = value
|
||||
}
|
||||
|
||||
PhoneDataLayoutType.SPINNER -> {
|
||||
if (value.isNotEmpty()) {
|
||||
val position = list.indexOfFirst { it.value == value }
|
||||
if (position >= 0) spinner.setSelection(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 同一页面往往放置多个本组件实例,而它们的内部子控件(et / tv / spinner)共用同一套 id。
|
||||
* 系统按 id 保存/恢复视图状态会发生跨实例串档(典型表现:日期项被邻近输入框的空文本覆盖)。
|
||||
* 这里只保存/恢复自身状态、不下发给子控件;控件取值本就由 ViewModel + DataBinding 持有,无需视图级恢复。
|
||||
*/
|
||||
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
|
||||
dispatchFreezeSelfOnly(container)
|
||||
}
|
||||
|
||||
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
|
||||
dispatchThawSelfOnly(container)
|
||||
}
|
||||
|
||||
/** 重置为空值(筛选弹层「重置」按钮使用) */
|
||||
fun reset() {
|
||||
value = ""
|
||||
if (type == PhoneDataLayoutType.SPINNER && list.isNotEmpty()) {
|
||||
spinner.setSelection(if (hint.isEmpty()) list.size - 1 else list.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** [PhoneDataLayout] 支持的控件形态 */
|
||||
enum class PhoneDataLayoutType {
|
||||
INPUT, // 文本输入
|
||||
DATE, // 日期选择
|
||||
SPINNER, // 下拉选择
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.lukouguoji.module_base.ui.weight.phone
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.lukouguoji.module_base.R
|
||||
|
||||
/**
|
||||
* 手机版底部筛选弹层(5.5 寸手持端公共组件)
|
||||
*
|
||||
* 半透明遮罩 + 底部白色面板(顶部 20dp 圆角),面板内含标题行、内容插槽、重置/确认按钮行。
|
||||
* 对应设计稿各列表页的「筛选条件」弹层。
|
||||
*
|
||||
* **内容插槽**:直接把筛选项(一般是 [PhoneDataLayout])写在本组件标签内即可,
|
||||
* [onFinishInflate] 会自动把它们移入面板中部,无需关心内部层级:
|
||||
*
|
||||
* ```xml
|
||||
* <com.lukouguoji.module_base.ui.weight.phone.PhoneFilterPanel
|
||||
* android:layout_width="match_parent"
|
||||
* android:layout_height="match_parent"
|
||||
* panelVisible="@{activity.filterPanelVisible}"
|
||||
* setOnResetClick="@{(v)-> activity.resetFilter()}"
|
||||
* setOnConfirmClick="@{(v)-> activity.confirmFilter()}"
|
||||
* setOnDismissClick="@{(v)-> activity.toggleFilterPanel()}">
|
||||
*
|
||||
* <com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout ... />
|
||||
* <com.lukouguoji.module_base.ui.weight.phone.PhoneDataLayout ... />
|
||||
*
|
||||
* </com.lukouguoji.module_base.ui.weight.phone.PhoneFilterPanel>
|
||||
* ```
|
||||
*
|
||||
* 相邻筛选项之间的 20dp 间距由本组件统一施加,使用方无需逐个写 marginTop。
|
||||
*/
|
||||
class PhoneFilterPanel @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
val llPanel: LinearLayout
|
||||
val llContent: LinearLayout
|
||||
val tvTitle: TextView
|
||||
val ivClose: ImageView
|
||||
val tvReset: TextView
|
||||
val tvConfirm: TextView
|
||||
|
||||
/** 遮罩点击 / 关闭图标点击回调 */
|
||||
private var dismissListener: OnClickListener? = null
|
||||
|
||||
var title: String = ""
|
||||
set(v) {
|
||||
field = v
|
||||
if (v.isNotEmpty()) tvTitle.text = v
|
||||
}
|
||||
|
||||
/** 弹层显隐 */
|
||||
var panelVisible: Boolean = false
|
||||
set(v) {
|
||||
field = v
|
||||
visibility = if (v) VISIBLE else GONE
|
||||
}
|
||||
|
||||
init {
|
||||
setBackgroundColor(ContextCompat.getColor(context, R.color.black_tran50))
|
||||
visibility = GONE
|
||||
|
||||
inflate(context, R.layout.layout_phone_filter_panel, this)
|
||||
|
||||
llPanel = findViewById(R.id.ll_panel)
|
||||
llContent = findViewById(R.id.ll_content)
|
||||
tvTitle = findViewById(R.id.tv_title)
|
||||
ivClose = findViewById(R.id.iv_close)
|
||||
tvReset = findViewById(R.id.tv_reset)
|
||||
tvConfirm = findViewById(R.id.tv_confirm)
|
||||
|
||||
// 遮罩点击关闭;面板自身已 clickable,不会穿透
|
||||
setOnClickListener { dismissListener?.onClick(this) }
|
||||
ivClose.setOnClickListener { dismissListener?.onClick(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 XML 中声明在本组件内的子 View 移入面板内容区,并统一施加 20dp 项间距。
|
||||
*/
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
|
||||
// index 0 是内部面板(init 中 inflate 得到),其后才是使用方声明的子 View
|
||||
val declared = mutableListOf<View>()
|
||||
for (i in childCount - 1 downTo 1) {
|
||||
declared.add(0, getChildAt(i))
|
||||
removeViewAt(i)
|
||||
}
|
||||
|
||||
declared.forEachIndexed { index, child ->
|
||||
llContent.addView(child)
|
||||
if (index > 0) {
|
||||
(child.layoutParams as? LinearLayout.LayoutParams)?.let { lp ->
|
||||
lp.topMargin = (20 * resources.displayMetrics.density).toInt()
|
||||
child.layoutParams = lp
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setOnDismiss(listener: OnClickListener?) {
|
||||
dismissListener = listener
|
||||
}
|
||||
|
||||
/** 遍历内容区所有 [PhoneDataLayout] 并重置为空值 */
|
||||
fun resetAllFields() {
|
||||
for (i in 0 until llContent.childCount) {
|
||||
(llContent.getChildAt(i) as? PhoneDataLayout)?.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.lukouguoji.module_base.ui.weight.phone
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.lukouguoji.module_base.R
|
||||
|
||||
/**
|
||||
* 手机版详情页字段项(5.5 寸手持端公共组件)
|
||||
*
|
||||
* 竖排「小号灰标签(12sp)+ 数值(15sp)」,用于详情页两列信息网格。
|
||||
* 对应设计稿「板箱详情」等详情卡片内的字段。
|
||||
*
|
||||
* XML 用法:
|
||||
* ```xml
|
||||
* <com.lukouguoji.module_base.ui.weight.phone.PhoneKvItem
|
||||
* android:layout_width="0dp"
|
||||
* android:layout_height="wrap_content"
|
||||
* android:layout_weight="1"
|
||||
* title="@{`装机重`}"
|
||||
* value="@{bean.netWeight}" />
|
||||
* ```
|
||||
*
|
||||
* 需要突出的字段(如库位)设 `tag="@{true}"`,数值会渲染为橙色高亮标签。
|
||||
*/
|
||||
class PhoneKvItem @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
val tvTitle: TextView
|
||||
val tvValue: TextView
|
||||
|
||||
var title: String = ""
|
||||
set(v) {
|
||||
field = v
|
||||
tvTitle.text = v
|
||||
}
|
||||
|
||||
var value: String = ""
|
||||
set(v) {
|
||||
field = v
|
||||
// 详情页空值统一显示为 "-",与设计稿一致
|
||||
tvValue.text = v.ifEmpty { "-" }
|
||||
applyStyle()
|
||||
}
|
||||
|
||||
/** 是否以橙色高亮标签样式展示数值 */
|
||||
var tag: Boolean = false
|
||||
set(v) {
|
||||
field = v
|
||||
applyStyle()
|
||||
}
|
||||
|
||||
/**
|
||||
* 空值不套高亮标签底 —— 否则占位符 "-" 会显示成一个孤零零的橙色小方块。
|
||||
*/
|
||||
private fun applyStyle() {
|
||||
if (tag && value.isNotEmpty()) {
|
||||
tvValue.setBackgroundResource(R.drawable.bg_phone_value_orange)
|
||||
tvValue.setTextColor(ContextCompat.getColor(context, R.color.phone_orange_text))
|
||||
tvValue.setTypeface(tvValue.typeface, android.graphics.Typeface.BOLD)
|
||||
val padH = (4 * resources.displayMetrics.density).toInt()
|
||||
tvValue.setPadding(padH, 0, padH, 0)
|
||||
} else {
|
||||
tvValue.background = null
|
||||
tvValue.setTextColor(ContextCompat.getColor(context, R.color.phone_text_title))
|
||||
tvValue.setPadding(0, 0, 0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
orientation = VERTICAL
|
||||
|
||||
tvTitle = TextView(context).apply {
|
||||
textSize = 12f
|
||||
setTextColor(ContextCompat.getColor(context, R.color.phone_text_hint))
|
||||
}
|
||||
addView(
|
||||
tvTitle,
|
||||
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
||||
)
|
||||
|
||||
tvValue = TextView(context).apply {
|
||||
textSize = 15f
|
||||
setTextColor(ContextCompat.getColor(context, R.color.phone_text_title))
|
||||
}
|
||||
addView(
|
||||
tvValue,
|
||||
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
||||
topMargin = (2 * resources.displayMetrics.density).toInt()
|
||||
gravity = Gravity.START
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
package com.lukouguoji.module_base.ui.weight.phone
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import android.util.AttributeSet
|
||||
import android.util.SparseArray
|
||||
import android.view.View
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.EditText
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import com.lukouguoji.module_base.R
|
||||
import com.lukouguoji.module_base.ktx.setUpperCaseAlphanumericFilter
|
||||
|
||||
/**
|
||||
* 手机版搜索栏(5.5 寸手持端公共组件)
|
||||
*
|
||||
* 结构:圆角搜索框(放大镜 + 输入框 + 扫码图标)+ 右侧圆形筛选按钮。
|
||||
* 对应设计稿「5.5寸手持端设计文件」各列表页顶部搜索区。
|
||||
*
|
||||
* XML 用法:
|
||||
* ```xml
|
||||
* <com.lukouguoji.module_base.ui.weight.phone.PhoneSearchBar
|
||||
* android:layout_width="match_parent"
|
||||
* android:layout_height="wrap_content"
|
||||
* hint="@{`搜索ULD编号`}"
|
||||
* value="@={viewModel.uldNo}"
|
||||
* upperCase="@{true}"
|
||||
* setOnScanClickListener="@{(v)-> viewModel.scanUld()}"
|
||||
* setOnFilterClickListener="@{(v)-> activity.toggleFilterPanel()}"
|
||||
* setOnSearchListener="@{()-> viewModel.searchClick()}" />
|
||||
* ```
|
||||
*
|
||||
* 说明:与平板端 [com.lukouguoji.module_base.ui.weight.search.layout.PadSearchLayout] 并列,
|
||||
* 二者互不影响;手机布局用本组件,平板布局继续用 PadSearchLayout。
|
||||
*/
|
||||
class PhoneSearchBar @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
val llSearch: LinearLayout
|
||||
val ivSearch: ImageView
|
||||
val et: EditText
|
||||
val ivScan: ImageView
|
||||
val ivFilter: ImageView
|
||||
|
||||
/** 双向绑定回调 */
|
||||
var onChangeListener: InverseBindingListener? = null
|
||||
|
||||
/** 键盘「搜索」键 / 扫码完成后触发的查询回调 */
|
||||
var searchCallBack: (() -> Unit)? = null
|
||||
|
||||
var value: String = ""
|
||||
set(v) {
|
||||
if (field == v) return
|
||||
field = v
|
||||
if (et.text.toString() != v) et.setText(v)
|
||||
onChangeListener?.onChange()
|
||||
}
|
||||
|
||||
var hint: String = ""
|
||||
set(v) {
|
||||
field = v
|
||||
et.hint = v
|
||||
}
|
||||
|
||||
/** 是否显示扫码图标(默认显示) */
|
||||
var showScan: Boolean = true
|
||||
set(v) {
|
||||
field = v
|
||||
ivScan.visibility = if (v) VISIBLE else GONE
|
||||
}
|
||||
|
||||
/** 是否显示右侧筛选按钮(默认显示) */
|
||||
var showFilter: Boolean = true
|
||||
set(v) {
|
||||
field = v
|
||||
ivFilter.visibility = if (v) VISIBLE else GONE
|
||||
}
|
||||
|
||||
/** 输入内容强制大写字母数字(运单号 / ULD 编号等场景) */
|
||||
var upperCase: Boolean = false
|
||||
set(v) {
|
||||
field = v
|
||||
if (v) et.setUpperCaseAlphanumericFilter()
|
||||
}
|
||||
|
||||
init {
|
||||
orientation = HORIZONTAL
|
||||
gravity = android.view.Gravity.CENTER_VERTICAL
|
||||
inflate(context, R.layout.layout_phone_search_bar, this)
|
||||
|
||||
llSearch = findViewById(R.id.ll_search)
|
||||
ivSearch = findViewById(R.id.iv_search)
|
||||
et = findViewById(R.id.et)
|
||||
ivScan = findViewById(R.id.iv_scan)
|
||||
ivFilter = findViewById(R.id.iv_filter)
|
||||
|
||||
et.doOnTextChanged { text, _, _, _ ->
|
||||
value = text.toString()
|
||||
}
|
||||
|
||||
// 软键盘「搜索」键触发查询
|
||||
et.setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
|
||||
searchCallBack?.invoke()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 只保存/恢复自身状态,不下发给内部子控件。
|
||||
* 内部 et / iv_* 的 id 在每个实例中相同,交由系统按 id 恢复会跨实例串档;
|
||||
* 取值本就由 ViewModel + DataBinding 持有,无需视图级恢复。
|
||||
*/
|
||||
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
|
||||
dispatchFreezeSelfOnly(container)
|
||||
}
|
||||
|
||||
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
|
||||
dispatchThawSelfOnly(container)
|
||||
}
|
||||
|
||||
/** 供外部(如扫码回调)复用的图标点击入口 */
|
||||
fun setOnScanClick(listener: View.OnClickListener?) {
|
||||
ivScan.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
fun setOnFilterClick(listener: View.OnClickListener?) {
|
||||
ivFilter.setOnClickListener(listener)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.lukouguoji.module_base.ui.weight.phone
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import android.util.AttributeSet
|
||||
import android.util.SparseArray
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import com.lukouguoji.module_base.R
|
||||
|
||||
/**
|
||||
* 手机版卡片统计框(5.5 寸手持端公共组件)
|
||||
*
|
||||
* 浅灰底 + 8dp 圆角,横向等宽 2~4 列,每列为「小号灰标签 + 加粗数值」,列间带竖分隔线。
|
||||
* 对应设计稿列表卡片中部的统计区(如「架子车号 / 目的港 / 装机重」)。
|
||||
*
|
||||
* XML 用法(列数由是否传 label 决定,未传的列自动隐藏):
|
||||
* ```xml
|
||||
* <com.lukouguoji.module_base.ui.weight.phone.PhoneStatBox
|
||||
* android:layout_width="match_parent"
|
||||
* android:layout_height="wrap_content"
|
||||
* label1="@{`架子车号`}" value1="@{bean.carId}"
|
||||
* label2="@{`目的港`}" value2="@{bean.fdest}"
|
||||
* label3="@{`装机重`}" value3='@{bean.netWeight + "kg"}' />
|
||||
* ```
|
||||
*/
|
||||
class PhoneStatBox @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private val columns: List<LinearLayout>
|
||||
private val labels: List<TextView>
|
||||
private val values: List<TextView>
|
||||
|
||||
/** 分隔线(下标 0 对应第 2 列左侧,依次类推) */
|
||||
private val dividers: List<View>
|
||||
|
||||
init {
|
||||
orientation = HORIZONTAL
|
||||
setBackgroundResource(R.drawable.bg_phone_stat_box)
|
||||
val pad = (12 * resources.displayMetrics.density).toInt()
|
||||
setPadding(pad, pad, pad, pad)
|
||||
|
||||
inflate(context, R.layout.layout_phone_stat_box, this)
|
||||
|
||||
columns = listOf(
|
||||
findViewById(R.id.ll_col1),
|
||||
findViewById(R.id.ll_col2),
|
||||
findViewById(R.id.ll_col3),
|
||||
findViewById(R.id.ll_col4)
|
||||
)
|
||||
labels = listOf(
|
||||
findViewById(R.id.tv_label1),
|
||||
findViewById(R.id.tv_label2),
|
||||
findViewById(R.id.tv_label3),
|
||||
findViewById(R.id.tv_label4)
|
||||
)
|
||||
values = listOf(
|
||||
findViewById(R.id.tv_value1),
|
||||
findViewById(R.id.tv_value2),
|
||||
findViewById(R.id.tv_value3),
|
||||
findViewById(R.id.tv_value4)
|
||||
)
|
||||
dividers = listOf(
|
||||
findViewById(R.id.divider2),
|
||||
findViewById(R.id.divider3),
|
||||
findViewById(R.id.divider4)
|
||||
)
|
||||
|
||||
// 默认只展示第 1 列,其余待 label 赋值后再显示
|
||||
for (i in 1..3) setColumnVisible(i, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* 只保存/恢复自身状态。列表中每个卡片都含一个本组件,内部子控件 id 相同,
|
||||
* 交由系统按 id 恢复会跨实例串档。
|
||||
*/
|
||||
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
|
||||
dispatchFreezeSelfOnly(container)
|
||||
}
|
||||
|
||||
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
|
||||
dispatchThawSelfOnly(container)
|
||||
}
|
||||
|
||||
/** 设置第 [index] 列(0 起)的标签,传空则隐藏该列 */
|
||||
fun setLabel(index: Int, text: String?) {
|
||||
if (index !in labels.indices) return
|
||||
labels[index].text = text.orEmpty()
|
||||
setColumnVisible(index, !text.isNullOrEmpty())
|
||||
}
|
||||
|
||||
/** 设置第 [index] 列(0 起)的数值 */
|
||||
fun setValue(index: Int, text: String?) {
|
||||
if (index !in values.indices) return
|
||||
values[index].text = text.orEmpty()
|
||||
}
|
||||
|
||||
private fun setColumnVisible(index: Int, visible: Boolean) {
|
||||
val flag = if (visible) VISIBLE else GONE
|
||||
columns[index].visibility = flag
|
||||
// 第 2~4 列连带其左侧分隔线一起显隐
|
||||
if (index in 1..3) dividers[index - 1].visibility = flag
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package com.lukouguoji.module_base.ui.weight.phone
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import com.lukouguoji.module_base.R
|
||||
import dev.utils.app.info.KeyValue
|
||||
|
||||
/**
|
||||
* 手机版状态 Tab 条(5.5 寸手持端公共组件)
|
||||
*
|
||||
* 每个 Tab = 文字 + 可选角标数字,选中态为「深色加粗文字 + 底部 2dp 全宽指示条」。
|
||||
* 对应设计稿各列表页的「待交接 / 已交接」「全部 / 待移库 / 已移库」等状态切换栏。
|
||||
*
|
||||
* XML 用法:
|
||||
* ```xml
|
||||
* <com.lukouguoji.module_base.ui.weight.phone.PhoneStatusTab
|
||||
* android:layout_width="match_parent"
|
||||
* android:layout_height="wrap_content"
|
||||
* tabs="@{viewModel.handoverStateTabs}"
|
||||
* counts="@{viewModel.handoverStateCounts}"
|
||||
* value="@={viewModel.handoverState}"
|
||||
* setOnTabChanged="@{()-> activity.onTabChanged()}" />
|
||||
* ```
|
||||
*
|
||||
* [tabs] 用 [KeyValue]:`key` 为显示文字,`value` 为写回 [value] 的业务值。
|
||||
* [counts] 为与 tabs 等长的角标文案列表,传 null 或空串则该 Tab 不显示角标。
|
||||
*/
|
||||
class PhoneStatusTab @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
/** 双向绑定回调 */
|
||||
var onChangeListener: InverseBindingListener? = null
|
||||
|
||||
/** Tab 切换回调(用户点击引起的变化才触发,外部赋值不触发) */
|
||||
var tabChangedCallBack: (() -> Unit)? = null
|
||||
|
||||
var tabs: List<KeyValue> = emptyList()
|
||||
set(v) {
|
||||
field = v
|
||||
buildTabs()
|
||||
}
|
||||
|
||||
var counts: List<String> = emptyList()
|
||||
set(v) {
|
||||
field = v
|
||||
refreshState()
|
||||
}
|
||||
|
||||
var value: String = ""
|
||||
set(v) {
|
||||
if (field == v) return
|
||||
field = v
|
||||
refreshState()
|
||||
onChangeListener?.onChange()
|
||||
}
|
||||
|
||||
init {
|
||||
orientation = HORIZONTAL
|
||||
setBackgroundColor(ContextCompat.getColor(context, R.color.white))
|
||||
}
|
||||
|
||||
private fun buildTabs() {
|
||||
removeAllViews()
|
||||
tabs.forEachIndexed { index, tab ->
|
||||
addView(createTab(tab, index))
|
||||
}
|
||||
refreshState()
|
||||
}
|
||||
|
||||
private fun createTab(tab: KeyValue, index: Int): View {
|
||||
val column = LinearLayout(context).apply {
|
||||
orientation = VERTICAL
|
||||
layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
|
||||
setOnClickListener {
|
||||
val target = tab.value ?: ""
|
||||
if (value != target) {
|
||||
value = target
|
||||
tabChangedCallBack?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 文字 + 角标
|
||||
val row = LinearLayout(context).apply {
|
||||
orientation = HORIZONTAL
|
||||
gravity = Gravity.CENTER
|
||||
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, dp(42))
|
||||
}
|
||||
row.addView(TextView(context).apply {
|
||||
id = titleIdOf(index)
|
||||
text = tab.key
|
||||
textSize = 14f
|
||||
setTypeface(typeface, android.graphics.Typeface.BOLD)
|
||||
})
|
||||
row.addView(TextView(context).apply {
|
||||
id = badgeIdOf(index)
|
||||
textSize = 11f
|
||||
gravity = Gravity.CENTER
|
||||
setBackgroundResource(R.drawable.bg_phone_badge)
|
||||
setTextColor(ContextCompat.getColor(context, R.color.phone_primary))
|
||||
setPadding(dp(6), dp(1), dp(6), dp(1))
|
||||
layoutParams = LayoutParams(
|
||||
LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT
|
||||
).apply { marginStart = dp(6) }
|
||||
})
|
||||
column.addView(row)
|
||||
|
||||
// 底部指示条
|
||||
column.addView(View(context).apply {
|
||||
id = indicatorIdOf(index)
|
||||
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, dp(2))
|
||||
})
|
||||
|
||||
return column
|
||||
}
|
||||
|
||||
private fun refreshState() {
|
||||
tabs.forEachIndexed { index, tab ->
|
||||
val selected = (tab.value ?: "") == value
|
||||
|
||||
findViewById<TextView>(titleIdOf(index))?.setTextColor(
|
||||
ContextCompat.getColor(
|
||||
context,
|
||||
if (selected) R.color.phone_text_title else R.color.phone_text_body
|
||||
)
|
||||
)
|
||||
|
||||
findViewById<View>(indicatorIdOf(index))?.setBackgroundColor(
|
||||
if (selected) ContextCompat.getColor(context, R.color.phone_text_title)
|
||||
else android.graphics.Color.TRANSPARENT
|
||||
)
|
||||
|
||||
findViewById<TextView>(badgeIdOf(index))?.apply {
|
||||
val count = counts.getOrNull(index).orEmpty()
|
||||
if (count.isEmpty()) {
|
||||
visibility = GONE
|
||||
} else {
|
||||
visibility = VISIBLE
|
||||
text = count
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun dp(value: Int): Int =
|
||||
(value * resources.displayMetrics.density).toInt()
|
||||
|
||||
// 动态生成的子 View 用固定偏移量作为 id,避免与资源 id 冲突
|
||||
private fun titleIdOf(index: Int) = ID_BASE_TITLE + index
|
||||
private fun badgeIdOf(index: Int) = ID_BASE_BADGE + index
|
||||
private fun indicatorIdOf(index: Int) = ID_BASE_INDICATOR + index
|
||||
|
||||
companion object {
|
||||
private const val ID_BASE_TITLE = 0x7E100000
|
||||
private const val ID_BASE_BADGE = 0x7E200000
|
||||
private const val ID_BASE_INDICATOR = 0x7E300000
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
package com.lukouguoji.module_base.ui.weight.phone
|
||||
|
||||
import android.view.View
|
||||
import androidx.databinding.BindingAdapter
|
||||
import androidx.databinding.InverseBindingAdapter
|
||||
import androidx.databinding.InverseBindingListener
|
||||
import dev.utils.app.info.KeyValue
|
||||
|
||||
/**
|
||||
* 5.5 寸手持端公共组件的 DataBinding 适配器集合
|
||||
*
|
||||
* 所有属性均为无命名空间写法(与平板端 PadSearchLayout / PadDataLayoutNew 的用法一致)。
|
||||
* 同名属性(hint / value / title / list 等)按控件类型解析,与平板端组件互不冲突。
|
||||
*/
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// PhoneSearchBar
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@BindingAdapter("hint", "showScan", "showFilter", "upperCase", requireAll = false)
|
||||
fun setPhoneSearchBarAttrs(
|
||||
bar: PhoneSearchBar,
|
||||
hint: String?,
|
||||
showScan: Boolean?,
|
||||
showFilter: Boolean?,
|
||||
upperCase: Boolean?
|
||||
) {
|
||||
hint?.let { bar.hint = it }
|
||||
showScan?.let { bar.showScan = it }
|
||||
showFilter?.let { bar.showFilter = it }
|
||||
upperCase?.let { bar.upperCase = it }
|
||||
}
|
||||
|
||||
@BindingAdapter("value", requireAll = false)
|
||||
fun setPhoneSearchBarValue(bar: PhoneSearchBar, value: String?) {
|
||||
value?.let { bar.value = it }
|
||||
}
|
||||
|
||||
@InverseBindingAdapter(attribute = "value", event = "valueAttrChanged")
|
||||
fun getPhoneSearchBarValue(bar: PhoneSearchBar): String = bar.value
|
||||
|
||||
@BindingAdapter("valueAttrChanged", requireAll = false)
|
||||
fun setPhoneSearchBarValueAttrChanged(bar: PhoneSearchBar, listener: InverseBindingListener) {
|
||||
bar.onChangeListener = listener
|
||||
}
|
||||
|
||||
@BindingAdapter("setOnScanClickListener", requireAll = false)
|
||||
fun setPhoneSearchBarScanClick(bar: PhoneSearchBar, listener: View.OnClickListener?) {
|
||||
bar.setOnScanClick(listener)
|
||||
}
|
||||
|
||||
@BindingAdapter("setOnFilterClickListener", requireAll = false)
|
||||
fun setPhoneSearchBarFilterClick(bar: PhoneSearchBar, listener: View.OnClickListener?) {
|
||||
bar.setOnFilterClick(listener)
|
||||
}
|
||||
|
||||
@BindingAdapter("setRefreshCallBack", requireAll = false)
|
||||
fun setPhoneSearchBarRefreshCallBack(bar: PhoneSearchBar, callBack: (() -> Unit)?) {
|
||||
bar.searchCallBack = callBack
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// PhoneStatusTab
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@BindingAdapter("tabs", requireAll = false)
|
||||
fun setPhoneStatusTabTabs(tab: PhoneStatusTab, tabs: List<KeyValue>?) {
|
||||
tabs?.let { tab.tabs = it }
|
||||
}
|
||||
|
||||
@BindingAdapter("counts", requireAll = false)
|
||||
fun setPhoneStatusTabCounts(tab: PhoneStatusTab, counts: List<String>?) {
|
||||
tab.counts = counts ?: emptyList()
|
||||
}
|
||||
|
||||
@BindingAdapter("value", requireAll = false)
|
||||
fun setPhoneStatusTabValue(tab: PhoneStatusTab, value: String?) {
|
||||
value?.let { tab.value = it }
|
||||
}
|
||||
|
||||
@InverseBindingAdapter(attribute = "value", event = "valueAttrChanged")
|
||||
fun getPhoneStatusTabValue(tab: PhoneStatusTab): String = tab.value
|
||||
|
||||
@BindingAdapter("valueAttrChanged", requireAll = false)
|
||||
fun setPhoneStatusTabValueAttrChanged(tab: PhoneStatusTab, listener: InverseBindingListener) {
|
||||
tab.onChangeListener = listener
|
||||
}
|
||||
|
||||
@BindingAdapter("setOnTabChanged", requireAll = false)
|
||||
fun setPhoneStatusTabChanged(tab: PhoneStatusTab, callBack: (() -> Unit)?) {
|
||||
tab.tabChangedCallBack = callBack
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// PhoneDataLayout
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@BindingAdapter("title", "hint", "type", "required", "enable", requireAll = false)
|
||||
fun setPhoneDataLayoutAttrs(
|
||||
layout: PhoneDataLayout,
|
||||
title: String?,
|
||||
hint: String?,
|
||||
type: PhoneDataLayoutType?,
|
||||
required: Boolean?,
|
||||
enable: Boolean?
|
||||
) {
|
||||
// type 必须先于 value/hint 生效,否则控件形态与取值分支不匹配
|
||||
type?.let { layout.type = it }
|
||||
title?.let { layout.title = it }
|
||||
hint?.let { layout.hint = it }
|
||||
required?.let { layout.required = it }
|
||||
enable?.let { layout.enable = it }
|
||||
}
|
||||
|
||||
@BindingAdapter("value", requireAll = false)
|
||||
fun setPhoneDataLayoutValue(layout: PhoneDataLayout, value: String?) {
|
||||
value?.let { layout.value = it }
|
||||
}
|
||||
|
||||
@InverseBindingAdapter(attribute = "value", event = "valueAttrChanged")
|
||||
fun getPhoneDataLayoutValue(layout: PhoneDataLayout): String = layout.value
|
||||
|
||||
@BindingAdapter("valueAttrChanged", requireAll = false)
|
||||
fun setPhoneDataLayoutValueAttrChanged(
|
||||
layout: PhoneDataLayout,
|
||||
listener: InverseBindingListener
|
||||
) {
|
||||
layout.onChangeListener = listener
|
||||
}
|
||||
|
||||
@BindingAdapter("list", requireAll = false)
|
||||
fun setPhoneDataLayoutList(layout: PhoneDataLayout, list: List<KeyValue>?) {
|
||||
list?.let { layout.list = it }
|
||||
}
|
||||
|
||||
@BindingAdapter("setRefreshCallBack", requireAll = false)
|
||||
fun setPhoneDataLayoutRefreshCallBack(layout: PhoneDataLayout, callBack: (() -> Unit)?) {
|
||||
layout.refreshCallBack = callBack
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// PhoneFilterPanel
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@BindingAdapter("title", "panelVisible", requireAll = false)
|
||||
fun setPhoneFilterPanelAttrs(panel: PhoneFilterPanel, title: String?, panelVisible: Boolean?) {
|
||||
title?.let { panel.title = it }
|
||||
panelVisible?.let { panel.panelVisible = it }
|
||||
}
|
||||
|
||||
@BindingAdapter("setOnResetClick", requireAll = false)
|
||||
fun setPhoneFilterPanelResetClick(panel: PhoneFilterPanel, listener: View.OnClickListener?) {
|
||||
panel.tvReset.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
@BindingAdapter("setOnConfirmClick", requireAll = false)
|
||||
fun setPhoneFilterPanelConfirmClick(panel: PhoneFilterPanel, listener: View.OnClickListener?) {
|
||||
panel.tvConfirm.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
@BindingAdapter("setOnDismissClick", requireAll = false)
|
||||
fun setPhoneFilterPanelDismissClick(panel: PhoneFilterPanel, listener: View.OnClickListener?) {
|
||||
panel.setOnDismiss(listener)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// PhoneBottomBar
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@BindingAdapter("allChecked", "countText", "actionText", requireAll = false)
|
||||
fun setPhoneBottomBarAttrs(
|
||||
bar: PhoneBottomBar,
|
||||
allChecked: Boolean?,
|
||||
countText: String?,
|
||||
actionText: String?
|
||||
) {
|
||||
allChecked?.let { bar.allChecked = it }
|
||||
countText?.let { bar.countText = it }
|
||||
actionText?.let { bar.actionText = it }
|
||||
}
|
||||
|
||||
@BindingAdapter("setOnAllCheckClick", requireAll = false)
|
||||
fun setPhoneBottomBarAllCheckClick(bar: PhoneBottomBar, listener: View.OnClickListener?) {
|
||||
bar.llCheckAll.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
@BindingAdapter("setOnActionClick", requireAll = false)
|
||||
fun setPhoneBottomBarActionClick(bar: PhoneBottomBar, listener: View.OnClickListener?) {
|
||||
bar.tvAction.setOnClickListener(listener)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// PhoneStatBox
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@BindingAdapter(
|
||||
"label1", "value1", "label2", "value2",
|
||||
"label3", "value3", "label4", "value4",
|
||||
requireAll = false
|
||||
)
|
||||
fun setPhoneStatBoxAttrs(
|
||||
box: PhoneStatBox,
|
||||
label1: String?, value1: String?,
|
||||
label2: String?, value2: String?,
|
||||
label3: String?, value3: String?,
|
||||
label4: String?, value4: String?
|
||||
) {
|
||||
box.setLabel(0, label1)
|
||||
box.setValue(0, value1)
|
||||
box.setLabel(1, label2)
|
||||
box.setValue(1, value2)
|
||||
box.setLabel(2, label3)
|
||||
box.setValue(2, value3)
|
||||
box.setLabel(3, label4)
|
||||
box.setValue(3, value4)
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// PhoneKvItem
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@BindingAdapter("title", "value", "tag", requireAll = false)
|
||||
fun setPhoneKvItemAttrs(item: PhoneKvItem, title: String?, value: String?, tag: Boolean?) {
|
||||
title?.let { item.title = it }
|
||||
// tag 需先于 value 生效,确保样式切换后再填内容
|
||||
tag?.let { item.tag = it }
|
||||
item.value = value.orEmpty()
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package com.lukouguoji.module_base.util
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Resources
|
||||
import androidx.annotation.LayoutRes
|
||||
import com.lukouguoji.module_base.MyApplication
|
||||
|
||||
/**
|
||||
* 设备形态判定工具
|
||||
*
|
||||
* 用于「平板(10 寸横屏)」与「手机(5.5 寸竖屏)」双形态适配:
|
||||
* 1. 识别当前设备是平板还是手机
|
||||
* 2. 提供按形态选择布局的入口(Activity 布局 / RecyclerView item 布局通用)
|
||||
* 3. 提供手机端 AutoSize 设计基准尺寸常量
|
||||
*
|
||||
* 判定依据:`smallestScreenWidthDp`(最小屏幕宽度,与横竖屏无关,是 Android 官方推荐的设备分类维度)
|
||||
* - 5.5 寸手机 ≈ 360dp
|
||||
* - 10 寸平板 ≈ 800dp
|
||||
* - 分界线取官方标准 600dp
|
||||
*
|
||||
* 注意:这里读取的是 [Resources.getSystem](系统资源)而非应用 Resources,
|
||||
* 目的是绕开 AndroidAutoSize 对 density 的动态改写,保证判定结果稳定。
|
||||
*/
|
||||
object DeviceUtil {
|
||||
|
||||
/** 平板判定阈值:最小屏幕宽度 >= 600dp 视为平板 */
|
||||
private const val TABLET_MIN_WIDTH_DP = 600
|
||||
|
||||
/** 手机端设计稿基准尺寸(对应 5.5 寸手持端设计稿画布 390 × 844) */
|
||||
const val PHONE_DESIGN_WIDTH_DP = 390
|
||||
const val PHONE_DESIGN_HEIGHT_DP = 844
|
||||
|
||||
/** 平板端设计稿基准尺寸(沿用现有配置) */
|
||||
const val TABLET_DESIGN_WIDTH_DP_LAND = 1152
|
||||
const val TABLET_DESIGN_HEIGHT_DP_LAND = 720
|
||||
const val TABLET_DESIGN_WIDTH_DP_PORT = 720
|
||||
const val TABLET_DESIGN_HEIGHT_DP_PORT = 1280
|
||||
|
||||
/**
|
||||
* 调试用:强制指定设备形态
|
||||
*
|
||||
* 开发阶段可在平板上强制走手机布局做验证,正式发布保持 [ForceMode.AUTO]。
|
||||
* 用法:`DeviceUtil.forceMode = DeviceUtil.ForceMode.PHONE`
|
||||
*/
|
||||
enum class ForceMode { AUTO, PHONE, TABLET }
|
||||
|
||||
@JvmStatic
|
||||
var forceMode: ForceMode = ForceMode.AUTO
|
||||
|
||||
/** 缓存自动判定结果,避免每次都读 Configuration */
|
||||
private var cachedIsTablet: Boolean? = null
|
||||
|
||||
/**
|
||||
* 是否平板
|
||||
* @param context 可不传,默认用 Application 上下文(判定与具体 Activity 无关)
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun isTablet(context: Context? = null): Boolean = when (forceMode) {
|
||||
ForceMode.PHONE -> false
|
||||
ForceMode.TABLET -> true
|
||||
ForceMode.AUTO -> cachedIsTablet ?: (smallestWidthDp(context) >= TABLET_MIN_WIDTH_DP)
|
||||
.also { cachedIsTablet = it }
|
||||
}
|
||||
|
||||
/** 是否手机 */
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun isPhone(context: Context? = null): Boolean = !isTablet(context)
|
||||
|
||||
/**
|
||||
* 获取最小屏幕宽度(dp)
|
||||
*
|
||||
* 优先读系统资源(不受 AutoSize 影响);异常时回退到应用资源。
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun smallestWidthDp(context: Context? = null): Int {
|
||||
val fromSystem = Resources.getSystem()?.configuration?.smallestScreenWidthDp ?: 0
|
||||
if (fromSystem > 0) return fromSystem
|
||||
|
||||
val res = (context ?: runCatching { MyApplication.context }.getOrNull())?.resources
|
||||
return res?.configuration?.smallestScreenWidthDp ?: TABLET_MIN_WIDTH_DP
|
||||
}
|
||||
|
||||
/**
|
||||
* 按设备形态选择布局
|
||||
*
|
||||
* @param tabletLayout 平板布局(现有布局,必填)
|
||||
* @param phoneLayout 手机布局;传 0 或未提供时,手机上回退使用平板布局
|
||||
* @return 当前设备应加载的布局 id
|
||||
*
|
||||
* 用法(ViewModel 中选择 item 布局):
|
||||
* ```
|
||||
* val itemLayoutId = DeviceUtil.layout(R.layout.item_xxx, R.layout.item_xxx_phone)
|
||||
* ```
|
||||
*/
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun layout(
|
||||
@LayoutRes tabletLayout: Int,
|
||||
@LayoutRes phoneLayout: Int = 0,
|
||||
context: Context? = null
|
||||
): Int = if (phoneLayout != 0 && isPhone(context)) phoneLayout else tabletLayout
|
||||
|
||||
/** 配置发生变化时清空缓存(如分屏、折叠屏形态切换) */
|
||||
@JvmStatic
|
||||
fun clearCache() {
|
||||
cachedIsTablet = null
|
||||
}
|
||||
|
||||
/** 设备形态描述,用于日志排查 */
|
||||
@JvmStatic
|
||||
fun describe(): String =
|
||||
"${if (isTablet()) "TABLET" else "PHONE"}(sw=${smallestWidthDp()}dp, force=$forceMode)"
|
||||
}
|
||||
@@ -1,46 +1,46 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.lukouguoji.module_base">
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/logot"
|
||||
android:roundIcon="@mipmap/logot_round">
|
||||
<activity
|
||||
android:name=".ui.signature.SignatureActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:screenOrientation="userLandscape" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="true"
|
||||
android:screenOrientation="userLandscape" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.page.preview.PreviewActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="true"/>
|
||||
|
||||
<!-- PDF预览Activity -->
|
||||
<activity
|
||||
android:name=".ui.page.preview.PdfPreviewActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:screenOrientation="userLandscape" />
|
||||
|
||||
<!-- 屏幕适配 -->
|
||||
<meta-data
|
||||
android:name="design_width_in_dp"
|
||||
android:value="1152" />
|
||||
<meta-data
|
||||
android:name="design_height_in_dp"
|
||||
android:value="720" />
|
||||
</application>
|
||||
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.lukouguoji.module_base">
|
||||
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/logot"
|
||||
android:roundIcon="@mipmap/logot_round">
|
||||
<activity
|
||||
android:name=".ui.signature.SignatureActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:screenOrientation="unspecified" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="true"
|
||||
android:screenOrientation="unspecified" />
|
||||
|
||||
<activity
|
||||
android:name=".ui.page.preview.PreviewActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="true"/>
|
||||
|
||||
<!-- PDF预览Activity -->
|
||||
<activity
|
||||
android:name=".ui.page.preview.PdfPreviewActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:screenOrientation="unspecified" />
|
||||
|
||||
<!-- 屏幕适配 -->
|
||||
<meta-data
|
||||
android:name="design_width_in_dp"
|
||||
android:value="1152" />
|
||||
<meta-data
|
||||
android:name="design_height_in_dp"
|
||||
android:value="720" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
7
module_base/src/main/res/drawable/bg_phone_badge.xml
Normal file
7
module_base/src/main/res/drawable/bg_phone_badge.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版 Tab 角标:主色浅底 + 6dp 圆角 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/phone_badge_bg" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
22
module_base/src/main/res/drawable/bg_phone_btn_outline.xml
Normal file
22
module_base/src/main/res/drawable/bg_phone_btn_outline.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版次要按钮(重置):白底 + 浅边框 + 10dp 圆角 -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/phone_stat_bg" />
|
||||
<corners android:radius="10dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/phone_divider" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/white" />
|
||||
<corners android:radius="10dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/phone_divider" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
16
module_base/src/main/res/drawable/bg_phone_btn_primary.xml
Normal file
16
module_base/src/main/res/drawable/bg_phone_btn_primary.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版主操作按钮:主色实底 + 10dp 圆角 -->
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#1D4FD8" />
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item>
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="@color/phone_primary" />
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
</selector>
|
||||
10
module_base/src/main/res/drawable/bg_phone_card.xml
Normal file
10
module_base/src/main/res/drawable/bg_phone_card.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版列表卡片背景:白底 + 12dp 圆角 + 浅边框 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/white" />
|
||||
<corners android:radius="12dp" />
|
||||
<stroke
|
||||
android:width="0.5dp"
|
||||
android:color="@color/phone_divider" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版详情卡片表头:主色底 + 顶部 12dp 圆角(与 bg_phone_card 圆角一致) -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/phone_primary" />
|
||||
<corners
|
||||
android:topLeftRadius="12dp"
|
||||
android:topRightRadius="12dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版详情卡片表头:完成态绿底 + 顶部 12dp 圆角 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/phone_green" />
|
||||
<corners
|
||||
android:topLeftRadius="12dp"
|
||||
android:topRightRadius="12dp" />
|
||||
</shape>
|
||||
6
module_base/src/main/res/drawable/bg_phone_dot_green.xml
Normal file
6
module_base/src/main/res/drawable/bg_phone_dot_green.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版状态圆点:绿色(详情页表头的在途/正常指示点) -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/phone_green" />
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版圆形图标按钮(搜索行右侧筛选按钮):白底 + 浅边框 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
<solid android:color="@color/white" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/phone_divider" />
|
||||
</shape>
|
||||
10
module_base/src/main/res/drawable/bg_phone_input.xml
Normal file
10
module_base/src/main/res/drawable/bg_phone_input.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版表单/筛选输入框:白底 + 1px 边框 + 8dp 圆角 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/white" />
|
||||
<corners android:radius="8dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/phone_divider" />
|
||||
</shape>
|
||||
9
module_base/src/main/res/drawable/bg_phone_panel_top.xml
Normal file
9
module_base/src/main/res/drawable/bg_phone_panel_top.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版底部弹层面板:白底 + 顶部 20dp 圆角 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/white" />
|
||||
<corners
|
||||
android:topLeftRadius="20dp"
|
||||
android:topRightRadius="20dp" />
|
||||
</shape>
|
||||
10
module_base/src/main/res/drawable/bg_phone_search.xml
Normal file
10
module_base/src/main/res/drawable/bg_phone_search.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版搜索框背景:白底 + 12dp 圆角 + 浅边框 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/white" />
|
||||
<corners android:radius="12dp" />
|
||||
<stroke
|
||||
android:width="1dp"
|
||||
android:color="@color/phone_divider" />
|
||||
</shape>
|
||||
7
module_base/src/main/res/drawable/bg_phone_stat_box.xml
Normal file
7
module_base/src/main/res/drawable/bg_phone_stat_box.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版卡片内统计框:浅灰底 + 8dp 圆角 -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/phone_stat_bg" />
|
||||
<corners android:radius="8dp" />
|
||||
</shape>
|
||||
7
module_base/src/main/res/drawable/bg_phone_tag_gray.xml
Normal file
7
module_base/src/main/res/drawable/bg_phone_tag_gray.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版状态标签:灰底(未完成态) -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/phone_tag_gray_bg" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
7
module_base/src/main/res/drawable/bg_phone_tag_green.xml
Normal file
7
module_base/src/main/res/drawable/bg_phone_tag_green.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版状态标签:绿底(已完成态) -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/phone_tag_green_bg" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
11
module_base/src/main/res/drawable/bg_phone_tag_orange.xml
Normal file
11
module_base/src/main/res/drawable/bg_phone_tag_orange.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
手机版状态标签:橙底(待处理态,如「待移库」)
|
||||
与 bg_phone_tag_green / gray / red 同属状态标签家族,统一 6dp 圆角。
|
||||
数值高亮(详情页库位那种小色块)请用 bg_phone_value_orange。
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/phone_tag_orange_bg" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
7
module_base/src/main/res/drawable/bg_phone_tag_red.xml
Normal file
7
module_base/src/main/res/drawable/bg_phone_tag_red.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版状态标签:红底(危险品等强提示) -->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/phone_tag_red_bg" />
|
||||
<corners android:radius="6dp" />
|
||||
</shape>
|
||||
11
module_base/src/main/res/drawable/bg_phone_value_orange.xml
Normal file
11
module_base/src/main/res/drawable/bg_phone_value_orange.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
手机版数值高亮底:浅橙 + 4dp 圆角
|
||||
用于详情页需要突出的字段值(如库位),由 PhoneKvItem 的 tag=true 使用。
|
||||
状态标签(待移库/已完成那类)请用 bg_phone_tag_* 家族。
|
||||
-->
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/phone_orange_bg" />
|
||||
<corners android:radius="4dp" />
|
||||
</shape>
|
||||
12
module_base/src/main/res/drawable/ic_phone_airplane.xml
Normal file
12
module_base/src/main/res/drawable/ic_phone_airplane.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版图标:飞机(航班信息行) -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="@color/phone_text_body">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M21,16V14L13,9V3.5A1.5,1.5 0 0,0 11.5,2A1.5,1.5 0 0,0 10,3.5V9L2,14V16L10,13.5V19L8,20.5V22L11.5,21L15,22V20.5L13,19V13.5L21,16Z" />
|
||||
</vector>
|
||||
12
module_base/src/main/res/drawable/ic_phone_calendar.xml
Normal file
12
module_base/src/main/res/drawable/ic_phone_calendar.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版图标:日历(日期选择) -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="@color/phone_text_body">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,19H5V8H19M16,1V3H8V1H6V3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3H18V1H16Z" />
|
||||
</vector>
|
||||
12
module_base/src/main/res/drawable/ic_phone_check_circle.xml
Normal file
12
module_base/src/main/res/drawable/ic_phone_check_circle.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版图标:圆形对勾(已完成状态) -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="@color/phone_green">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,16.5L6.5,12L7.91,10.59L11,13.67L16.59,8.09L18,9.5L11,16.5Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
手机版圆形复选框 —— 选中态
|
||||
主色实心圆 + 白色对勾(对应设计稿 .custom-checkbox-round:checked)
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/phone_primary"
|
||||
android:pathData="M12,1A11,11 0 1,0 12,23A11,11 0 1,0 12,1Z" />
|
||||
<group
|
||||
android:pivotX="12"
|
||||
android:pivotY="12"
|
||||
android:scaleX="0.6"
|
||||
android:scaleY="0.6">
|
||||
<path
|
||||
android:fillColor="@color/white"
|
||||
android:pathData="M9,20.42L2.79,14.21L5.62,11.38L9,14.77L18.88,4.88L21.71,7.71L9,20.42Z" />
|
||||
</group>
|
||||
</vector>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
手机版圆形复选框 —— 未选中态
|
||||
白色填充 + 2dp 灰边圆环(对应设计稿 .custom-checkbox-round 默认态)
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@color/white"
|
||||
android:pathData="M12,2A10,10 0 1,0 12,22A10,10 0 1,0 12,2Z"
|
||||
android:strokeColor="@color/phone_check_border"
|
||||
android:strokeWidth="2" />
|
||||
</vector>
|
||||
12
module_base/src/main/res/drawable/ic_phone_chevron_down.xml
Normal file
12
module_base/src/main/res/drawable/ic_phone_chevron_down.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版图标:下箭头(下拉选择) -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="@color/phone_text_body">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z" />
|
||||
</vector>
|
||||
12
module_base/src/main/res/drawable/ic_phone_chevron_right.xml
Normal file
12
module_base/src/main/res/drawable/ic_phone_chevron_right.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版图标:右箭头(查看详情) -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="@color/phone_primary">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z" />
|
||||
</vector>
|
||||
12
module_base/src/main/res/drawable/ic_phone_close.xml
Normal file
12
module_base/src/main/res/drawable/ic_phone_close.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版图标:关闭(弹层右上角) -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="@color/phone_text_hint">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
|
||||
</vector>
|
||||
12
module_base/src/main/res/drawable/ic_phone_location.xml
Normal file
12
module_base/src/main/res/drawable/ic_phone_location.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版图标:定位(库位行) -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="@color/phone_text_body">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,11.5A2.5,2.5 0 0,1 9.5,9A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 14.5,9A2.5,2.5 0 0,1 12,11.5M12,2A7,7 0 0,0 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9A7,7 0 0,0 12,2Z" />
|
||||
</vector>
|
||||
12
module_base/src/main/res/drawable/ic_phone_person.xml
Normal file
12
module_base/src/main/res/drawable/ic_phone_person.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- 手机版图标:人像(操作人信息行) -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="@color/phone_text_body">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,4A4,4 0 0,1 16,8A4,4 0 0,1 12,12A4,4 0 0,1 8,8A4,4 0 0,1 12,4M12,6A2,2 0 0,0 10,8A2,2 0 0,0 12,10A2,2 0 0,0 14,8A2,2 0 0,0 12,6M12,13C14.67,13 20,14.33 20,17V20H4V17C4,14.33 9.33,13 12,13M12,14.9C9,14.9 5.9,16.36 5.9,17V18.1H18.1V17C18.1,16.36 15,14.9 12,14.9Z" />
|
||||
</vector>
|
||||
42
module_base/src/main/res/layout-sw600dp/title_tool_bar.xml
Normal file
42
module_base/src/main/res/layout-sw600dp/title_tool_bar.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tool_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tool_iv_back"
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:src="@mipmap/left_icon"
|
||||
app:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tool_tv_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|left"
|
||||
android:text="返回"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="主页"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
15
module_base/src/main/res/layout/item_spinner_list_14sp.xml
Normal file
15
module_base/src/main/res/layout/item_spinner_list_14sp.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
手机版下拉选项条目(14sp)
|
||||
与 PhoneDataLayout 输入框文字大小保持一致,供 5.5 寸手持端 SPINNER 使用。
|
||||
-->
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:paddingStart="10dp"
|
||||
android:paddingEnd="10dp"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
||||
android:textSize="14sp" />
|
||||
64
module_base/src/main/res/layout/layout_phone_bottom_bar.xml
Normal file
64
module_base/src/main/res/layout/layout_phone_bottom_bar.xml
Normal file
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
PhoneBottomBar 内部布局(手机版底部操作条)
|
||||
|
||||
结构:全选 + 已选统计 + 主操作按钮
|
||||
对应设计稿各多选列表页底部的白色操作条
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<!-- 全选 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_check_all"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_check_all"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:src="@drawable/ic_phone_check_round_unchecked" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:text="全选"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textSize="13sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 中间留白 -->
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="1dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<!-- 已选统计 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_count"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:textColor="@color/phone_text_hint"
|
||||
android:textSize="12sp" />
|
||||
|
||||
<!-- 主操作按钮 -->
|
||||
<TextView
|
||||
android:id="@+id/tv_action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_phone_btn_primary"
|
||||
android:gravity="center"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingVertical="9dp"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</merge>
|
||||
94
module_base/src/main/res/layout/layout_phone_data_layout.xml
Normal file
94
module_base/src/main/res/layout/layout_phone_data_layout.xml
Normal file
@@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
PhoneDataLayout 内部布局(手机版表单 / 筛选项)
|
||||
|
||||
结构:上方标题 + 下方控件框(输入 / 日期 / 下拉三态互斥显示)
|
||||
对应设计稿:5.5 寸手持端筛选弹层内的条件项
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<!-- 标题行:标题 + 必填星号 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="6dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/phone_text_label"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_must"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
android:text="*"
|
||||
android:textColor="@color/phone_tag_red_bg"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 控件框 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:background="@drawable/bg_phone_input"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp">
|
||||
|
||||
<!-- 文本输入 -->
|
||||
<EditText
|
||||
android:id="@+id/et"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@null"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textColorHint="@color/phone_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<!-- 日期显示(点击弹日历) -->
|
||||
<TextView
|
||||
android:id="@+id/tv"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textColorHint="@color/phone_text_hint"
|
||||
android:textSize="14sp"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- 下拉选择 -->
|
||||
<Spinner
|
||||
android:id="@+id/spinner"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@null"
|
||||
android:visibility="gone" />
|
||||
|
||||
<!-- 右侧指示图标(日期=日历 / 下拉=箭头 / 输入=隐藏) -->
|
||||
<ImageView
|
||||
android:id="@+id/iv"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:visibility="gone"
|
||||
app:tint="@color/phone_text_body" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
||||
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
PhoneFilterPanel 内部布局(手机版底部筛选弹层)
|
||||
|
||||
结构:底部白色面板(顶部 20dp 圆角)= 标题行 + 内容插槽 + 重置/确认按钮行
|
||||
外层遮罩由 PhoneFilterPanel 自身(FrameLayout)承担。
|
||||
XML 中声明在 PhoneFilterPanel 内的子 View 会被自动移入 ll_content。
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/ll_panel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="@drawable/bg_phone_panel_top"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingHorizontal="20dp"
|
||||
android:paddingTop="24dp"
|
||||
android:paddingBottom="32dp">
|
||||
|
||||
<!-- 标题行:标题居中 + 右上关闭 -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="筛选条件"
|
||||
android:textColor="@color/phone_text_strong"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_close"
|
||||
android:layout_width="24dp"
|
||||
android:layout_height="24dp"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:src="@drawable/ic_phone_close" />
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<!-- 内容插槽:筛选条件项由使用方在 XML 中声明 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical" />
|
||||
|
||||
<!-- 底部按钮行 -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="32dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_reset"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_btn_outline"
|
||||
android:gravity="center"
|
||||
android:text="重置"
|
||||
android:textColor="@color/phone_text_weak_btn"
|
||||
android:textSize="15sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_confirm"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="44dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_btn_primary"
|
||||
android:gravity="center"
|
||||
android:text="确认"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
65
module_base/src/main/res/layout/layout_phone_search_bar.xml
Normal file
65
module_base/src/main/res/layout/layout_phone_search_bar.xml
Normal file
@@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
PhoneSearchBar 内部布局(手机版搜索行)
|
||||
|
||||
结构:圆角搜索框(放大镜 + 输入 + 扫码) + 右侧圆形筛选按钮
|
||||
对应设计稿:5.5 寸手持端各列表页顶部搜索区
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<!-- 搜索框 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_search"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/bg_phone_search"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="12dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_search"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:src="@drawable/img_search"
|
||||
app:tint="@color/phone_text_hint" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/et"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@null"
|
||||
android:imeOptions="actionSearch"
|
||||
android:inputType="text"
|
||||
android:maxLines="1"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textColorHint="@color/phone_text_hint"
|
||||
android:textSize="14sp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_scan"
|
||||
android:layout_width="18dp"
|
||||
android:layout_height="18dp"
|
||||
android:src="@drawable/img_scan"
|
||||
app:tint="@color/phone_primary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<!-- 筛选按钮 -->
|
||||
<ImageView
|
||||
android:id="@+id/iv_filter"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:background="@drawable/bg_phone_icon_round"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/img_filter"
|
||||
app:tint="@color/phone_primary" />
|
||||
|
||||
</merge>
|
||||
138
module_base/src/main/res/layout/layout_phone_stat_box.xml
Normal file
138
module_base/src/main/res/layout/layout_phone_stat_box.xml
Normal file
@@ -0,0 +1,138 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
PhoneStatBox 内部布局(手机版卡片统计框)
|
||||
|
||||
最多 4 列,未设置 label 的列自动隐藏(含其左侧分隔线)。
|
||||
对应设计稿列表卡片中间的浅灰统计区。
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:parentTag="android.widget.LinearLayout">
|
||||
|
||||
<!-- 第 1 列 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_col1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_label1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/phone_text_hint"
|
||||
android:textSize="11sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_value1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider2"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/phone_divider" />
|
||||
|
||||
<!-- 第 2 列 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_col2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_label2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/phone_text_hint"
|
||||
android:textSize="11sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_value2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider3"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/phone_divider" />
|
||||
|
||||
<!-- 第 3 列 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_col3"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_label3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/phone_text_hint"
|
||||
android:textSize="11sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_value3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<View
|
||||
android:id="@+id/divider4"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/phone_divider" />
|
||||
|
||||
<!-- 第 4 列 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/ll_col4"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_label4"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/phone_text_hint"
|
||||
android:textSize="11sp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_value4"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="2dp"
|
||||
android:textColor="@color/phone_text_title"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</merge>
|
||||
@@ -1,42 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:background="@color/colorPrimary"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/tool_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tool_iv_back"
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="15dp"
|
||||
android:src="@mipmap/left_icon"
|
||||
app:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tool_tv_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical|left"
|
||||
android:text="返回"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:text="主页"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp" />
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
手机版公共标题栏(5.5 寸手持端)
|
||||
|
||||
与平板版 title_tool_bar.xml 保持相同的根类型(Toolbar)与控件 id:
|
||||
toolbar / tool_back / tool_iv_back / tool_tv_back / title_name
|
||||
因此 BaseActivity.setBackArrow() 无需任何改动即可复用。
|
||||
-->
|
||||
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:background="@color/phone_primary"
|
||||
android:contentInsetStart="0dp"
|
||||
android:contentInsetLeft="0dp"
|
||||
app:contentInsetStart="0dp"
|
||||
app:contentInsetLeft="0dp"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
|
||||
|
||||
<!-- 整条标题栏:左侧返回 + 居中标题 -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<!-- 居中标题(先声明,保证在返回区下层,不抢占点击) -->
|
||||
<TextView
|
||||
android:id="@+id/title_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:text="标题"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- 左侧返回区 -->
|
||||
<LinearLayout
|
||||
android:id="@+id/tool_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start|center_vertical"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/tool_iv_back"
|
||||
android:layout_width="14dp"
|
||||
android:layout_height="14dp"
|
||||
android:src="@mipmap/left_icon"
|
||||
app:tint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tool_tv_back"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
android:text="返回"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp" />
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
@@ -47,4 +47,31 @@
|
||||
<color name="black_tran50">#80000000</color> <!-- 50%透明度 -->
|
||||
<color name="black_tran70">#B3000000</color> <!-- 70%透明度 -->
|
||||
|
||||
<!-- ==================== 5.5 寸手持端(手机版)配色 ==================== -->
|
||||
<color name="phone_primary">#2563EB</color> <!-- 手机版主色(顶栏、主按钮) -->
|
||||
<color name="phone_primary_light">#EAF1FE</color> <!-- 主色浅底(选中态背景) -->
|
||||
<color name="phone_bg">#F3F4F6</color> <!-- 页面背景 -->
|
||||
<color name="phone_text_title">#1F2937</color> <!-- 标题/主要文字 -->
|
||||
<color name="phone_text_body">#6B7280</color> <!-- 次要文字 -->
|
||||
<color name="phone_text_hint">#9CA3AF</color> <!-- 占位文字 -->
|
||||
<color name="phone_divider">#E5E7EB</color> <!-- 分割线/边框 -->
|
||||
<color name="phone_tag_gray_bg">#F3F4F6</color> <!-- 状态标签-灰底 -->
|
||||
<color name="phone_tag_gray_text">#6B7280</color>
|
||||
<color name="phone_tag_green_bg">#DCFCE7</color> <!-- 状态标签-绿底 -->
|
||||
<color name="phone_tag_green_text">#16A34A</color>
|
||||
<color name="phone_tag_red_bg">#DC2626</color> <!-- 状态标签-红底(危险品等强提示) -->
|
||||
<color name="phone_tag_red_text">#FFFFFF</color>
|
||||
<color name="phone_page_bg">#F5F6FA</color> <!-- 列表页滚动区背景 -->
|
||||
<color name="phone_stat_bg">#F3F4F6</color> <!-- 卡片内统计框底色 -->
|
||||
<color name="phone_badge_bg">#DBEAF6</color> <!-- Tab 角标底色 -->
|
||||
<color name="phone_text_label">#374151</color> <!-- 表单/筛选项标题 -->
|
||||
<color name="phone_text_strong">#111827</color> <!-- 强调标题(弹层标题、详情主字段) -->
|
||||
<color name="phone_text_weak_btn">#4B5563</color> <!-- 次要按钮文字(重置) -->
|
||||
<color name="phone_green">#10B981</color> <!-- 完成态强调色(对勾、信息卡表头) -->
|
||||
<color name="phone_check_border">#CBD5E1</color> <!-- 未选中复选框边框 -->
|
||||
<color name="phone_orange_bg">#FFF7ED</color> <!-- 数值高亮-浅橙底(详情页库位等) -->
|
||||
<color name="phone_orange_text">#EA580C</color>
|
||||
<color name="phone_tag_orange_bg">#FFEDD5</color> <!-- 状态标签-橙底(待处理态) -->
|
||||
<color name="phone_tag_orange_text">#EA580C</color>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user