feat: 进港查询筛选弹窗改为内容区嵌入式面板

将筛选条件弹框从 XPopup DrawerPopupView 改为嵌入 Activity 内容区的滑动面板,
使筛选面板从右侧滑入时不遮挡顶部蓝色标题栏,暗色遮罩也只覆盖内容区域。
同时修复首次弹出时因布局测量时序导致的闪烁问题。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-22 16:54:28 +08:00
parent 3c413833cf
commit df5fa2ea74
5 changed files with 328 additions and 279 deletions

View File

@@ -3,6 +3,9 @@ package com.lukouguoji.gjj.activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import com.alibaba.android.arouter.facade.annotation.Route import com.alibaba.android.arouter.facade.annotation.Route
import com.lukouguoji.gjj.R import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.databinding.ActivityIntImpQueryBinding import com.lukouguoji.gjj.databinding.ActivityIntImpQueryBinding
@@ -43,10 +46,66 @@ class IntImpQueryActivity :
viewModel.initAgentList() viewModel.initAgentList()
viewModel.initSpecialCodeList() viewModel.initSpecialCodeList()
viewModel.initFilterLists()
// 观察筛选面板显示状态
viewModel.filterVisible.observe(this) { visible ->
if (visible) showFilterPanel() else hideFilterPanel()
}
viewModel.refresh() viewModel.refresh()
} }
private fun showFilterPanel() {
val panel = binding.filterPanel.root
val overlay = binding.filterOverlay
val panelWidth = window.decorView.width / 3
panel.layoutParams = (panel.layoutParams as FrameLayout.LayoutParams).apply {
width = panelWidth
gravity = Gravity.END
}
// 先 INVISIBLE 完成测量,避免第一次显示时闪烁
panel.visibility = View.INVISIBLE
panel.translationX = panelWidth.toFloat()
overlay.visibility = View.VISIBLE
overlay.alpha = 0f
panel.post {
panel.visibility = View.VISIBLE
panel.animate().translationX(0f).setDuration(250).start()
overlay.animate().alpha(1f).setDuration(250).start()
}
}
private fun hideFilterPanel() {
val panel = binding.filterPanel.root
val overlay = binding.filterOverlay
if (panel.visibility != View.VISIBLE) return
panel.animate()
.translationX(panel.width.toFloat())
.setDuration(250)
.withEndAction { panel.visibility = View.GONE }
.start()
overlay.animate()
.alpha(0f)
.setDuration(250)
.withEndAction { overlay.visibility = View.GONE }
.start()
}
override fun onBackPressed() {
if (viewModel.filterVisible.value == true) {
viewModel.closeFilter()
} else {
super.onBackPressed()
}
}
companion object { companion object {
@JvmStatic @JvmStatic
fun start(context: Context) { fun start(context: Context) {

View File

@@ -1,84 +0,0 @@
package com.lukouguoji.gjj.dialog
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.lifecycle.MutableLiveData
import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.databinding.DialogIntImpQueryFilterBinding
import com.lukouguoji.module_base.base.BaseDialogModel
import com.lxj.xpopup.XPopup
import com.lxj.xpopup.enums.PopupPosition
import dev.DevUtils
import com.lukouguoji.module_base.util.DictUtils
import dev.utils.app.info.KeyValue
/**
* 国际进港查询筛选抽屉
*/
class IntImpQueryFilterDialogModel(
val spCode: MutableLiveData<String>,
val spCodeList: MutableLiveData<List<KeyValue>>,
val flightNo: MutableLiveData<String>,
val origin: MutableLiveData<String>,
val awbType: MutableLiveData<String>,
val businessType: MutableLiveData<String>,
val goodsCn: MutableLiveData<String>,
private val onConfirm: () -> Unit
) : BaseDialogModel<DialogIntImpQueryFilterBinding>(DIALOG_TYPE_DRAWER) {
val awbTypeList = MutableLiveData<List<KeyValue>>(emptyList())
val businessTypeList = MutableLiveData<List<KeyValue>>(emptyList())
override fun layoutId() = R.layout.dialog_int_imp_query_filter
override fun onBuild(builder: XPopup.Builder) {
super.onBuild(builder)
builder.popupPosition(PopupPosition.Right)
val activity = DevUtils.getTopActivity()
val activityWidth = activity.window.decorView.width
builder.maxWidth(activityWidth / 3)
builder.popupWidth(activityWidth / 3)
}
override fun onDialogCreated(context: Context) {
binding.model = this
binding.lifecycleOwner = context as? androidx.lifecycle.LifecycleOwner
val titleColor = Color.parseColor("#666666")
binding.root.findViewById<TextView>(R.id.title_name)?.text = "筛选条件"
binding.root.findViewById<TextView>(R.id.title_name)?.setTextColor(titleColor)
binding.root.findViewById<TextView>(R.id.tool_tv_back)?.setTextColor(titleColor)
binding.root.findViewById<ImageView>(R.id.tool_iv_back)?.imageTintList = ColorStateList.valueOf(titleColor)
binding.root.findViewById<View>(R.id.toolbar)?.setBackgroundColor(Color.WHITE)
binding.root.findViewById<View>(R.id.tool_back)?.setOnClickListener {
dismiss()
}
DictUtils.getWaybillTypeList(type = "II", addAll = true, checkedValue = awbType.value) {
awbTypeList.postValue(it)
}
DictUtils.getBusinessTypeList(type = "II", addAll = true, checkedValue = businessType.value) {
businessTypeList.postValue(it)
}
}
fun onResetClick() {
spCode.value = ""
flightNo.value = ""
origin.value = ""
awbType.value = ""
businessType.value = ""
goodsCn.value = ""
}
fun onConfirmClick() {
dismiss()
onConfirm()
}
}

View File

@@ -5,7 +5,6 @@ import android.content.Intent
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.lukouguoji.gjj.R import com.lukouguoji.gjj.R
import com.lukouguoji.gjj.activity.IntImpQueryEditActivity import com.lukouguoji.gjj.activity.IntImpQueryEditActivity
import com.lukouguoji.gjj.dialog.IntImpQueryFilterDialogModel
import com.lukouguoji.gjj.holder.IntImpQueryViewHolder import com.lukouguoji.gjj.holder.IntImpQueryViewHolder
import com.lukouguoji.gjj.activity.IntImpQueryDetailsActivity import com.lukouguoji.gjj.activity.IntImpQueryDetailsActivity
import com.lukouguoji.module_base.base.BasePageViewModel import com.lukouguoji.module_base.base.BasePageViewModel
@@ -59,6 +58,11 @@ class IntImpQueryViewModel : BasePageViewModel(), IOnItemClickListener {
// ==================== 特码下拉 ==================== // ==================== 特码下拉 ====================
val spCodeList = MutableLiveData<List<KeyValue>>(emptyList()) val spCodeList = MutableLiveData<List<KeyValue>>(emptyList())
// ==================== 筛选面板 ====================
val filterVisible = MutableLiveData(false)
val awbTypeList = MutableLiveData<List<KeyValue>>(emptyList())
val businessTypeList = MutableLiveData<List<KeyValue>>(emptyList())
// ==================== 筛选条件 ==================== // ==================== 筛选条件 ====================
val spCode = MutableLiveData("") val spCode = MutableLiveData("")
val flightNo = MutableLiveData("") val flightNo = MutableLiveData("")
@@ -80,17 +84,34 @@ class IntImpQueryViewModel : BasePageViewModel(), IOnItemClickListener {
} }
fun filterClick() { fun filterClick() {
val filterDialog = IntImpQueryFilterDialogModel( filterVisible.value = true
spCode = spCode, }
spCodeList = spCodeList,
flightNo = flightNo, fun closeFilter() {
origin = origin, filterVisible.value = false
awbType = awbType, }
businessType = businessType,
goodsCn = goodsCn, fun resetFilter() {
onConfirm = { refresh() } spCode.value = ""
) flightNo.value = ""
filterDialog.show() origin.value = ""
awbType.value = ""
businessType.value = ""
goodsCn.value = ""
}
fun confirmFilter() {
filterVisible.value = false
refresh()
}
fun initFilterLists() {
DictUtils.getWaybillTypeList(type = "II", addAll = true, checkedValue = awbType.value) {
awbTypeList.postValue(it)
}
DictUtils.getBusinessTypeList(type = "II", addAll = true, checkedValue = businessType.value) {
businessTypeList.postValue(it)
}
} }
override fun getData() { override fun getData() {

View File

@@ -17,9 +17,21 @@
android:background="@color/color_f2" android:background="@color/color_f2"
android:orientation="vertical"> android:orientation="vertical">
<!-- 标题栏 --> <!-- 标题栏(始终在最上层,不被遮挡) -->
<include layout="@layout/title_tool_bar" /> <include layout="@layout/title_tool_bar" />
<!-- 内容区域FrameLayout 包裹,筛选面板和遮罩只在此区域内) -->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<!-- 原有内容 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!-- 搜索区域 --> <!-- 搜索区域 -->
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -180,4 +192,29 @@
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
<!-- 遮罩层(只覆盖内容区域) -->
<View
android:id="@+id/filter_overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#80000000"
android:clickable="true"
android:focusable="true"
android:onClick="@{()-> viewModel.closeFilter()}"
android:visibility="gone" />
<!-- 筛选面板(从右侧滑入,只在内容区域内) -->
<include
android:id="@+id/filter_panel"
layout="@layout/layout_int_imp_query_filter"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="end"
android:visibility="gone"
app:viewModel="@{viewModel}" />
</FrameLayout>
</LinearLayout>
</layout> </layout>

View File

@@ -5,24 +5,40 @@
<import type="com.lukouguoji.module_base.ui.weight.data.layout.DataLayoutType" /> <import type="com.lukouguoji.module_base.ui.weight.data.layout.DataLayoutType" />
<variable <variable
name="model" name="viewModel"
type="com.lukouguoji.gjj.dialog.IntImpQueryFilterDialogModel" /> type="com.lukouguoji.gjj.viewModel.IntImpQueryViewModel" />
</data> </data>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/white" android:background="@color/white"
android:elevation="8dp"
android:orientation="vertical"> android:orientation="vertical">
<!-- 状态栏占位 --> <!-- 筛选标题栏 -->
<View <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="25dp" android:layout_height="50dp"
android:background="@color/white" /> android:gravity="center_vertical"
android:paddingHorizontal="15dp">
<!-- 标题栏 --> <ImageView
<include layout="@layout/title_tool_bar" /> android:layout_width="15dp"
android:layout_height="15dp"
android:onClick="@{()-> viewModel.closeFilter()}"
android:src="@mipmap/left_icon"
android:tint="#666666" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:text="筛选条件"
android:textColor="#666666"
android:textSize="16sp" />
</LinearLayout>
<com.google.android.material.divider.MaterialDivider <com.google.android.material.divider.MaterialDivider
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -42,72 +58,72 @@
<!-- 特码 --> <!-- 特码 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
hint='@{"请选择特码"}' hint='@{"请选择特码"}'
list="@{model.spCodeList}" list="@{viewModel.spCodeList}"
title='@{"特码"}' title='@{"特码"}'
titleLength="@{4}" titleLength="@{4}"
type="@{DataLayoutType.SPINNER}" type="@{DataLayoutType.SPINNER}"
value='@={model.spCode}' value='@={viewModel.spCode}' />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp" />
<!-- 航班号 --> <!-- 航班号 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
hint='@{"请输入航班号"}' hint='@{"请输入航班号"}'
title='@{"航班号"}' title='@{"航班号"}'
titleLength="@{4}" titleLength="@{4}"
type="@{DataLayoutType.INPUT}" type="@{DataLayoutType.INPUT}"
value='@={model.flightNo}' value='@={viewModel.flightNo}' />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp" />
<!-- 始发港 --> <!-- 始发港 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
hint='@{"请输入始发港"}' hint='@{"请输入始发港"}'
title='@{"始发港"}' title='@{"始发港"}'
titleLength="@{4}" titleLength="@{4}"
type="@{DataLayoutType.INPUT}" type="@{DataLayoutType.INPUT}"
value='@={model.origin}' value='@={viewModel.origin}' />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp" />
<!-- 运单类型 --> <!-- 运单类型 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
hint='@{"请选择运单类型"}' hint='@{"请选择运单类型"}'
list="@{model.awbTypeList}" list="@{viewModel.awbTypeList}"
title='@{"运单类型"}' title='@{"运单类型"}'
titleLength="@{4}" titleLength="@{4}"
type="@{DataLayoutType.SPINNER}" type="@{DataLayoutType.SPINNER}"
value='@={model.awbType}' value='@={viewModel.awbType}' />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp" />
<!-- 业务类型 --> <!-- 业务类型 -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
hint='@{"请选择业务类型"}' hint='@{"请选择业务类型"}'
list="@{model.businessTypeList}" list="@{viewModel.businessTypeList}"
title='@{"业务类型"}' title='@{"业务类型"}'
titleLength="@{4}" titleLength="@{4}"
type="@{DataLayoutType.SPINNER}" type="@{DataLayoutType.SPINNER}"
value='@={model.businessType}' value='@={viewModel.businessType}' />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp" />
<!-- 品名(中) --> <!-- 品名(中) -->
<com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew <com.lukouguoji.module_base.ui.weight.data.layout.PadDataLayoutNew
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
hint='@{"请输入品名"}' hint='@{"请输入品名"}'
title='@{"品名(中)"}' title='@{"品名(中)"}'
titleLength="@{4}" titleLength="@{4}"
type="@{DataLayoutType.INPUT}" type="@{DataLayoutType.INPUT}"
value='@={model.goodsCn}' value='@={viewModel.goodsCn}' />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp" />
<!-- 底部按钮区域 --> <!-- 底部按钮区域 -->
<LinearLayout <LinearLayout
@@ -127,7 +143,7 @@
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/bg_primary_radius_4" android:background="@drawable/bg_primary_radius_4"
android:gravity="center" android:gravity="center"
android:onClick="@{()-> model.onResetClick()}" android:onClick="@{()-> viewModel.resetFilter()}"
android:text="重置" android:text="重置"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" /> android:textSize="16sp" />
@@ -138,7 +154,7 @@
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/bg_primary_radius_4" android:background="@drawable/bg_primary_radius_4"
android:gravity="center" android:gravity="center"
android:onClick="@{()-> model.onConfirmClick()}" android:onClick="@{()-> viewModel.confirmFilter()}"
android:text="搜索" android:text="搜索"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" /> android:textSize="16sp" />