Compare commits

...

2 Commits

Author SHA1 Message Date
fd84f18e4d 关联用户列表优化 2026-05-25 14:04:53 +08:00
fe025c8a69 关联用户滑动效果 2026-05-23 15:39:39 +08:00
10 changed files with 353 additions and 291 deletions

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker12650102232292672793.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -0,0 +1,48 @@
kotlin version: 2.2.10
error message: java.nio.file.AccessDeniedException: C:\Users\Administrator\AppData\Local\kotlin\daemon\kotlin-daemon-client-tsmarker2077405252922174295.tmp
error message: Daemon compilation failed: Could not connect to Kotlin compile daemon
java.lang.RuntimeException: Could not connect to Kotlin compile daemon
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemon(GradleKotlinCompilerWork.kt:214)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.compileWithDaemonOrFallbackImpl(GradleKotlinCompilerWork.kt:159)
at org.jetbrains.kotlin.compilerRunner.GradleKotlinCompilerWork.run(GradleKotlinCompilerWork.kt:111)
at org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction.execute(GradleCompilerRunnerWithWorkers.kt:74)
at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:68)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:64)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1$1.create(NoIsolationWorkerFactory.java:61)
at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:102)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.lambda$execute$0(NoIsolationWorkerFactory.java:61)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:209)
at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:166)
at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
at org.gradle.workers.internal.NoIsolationWorkerFactory$1.execute(NoIsolationWorkerFactory.java:58)
at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$0(DefaultWorkerExecutor.java:176)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
at org.gradle.internal.Factories$1.create(Factories.java:30)
at org.gradle.internal.work.DefaultWorkerLeaseService.lambda$withLocksAcquired$0(DefaultWorkerLeaseService.java:269)
at org.gradle.internal.work.ResourceLockStatistics$1.measure(ResourceLockStatistics.java:42)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocksAcquired(DefaultWorkerLeaseService.java:267)
at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:259)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:47)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
at java.base/java.lang.Thread.run(Thread.java:1583)

View File

@@ -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")
}

View File

@@ -15,10 +15,8 @@ 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>()
@@ -36,12 +34,6 @@ class LinkedUserAdapter(
}
}
fun closeSwipe(position: Int) {
if (position in items.indices) {
notifyItemChanged(position)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LinkedUserViewHolder {
val binding = ItemLinkedUserBinding.inflate(
LayoutInflater.from(parent.context),
@@ -52,53 +44,24 @@ class LinkedUserAdapter(
}
override fun onBindViewHolder(holder: LinkedUserViewHolder, position: Int) {
holder.bind(items[position], position)
}
override fun getItemCount(): Int = items.size
inner class LinkedUserViewHolder(
private val binding: ItemLinkedUserBinding
val binding: ItemLinkedUserBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: LinkedUser, position: Int) {
binding.tvPhone.text = "手机号码:${item.phone}"
binding.tvIdCard.text = "身份证号:${item.idCard}"
binding.llContent.translationX = 0f
binding.tvUserName.text = item.name
binding.tvTime.text = item.time
binding.tvPhone.text = "手机号码:${item.phone}"
binding.tvIdCard.text = "身份证号:${item.idCard}"
// MODE_SETTINGS → 显示"解除关联"MODE_PERSONAL → 显示"删除"
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.llContent.setOnClickListener {
if (binding.llContent.translationX < 0f) {
binding.llContent.animate()
.translationX(0f)
.setDuration(200)
.start()
} else {
onItemClick(item, position)
}
}
binding.tvDeleteAction.setOnClickListener {
onDeleteClick(item, position)
}
binding.tvUnlinkAction.setOnClickListener {
onDeleteClick(item, position)
binding.root.setOnClickListener {
onItemClick(item, position)
}
}
fun getContentLayout(): View = binding.llContent
}
}

View File

@@ -18,7 +18,7 @@ import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import androidx.recyclerview.widget.RecyclerView
class AddLinkedUserActivity : AppCompatActivity() {
private lateinit var binding: AddLinkedUserActivityBinding
private val selectedPhoneSet = mutableSetOf<String>()
@@ -37,9 +37,16 @@ class AddLinkedUserActivity : AppCompatActivity() {
initRecyclerView()
initListener()
setupSwipeRefresh()
fetchFamilyMembers()
}
private fun setupSwipeRefresh() {
binding.swipeRefresh.setOnRefreshListener {
fetchFamilyMembers()
}
}
private fun initRecyclerView() {
binding.rvAddLinkedUser.layoutManager = LinearLayoutManager(this)
binding.rvAddLinkedUser.adapter =
@@ -52,13 +59,31 @@ class AddLinkedUserActivity : AppCompatActivity() {
selectedUserIdMap.remove(phone)
}
}
// 设置上拉加载更多
binding.rvAddLinkedUser.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) {
fetchFamilyMembers()
}
}
}
})
}
private fun fetchFamilyMembers() {
binding.swipeRefresh.isRefreshing = true
lifecycleScope.launch {
try {
val response = RetrofitNetwork.getNetworkService().getFamilyMembers()
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
if (response.code == 200) {
if (response.data != null && response.data.isNotEmpty()) {
updateFamilyMemberList(response.data)
@@ -72,6 +97,7 @@ class AddLinkedUserActivity : AppCompatActivity() {
} catch (e: Exception) {
android.util.Log.e("AddLinkedUserActivity", "网络请求异常", e)
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
Toast.makeText(this@AddLinkedUserActivity, "网络请求失败: ${e.message}", Toast.LENGTH_SHORT).show()
}
}

View File

@@ -4,12 +4,11 @@ import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.defenseapplication.R
@@ -24,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
@@ -37,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
@@ -75,139 +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
// 根据模式只显示一个按钮,滑动露出完整按钮
val swipeThresholdPx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, -100f, resources.displayMetrics
)
val heldPositions = mutableMapOf<Int, Boolean>()
(binding.rvLinkedUser as RecyclerView).layoutManager = LinearLayoutManager(this)
var currentlyHeldPosition: Int = -1
val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean = false
binding.swipeRefresh.setOnRefreshListener {
fetchUserList()
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
}
override fun onChildDraw(
c: android.graphics.Canvas,
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dX: Float,
dY: Float,
actionState: Int,
isCurrentlyActive: Boolean
) {
if (viewHolder is LinkedUserAdapter.LinkedUserViewHolder) {
val contentLayout = viewHolder.getContentLayout()
val position = viewHolder.adapterPosition
// 如果当前滑动的位置和已展开的位置不同,先收起已展开的项
if (isCurrentlyActive && currentlyHeldPosition != -1 && currentlyHeldPosition != position) {
val oldViewHolder = recyclerView.findViewHolderForAdapterPosition(currentlyHeldPosition)
if (oldViewHolder is LinkedUserAdapter.LinkedUserViewHolder) {
oldViewHolder.getContentLayout().animate()
.translationX(0f)
.setDuration(200)
.start()
}
heldPositions.remove(currentlyHeldPosition)
currentlyHeldPosition = -1
}
if (isCurrentlyActive) {
val isHeld = heldPositions[position]
if (isHeld == true) {
if (dX > 0) {
// 已展开时向右滑动 → 平滑收回
val newX = swipeThresholdPx + dX
// 不能超过原位
contentLayout.translationX = if (newX > 0) 0f else newX
} else {
// 保持展开或继续左滑(限制不超过阈值)
contentLayout.translationX = swipeThresholdPx
}
} else {
// 未展开,正常滑动限制(左滑不超过阈值,右滑不超过原位)
val swipeDistance = when {
dX < swipeThresholdPx -> swipeThresholdPx
dX > 0 -> 0f
else -> dX
}
contentLayout.translationX = swipeDistance
}
} else {
val isHeld = heldPositions[position]
if (isHeld == true) {
contentLayout.translationX = swipeThresholdPx
}
}
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
}
}
/*
- 滑动超过一半后松手:列表项 保持在滑动位置,显示按钮
- 滑动未超过一半松手:列表项 恢复原位
- 已展开状态下向右拖动归位或拖动超过一半松手:恢复原位
* */
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
if (viewHolder is LinkedUserAdapter.LinkedUserViewHolder) {
val position = viewHolder.adapterPosition
val contentLayout = viewHolder.getContentLayout()
// 判断滑动距离是否超过一半阈值(注意都是负数,所以绝对值比较)
if (Math.abs(contentLayout.translationX) > Math.abs(swipeThresholdPx / 2)) {
heldPositions[position] = true
currentlyHeldPosition = position
contentLayout.translationX = swipeThresholdPx
} else {
heldPositions.remove(position)
if (currentlyHeldPosition == position) {
currentlyHeldPosition = -1
}
contentLayout.animate()
.translationX(0f)
.setDuration(300)
.start()
}
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)
}
}
override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float = 0.05f
})
itemTouchHelper.attachToRecyclerView(binding.rvLinkedUser)
binding.rvLinkedUser.setOnItemMenuClickListener { _, position ->
// 直接关闭菜单(使用 RecyclerView 的方法)
binding.rvLinkedUser.smoothCloseMenu() // 或 closeMenu()
val user = userList[position]
showDeleteDialog(user, position)
}
(binding.rvLinkedUser as RecyclerView).adapter = adapter
}
private var currentSwipePosition: Int = -1
private var currentSwipeHeld: Boolean = false
private fun showUnlinkDialog(user: LinkedUser, position: Int) {
currentSwipePosition = position
currentSwipeHeld = true
private fun showDeleteDialog(user: LinkedUser, position: Int) {
val dialog = CustomDialogFragmentText()
if (mode == MODE_SETTINGS) {
dialog.setTitle(getString(R.string.tip))
@@ -217,22 +134,16 @@ 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 {
resetSwipeState()
}
dialog.show(supportFragmentManager, "UnlinkDialog")
dialog.show(supportFragmentManager, "DeleteDialog")
}
private fun deleteDeviceUser(user: LinkedUser, position: Int) {
val deviceId = DeviceIdEvent.getDeviceId()
if (deviceId.isNullOrEmpty() || user.userId.isEmpty()) {
Toast.makeText(this, "参数错误", Toast.LENGTH_SHORT).show()
resetSwipeState()
return
}
@@ -246,57 +157,45 @@ 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()
}
resetSwipeState()
}
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
Toast.makeText(this@LinkedUserActivity, "网络请求失败", Toast.LENGTH_SHORT).show()
resetSwipeState()
}
}
}
}
private fun resetSwipeState() {
if (currentSwipePosition >= 0 && currentSwipeHeld) {
binding.rvLinkedUser.post {
val viewHolder = binding.rvLinkedUser.findViewHolderForAdapterPosition(currentSwipePosition)
if (viewHolder is LinkedUserAdapter.LinkedUserViewHolder) {
viewHolder.getContentLayout().animate()
.translationX(0f)
.setDuration(300)
.start()
}
currentSwipePosition = -1
currentSwipeHeld = false
}
}
}
private fun initData() {
fetchUserList()
}
private fun fetchUserList() {
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()
}
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
if (response.code == 200 && response.data != null) {
if (mode == MODE_SETTINGS) {
updateDeviceUserList(response.data as List<DeviceUserBean>)
@@ -310,6 +209,7 @@ class LinkedUserActivity : AppCompatActivity() {
} catch (e: Exception) {
e.printStackTrace()
withContext(Dispatchers.Main) {
binding.swipeRefresh.isRefreshing = false
Toast.makeText(this@LinkedUserActivity, "网络请求失败", Toast.LENGTH_SHORT).show()
}
}
@@ -398,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)
}
}
}
}
}

View File

@@ -0,0 +1,118 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/bg_gray"
tools:context=".liveVideo.LinkedUserActivity">
<!-- 标题栏 -->
<LinearLayout
android:id="@+id/topBar"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center_vertical"
android:paddingHorizontal="16dp"
android:background="@color/white">
<ImageView
android:id="@+id/ivBack"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/back" />
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center"
android:text="@string/linked_user"
android:textColor="@color/black"
android:textSize="16dp"
android:textStyle="bold" />
</LinearLayout>
<!-- 搜索区域 -->
<LinearLayout
android:id="@+id/searchLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/white"
android:paddingHorizontal="16dp"
android:paddingVertical="12dp"
android:layout_marginTop="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@drawable/bg_user_manage"
android:gravity="center_vertical"
android:paddingHorizontal="16dp">
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:src="@drawable/search"
android:layout_marginRight="8dp"/>
<EditText
android:id="@+id/etSearch"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="@null"
android:hint="@string/search_preset_text"
android:textColorHint="@color/gray"
android:textColor="@color/black"
android:textSize="14sp"
android:inputType="text"
android:imeOptions="actionSearch"
android:singleLine="true"/>
</LinearLayout>
</LinearLayout>
<!-- 列表区域 -->
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<com.yanzhenjie.recyclerview.SwipeRecyclerView
android:id="@+id/rvLinkedUser"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- 底部按钮 -->
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingBottom="60dp">
<android.widget.Button
android:id="@+id/btnAddLinkedUser"
android:layout_width="match_parent"
android:layout_height="40dp"
android:text="@string/add_linked_user"
android:textColor="@color/white"
android:textSize="14dp"
android:background="@drawable/rounded_blue_bg" />
<!-- 悬浮加号按钮 -->
<ImageView
android:visibility="gone"
android:layout_gravity="end"
android:layout_width="48dp"
android:layout_height="48dp"
android:src="@drawable/add_img"
android:id="@+id/fabAdd"/>
</LinearLayout>
</LinearLayout>

View File

@@ -68,14 +68,19 @@
</LinearLayout>
<!-- 列表区域 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvAddLinkedUser"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"/>
android:layout_weight="1">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvAddLinkedUser"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- 底部按钮 -->
<LinearLayout

View File

@@ -1,86 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="120dp"
android:background="@color/white"
android:orientation="vertical"
android:padding="16dp">
<!-- 背景按钮层:删除 + 解除关联 两个按钮 -->
<LinearLayout
android:id="@+id/llActions"
android:layout_width="match_parent"
android:layout_height="120dp"
android:gravity="end"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tvDeleteAction"
android:layout_width="100dp"
android:layout_height="match_parent"
android:background="@color/red"
android:gravity="center"
android:text="@string/delete"
android:textColor="@color/white"
android:id="@+id/tvUserName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvUnlinkAction"
android:layout_width="100dp"
android:layout_height="match_parent"
android:background="@color/red"
android:gravity="center"
android:text="@string/unlink"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold" />
</LinearLayout>
<!-- 前景内容层:随 swipe 移动 -->
<LinearLayout
android:id="@+id/llContent"
android:layout_width="match_parent"
android:layout_height="120dp"
android:background="@color/white"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tvUserName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:textColor="@color/black"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/gray"
android:textSize="12sp" />
</LinearLayout>
<TextView
android:id="@+id/tvPhone"
android:id="@+id/tvTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="@color/gray"
android:textSize="14sp" />
<TextView
android:id="@+id/tvIdCard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/gray"
android:textSize="14sp" />
android:textSize="12sp" />
</LinearLayout>
</FrameLayout>
<TextView
android:id="@+id/tvPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="@color/gray"
android:textSize="14sp" />
<TextView
android:id="@+id/tvIdCard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/gray"
android:textSize="14sp" />
</LinearLayout>

View File

@@ -73,14 +73,19 @@
</LinearLayout>
</LinearLayout>
<!-- 列表区域 -->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvLinkedUser"
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"/>
android:layout_weight="1">
<com.yanzhenjie.recyclerview.SwipeRecyclerView
android:id="@+id/rvLinkedUser"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="16dp"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<!-- 底部按钮 -->
<LinearLayout