关联用户列表优化
This commit is contained in:
@@ -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<LinkedUserAdapter.LinkedUserViewHolder>() {
|
||||
|
||||
private val items = mutableListOf<LinkedUser>()
|
||||
private var _openSlideLayout: SlideLayout? = null
|
||||
|
||||
/** 供外部(如 Activity)访问当前展开的 SlideLayout */
|
||||
val openSlideLayout: SlideLayout?
|
||||
get() = _openSlideLayout
|
||||
|
||||
fun submitList(userList: List<LinkedUser>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user