From fd84f18e4dd9fd377542f0b47007e82a058c10a4 Mon Sep 17 00:00:00 2001 From: maxinyu Date: Mon, 25 May 2026 14:04:53 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=B3=E8=81=94=E7=94=A8=E6=88=B7=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle.kts | 1 + .../adapter/LinkedUserAdapter.kt | 70 +------ .../liveVideo/LinkedUserActivity.kt | 104 +++++------ .../defenseapplication/view/SlideLayout.kt | 176 ------------------ .../res/layout-land/linked_user_activity.xml | 118 ++++++++++++ app/src/main/res/layout/item_linked_user.xml | 101 +++------- .../main/res/layout/linked_user_activity.xml | 2 +- 7 files changed, 206 insertions(+), 366 deletions(-) delete mode 100644 app/src/main/java/com/example/defenseapplication/view/SlideLayout.kt create mode 100644 app/src/main/res/layout-land/linked_user_activity.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 64c33e1..cc6fe97 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -102,5 +102,6 @@ dependencies { implementation("androidx.camera:camera-view:1.0.0-alpha25") //上拉刷新下拉加载 implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") + implementation("com.yanzhenjie.recyclerview:x:1.3.2") } \ No newline at end of file diff --git a/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt b/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt index 18990b2..1a96c8c 100644 --- a/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt +++ b/app/src/main/java/com/example/defenseapplication/adapter/LinkedUserAdapter.kt @@ -5,7 +5,6 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.example.defenseapplication.databinding.ItemLinkedUserBinding -import com.example.defenseapplication.view.SlideLayout data class LinkedUser( val userId: String, @@ -16,18 +15,11 @@ data class LinkedUser( ) class LinkedUserAdapter( - private val deleteText: String, private val isUnlinkMode: Boolean, - private val onItemClick: (LinkedUser, Int) -> Unit, - private val onDeleteClick: (LinkedUser, Int) -> Unit + private val onItemClick: (LinkedUser, Int) -> Unit ) : RecyclerView.Adapter() { private val items = mutableListOf() - private var _openSlideLayout: SlideLayout? = null - - /** 供外部(如 Activity)访问当前展开的 SlideLayout */ - val openSlideLayout: SlideLayout? - get() = _openSlideLayout fun submitList(userList: List) { items.clear() @@ -52,54 +44,7 @@ class LinkedUserAdapter( } override fun onBindViewHolder(holder: LinkedUserViewHolder, position: Int) { - val item = items[position] - holder.bind(item) - - // 每次 bind 都设置监听器,RecyclerView 复用视图时也会调用 - val slideLayout = holder.itemView as SlideLayout - slideLayout.setOnSlideChangeListener(object : SlideLayout.OnSlideChangeListener { - override fun onMenuOpen(slideLayout: SlideLayout) { - val prev = _openSlideLayout - if (prev != null && prev != slideLayout) { - prev.closeMenu() - } - _openSlideLayout = slideLayout - } - - override fun onMenuClose(slideLayout: SlideLayout) { - if (_openSlideLayout == slideLayout) { - _openSlideLayout = null - } - } - - override fun onClick(slideLayout: SlideLayout) { - val prev = _openSlideLayout - if (prev != null && prev != slideLayout) { - prev.closeMenu() - } - } - }) - - holder.binding.llContent.setOnClickListener { - val pos = holder.bindingAdapterPosition - if (pos != RecyclerView.NO_POSITION) { - onItemClick(items[pos], pos) - } - } - - holder.binding.tvDeleteAction.setOnClickListener { - val pos = holder.bindingAdapterPosition - if (pos != RecyclerView.NO_POSITION) { - onDeleteClick(items[pos], pos) - } - } - - holder.binding.tvUnlinkAction.setOnClickListener { - val pos = holder.bindingAdapterPosition - if (pos != RecyclerView.NO_POSITION) { - onDeleteClick(items[pos], pos) - } - } + holder.bind(items[position], position) } override fun getItemCount(): Int = items.size @@ -108,19 +53,14 @@ class LinkedUserAdapter( val binding: ItemLinkedUserBinding ) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: LinkedUser) { + fun bind(item: LinkedUser, position: Int) { binding.tvUserName.text = item.name binding.tvTime.text = item.time binding.tvPhone.text = "手机号码:${item.phone}" binding.tvIdCard.text = "身份证号:${item.idCard}" - if (isUnlinkMode) { - binding.tvUnlinkAction.visibility = View.VISIBLE - binding.tvDeleteAction.visibility = View.GONE - } else { - binding.tvUnlinkAction.visibility = View.GONE - binding.tvDeleteAction.visibility = View.VISIBLE - binding.tvDeleteAction.text = deleteText + binding.root.setOnClickListener { + onItemClick(item, position) } } } diff --git a/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserActivity.kt b/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserActivity.kt index 33dc062..6b684cb 100644 --- a/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserActivity.kt +++ b/app/src/main/java/com/example/defenseapplication/liveVideo/LinkedUserActivity.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.view.View +import android.view.ViewGroup import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope @@ -22,10 +23,12 @@ import com.example.defenseapplication.utils.DeviceUserUpdateEvent import com.example.defenseapplication.utils.RetrofitNetwork import com.example.defenseapplication.view.CustomDialogFragmentText import com.gyf.immersionbar.ImmersionBar +import com.yanzhenjie.recyclerview.SwipeMenuItem +import com.yanzhenjie.recyclerview.SwipeRecyclerView import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -//关联用户页面 + class LinkedUserActivity : AppCompatActivity() { private lateinit var binding: LinkedUserActivityBinding @@ -35,8 +38,6 @@ class LinkedUserActivity : AppCompatActivity() { private var mode: Int = MODE_SETTINGS companion object { - const val REQUEST_CODE_SEARCH = 1001 - const val EXTRA_SEARCH_RESULT = "search_result" const val EXTRA_MODE = "extra_mode" const val MODE_SETTINGS = 0 const val MODE_PERSONAL = 1 @@ -73,45 +74,57 @@ class LinkedUserActivity : AppCompatActivity() { } private fun initView() { - val deleteText = if (mode == MODE_SETTINGS) getString(R.string.unlink) else getString(R.string.delete) - val isUnlinkMode = mode == MODE_SETTINGS adapter = LinkedUserAdapter( - deleteText = deleteText, - isUnlinkMode = isUnlinkMode, + isUnlinkMode = mode == MODE_SETTINGS, onItemClick = { user, position -> val intent = Intent(this, UserDetailsActivity::class.java).apply { putExtra("userId", user.userId) + putExtra("deleteText", if (mode == MODE_SETTINGS) getString(R.string.unlink) else getString(R.string.delete)) + putExtra("isUnlinkMode", mode == MODE_SETTINGS) } startActivity(intent) - }, - onDeleteClick = { user, position -> - showUnlinkDialog(user, position) } ) - binding.rvLinkedUser.layoutManager = LinearLayoutManager(this) - binding.rvLinkedUser.adapter = adapter + + (binding.rvLinkedUser as RecyclerView).layoutManager = LinearLayoutManager(this) binding.swipeRefresh.setOnRefreshListener { fetchUserList() } - binding.rvLinkedUser.addOnScrollListener(object : RecyclerView.OnScrollListener() { - override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { - super.onScrolled(recyclerView, dx, dy) - val layoutManager = recyclerView.layoutManager as LinearLayoutManager - val totalItemCount = layoutManager.itemCount - val lastVisibleItem = layoutManager.findLastVisibleItemPosition() - if (totalItemCount > 0 && lastVisibleItem >= totalItemCount - 3) { - if (!binding.swipeRefresh.isRefreshing) { - fetchUserList() - } + binding.rvLinkedUser.setSwipeMenuCreator { leftMenu, rightMenu, position -> + if (mode == MODE_SETTINGS) { + val unlinkItem = SwipeMenuItem(this@LinkedUserActivity).apply { + setText(getString(R.string.unlink)) + setBackgroundColor(getColor(R.color.red)) + setTextColor(getColor(R.color.white)) + width = 200 + height = ViewGroup.LayoutParams.MATCH_PARENT } + rightMenu.addMenuItem(unlinkItem) + } else { + val deleteItem = SwipeMenuItem(this@LinkedUserActivity).apply { + setText(getString(R.string.delete)) + setBackgroundColor(getColor(R.color.red)) + setTextColor(getColor(R.color.white)) + width = 200 + height = ViewGroup.LayoutParams.MATCH_PARENT + } + rightMenu.addMenuItem(deleteItem) } - }) + } + + binding.rvLinkedUser.setOnItemMenuClickListener { _, position -> + // 直接关闭菜单(使用 RecyclerView 的方法) + binding.rvLinkedUser.smoothCloseMenu() // 或 closeMenu() + val user = userList[position] + showDeleteDialog(user, position) + } + + (binding.rvLinkedUser as RecyclerView).adapter = adapter } - private fun showUnlinkDialog(user: LinkedUser, position: Int) { - + private fun showDeleteDialog(user: LinkedUser, position: Int) { val dialog = CustomDialogFragmentText() if (mode == MODE_SETTINGS) { dialog.setTitle(getString(R.string.tip)) @@ -121,18 +134,10 @@ class LinkedUserActivity : AppCompatActivity() { dialog.setMessage(getString(R.string.confirm_delete_user)) } dialog.setShowInput(false) - dialog.setPositiveText(getString(R.string.confirm)) - dialog.setNegativeText(getString(R.string.cancel)) dialog.setOnConfirmListener { deleteDeviceUser(user, position) } - dialog.setOnDismissListener { - // Dialog 关闭后,如果之前打开了菜单则关闭它 - if (adapter.openSlideLayout != null) { - adapter.openSlideLayout?.closeMenu() - } - } - dialog.show(supportFragmentManager, "UnlinkDialog") + dialog.show(supportFragmentManager, "DeleteDialog") } private fun deleteDeviceUser(user: LinkedUser, position: Int) { @@ -152,7 +157,8 @@ class LinkedUserActivity : AppCompatActivity() { withContext(Dispatchers.Main) { if (response.code == 200) { userList.removeAt(position) - adapter.removeItem(position) + adapter.notifyItemRemoved(position) // 通知删除动画 + adapter.notifyItemRangeChanged(position, userList.size - position) // 更新后续位置索引 Toast.makeText(this@LinkedUserActivity, "删除成功", Toast.LENGTH_SHORT).show() } else { Toast.makeText(this@LinkedUserActivity, response.msg, Toast.LENGTH_SHORT).show() @@ -175,12 +181,15 @@ class LinkedUserActivity : AppCompatActivity() { binding.swipeRefresh.isRefreshing = true lifecycleScope.launch { try { - val response = if (mode == MODE_SETTINGS) { - val deviceId = DeviceIdEvent.getDeviceId() - if (deviceId.isNullOrEmpty()) { - Toast.makeText(this@LinkedUserActivity, "设备 ID 为空", Toast.LENGTH_SHORT).show() - return@launch + val deviceId = DeviceIdEvent.getDeviceId() + if (deviceId.isNullOrEmpty()) { + Toast.makeText(this@LinkedUserActivity, "设备 ID 为空", Toast.LENGTH_SHORT).show() + withContext(Dispatchers.Main) { + binding.swipeRefresh.isRefreshing = false } + return@launch + } + val response = if (mode == MODE_SETTINGS) { RetrofitNetwork.getNetworkService().getDeviceUserList(deviceId) } else { RetrofitNetwork.getNetworkService().getFamilyMembers() @@ -289,18 +298,5 @@ class LinkedUserActivity : AppCompatActivity() { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) - if (requestCode == REQUEST_CODE_SEARCH && resultCode == RESULT_OK) { - data?.getStringExtra(EXTRA_SEARCH_RESULT)?.let { result -> - val newUser = LinkedUser( - userId = "", - name = result, - phone = "13800000000", - idCard = "110101199001011234", - time = "2026-05-10 ${android.text.format.DateFormat.format("HH:mm:ss", System.currentTimeMillis())}" - ) - userList.add(0, newUser) - adapter.submitList(userList) - } - } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/example/defenseapplication/view/SlideLayout.kt b/app/src/main/java/com/example/defenseapplication/view/SlideLayout.kt deleted file mode 100644 index fdca350..0000000 --- a/app/src/main/java/com/example/defenseapplication/view/SlideLayout.kt +++ /dev/null @@ -1,176 +0,0 @@ -package com.example.defenseapplication.view - -import android.content.Context -import android.util.AttributeSet -import android.view.MotionEvent -import android.view.View -import android.view.ViewParent -import android.widget.FrameLayout -import android.widget.OverScroller - -class SlideLayout @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - defStyleAttr: Int = 0 -) : FrameLayout(context, attrs, defStyleAttr) { - - private val scroller = OverScroller(context) - - private var downX: Float = 0f - private var downY: Float = 0f - private var lastX: Float = 0f - - private var isHorizontalDragging: Boolean = false - - private var listener: OnSlideChangeListener? = null - private var shouldMenuBeOpen: Boolean = false - - /** 缓存菜单宽度,避免 RecyclerView 复用时 measuredWidth 读取不到 */ - private var cachedMenuWidth: Int = 0 - - interface OnSlideChangeListener { - fun onMenuOpen(slideLayout: SlideLayout) - fun onMenuClose(slideLayout: SlideLayout) - fun onClick(slideLayout: SlideLayout) - } - - fun setOnSlideChangeListener(listener: OnSlideChangeListener) { - this.listener = listener - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - // 缓存菜单宽度,在 onLayout 和 onTouchEvent 中使用 - cachedMenuWidth = if (childCount >= 2) getChildAt(0).measuredWidth else 0 - } - - private fun getMenuWidth(): Int { - // 优先使用缓存值,RecyclerView 复用时 measuredWidth 可能尚未更新 - val w = cachedMenuWidth - return if (w > 0) w else { - if (childCount >= 2) { - val measured = getChildAt(0).measuredWidth - if (measured > 0) measured else getChildAt(0).width - } else 0 - } - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - super.onLayout(changed, left, top, right, bottom) - if (childCount < 2) return - val contentWidth = right - left - val menu = getChildAt(0) - val menuW = getMenuWidth() - // 内容左移 scrollX → 菜单右边缘逐渐露出 - // 菜单左边界 = 内容右边缘 - scrollX - menu.layout(contentWidth - scrollX, top, contentWidth - scrollX + menuW, bottom) - } - - override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { - when (ev.action) { - MotionEvent.ACTION_DOWN -> { - downX = ev.x - downY = ev.y - lastX = ev.x - isHorizontalDragging = false - } - MotionEvent.ACTION_MOVE -> { - val dx = Math.abs(ev.x - downX) - val dy = Math.abs(ev.y - downY) - if (dx > dy && dx > 10f) { - isHorizontalDragging = true - disallowInterceptChain(true) - return true - } - } - } - return super.onInterceptTouchEvent(ev) - } - - override fun onTouchEvent(event: MotionEvent): Boolean { - when (event.action) { - MotionEvent.ACTION_DOWN -> { - downX = event.x - downY = event.y - lastX = event.x - disallowInterceptChain(true) - return true - } - MotionEvent.ACTION_MOVE -> { - val dx = lastX - event.x - lastX = event.x - - if (Math.abs(event.x - downX) > 10f) { - isHorizontalDragging = true - } - - if (isHorizontalDragging) { - val menuW = getMenuWidth() - var newScrollX = scrollX + dx.toInt() - if (newScrollX < 0) newScrollX = 0 - if (newScrollX > menuW) newScrollX = menuW - scrollTo(newScrollX, 0) - } - } - MotionEvent.ACTION_UP -> { - val wasDragging = isHorizontalDragging - isHorizontalDragging = false - disallowInterceptChain(false) - - if (!wasDragging) { - listener?.onClick(this) - } else { - val menuW = getMenuWidth() - if (scrollX > menuW / 2) { - openMenu() - } else { - closeMenu() - } - } - } - MotionEvent.ACTION_CANCEL -> { - isHorizontalDragging = false - disallowInterceptChain(false) - // 弹窗等外部中断:按当前菜单是否露出状态决定动画关闭或保持关闭 - if (scrollX > 0) { - closeMenu() - } - } - } - return true - } - - private fun disallowInterceptChain(disallow: Boolean) { - var parent: ViewParent? = parent - while (parent != null) { - parent.requestDisallowInterceptTouchEvent(disallow) - parent = parent.parent - } - } - - override fun computeScroll() { - if (scroller.computeScrollOffset()) { - scrollTo(scroller.currX, scroller.currY) - postInvalidateOnAnimation() - } - } - - fun openMenu() { - val menuW = getMenuWidth() - if (shouldMenuBeOpen && scrollX == menuW) return - shouldMenuBeOpen = true - scroller.startScroll(scrollX, 0, menuW - scrollX, 0, 300) - postInvalidateOnAnimation() - listener?.onMenuOpen(this) - } - - fun closeMenu() { - if (!shouldMenuBeOpen && scrollX == 0) return - shouldMenuBeOpen = false - scroller.startScroll(scrollX, 0, -scrollX, 0, 300) - postInvalidateOnAnimation() - listener?.onMenuClose(this) - } - - fun isMenuOpened(): Boolean = shouldMenuBeOpen -} diff --git a/app/src/main/res/layout-land/linked_user_activity.xml b/app/src/main/res/layout-land/linked_user_activity.xml new file mode 100644 index 0000000..675c16b --- /dev/null +++ b/app/src/main/res/layout-land/linked_user_activity.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_linked_user.xml b/app/src/main/res/layout/item_linked_user.xml index e4ac436..5eff3d9 100644 --- a/app/src/main/res/layout/item_linked_user.xml +++ b/app/src/main/res/layout/item_linked_user.xml @@ -1,86 +1,47 @@ - + android:layout_height="120dp" + android:background="@color/white" + android:orientation="vertical" + android:padding="16dp"> - - - - - - - - - - - - - - - - + android:textSize="12sp" /> - + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/linked_user_activity.xml b/app/src/main/res/layout/linked_user_activity.xml index ebf8636..675c16b 100644 --- a/app/src/main/res/layout/linked_user_activity.xml +++ b/app/src/main/res/layout/linked_user_activity.xml @@ -78,7 +78,7 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"> -