Compare commits

..

2 Commits

Author SHA1 Message Date
b16ced1c18 关联用户滑动 2026-05-23 08:57:31 +08:00
f3c1ae70d4 优化UI 2026-05-22 17:42:50 +08:00
6 changed files with 128 additions and 51 deletions

View File

@@ -16,6 +16,7 @@ data class LinkedUser(
class LinkedUserAdapter( class LinkedUserAdapter(
private val deleteText: String, private val deleteText: String,
private val isUnlinkMode: Boolean,
private val onItemClick: (LinkedUser, Int) -> Unit, private val onItemClick: (LinkedUser, Int) -> Unit,
private val onDeleteClick: (LinkedUser, Int) -> Unit private val onDeleteClick: (LinkedUser, Int) -> Unit
) : RecyclerView.Adapter<LinkedUserAdapter.LinkedUserViewHolder>() { ) : RecyclerView.Adapter<LinkedUserAdapter.LinkedUserViewHolder>() {
@@ -35,6 +36,12 @@ class LinkedUserAdapter(
} }
} }
fun closeSwipe(position: Int) {
if (position in items.indices) {
notifyItemChanged(position)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LinkedUserViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): LinkedUserViewHolder {
val binding = ItemLinkedUserBinding.inflate( val binding = ItemLinkedUserBinding.inflate(
LayoutInflater.from(parent.context), LayoutInflater.from(parent.context),
@@ -61,13 +68,33 @@ class LinkedUserAdapter(
binding.llContent.translationX = 0f binding.llContent.translationX = 0f
binding.tvUserName.text = item.name binding.tvUserName.text = item.name
binding.tvTime.text = item.time binding.tvTime.text = item.time
//设置删除按钮的文字
// 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.tvDeleteAction.text = deleteText
binding.llContent.setOnClickListener {
onItemClick(item, position)
} }
binding.llDelete.setOnClickListener { 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) onDeleteClick(item, position)
} }
} }

View File

@@ -2,7 +2,7 @@ package com.example.defenseapplication.liveVideo
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.TypedValue
import android.view.View import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -73,8 +73,10 @@ class LinkedUserActivity : AppCompatActivity() {
private fun initView() { private fun initView() {
val deleteText = if (mode == MODE_SETTINGS) getString(R.string.unlink) else getString(R.string.delete) val deleteText = if (mode == MODE_SETTINGS) getString(R.string.unlink) else getString(R.string.delete)
val isUnlinkMode = mode == MODE_SETTINGS
adapter = LinkedUserAdapter( adapter = LinkedUserAdapter(
deleteText = deleteText, deleteText = deleteText,
isUnlinkMode = isUnlinkMode,
onItemClick = { user, position -> onItemClick = { user, position ->
val intent = Intent(this, UserDetailsActivity::class.java).apply { val intent = Intent(this, UserDetailsActivity::class.java).apply {
putExtra("name", user.name) putExtra("name", user.name)
@@ -91,9 +93,14 @@ class LinkedUserActivity : AppCompatActivity() {
binding.rvLinkedUser.layoutManager = LinearLayoutManager(this) binding.rvLinkedUser.layoutManager = LinearLayoutManager(this)
binding.rvLinkedUser.adapter = adapter binding.rvLinkedUser.adapter = adapter
val swipeThreshold = -300f // 根据模式只显示一个按钮,滑动露出完整按钮
val swipeThresholdPx = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, -100f, resources.displayMetrics
)
val heldPositions = mutableMapOf<Int, Boolean>() val heldPositions = mutableMapOf<Int, Boolean>()
var currentlyHeldPosition: Int = -1
val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) { val itemTouchHelper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT) {
override fun onMove( override fun onMove(
recyclerView: RecyclerView, recyclerView: RecyclerView,
@@ -117,21 +124,53 @@ class LinkedUserActivity : AppCompatActivity() {
val contentLayout = viewHolder.getContentLayout() val contentLayout = viewHolder.getContentLayout()
val position = viewHolder.adapterPosition 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) { if (isCurrentlyActive) {
val swipeDistance = if (dX < swipeThreshold) swipeThreshold else dX 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 contentLayout.translationX = swipeDistance
}
} else { } else {
val isHeld = heldPositions[position] val isHeld = heldPositions[position]
if (isHeld == true) { if (isHeld == true) {
contentLayout.translationX = swipeThreshold contentLayout.translationX = swipeThresholdPx
} }
} }
} }
} }
/* /*
- 滑动超过一半后松手:列表项 保持在滑动位置 ,显示"解除关联"按钮 - 滑动超过一半后松手:列表项 保持在滑动位置,显示按钮
- 滑动未超过一半松手:列表项 恢复原位 - 滑动未超过一半松手:列表项 恢复原位
- 已展开状态下向右拖动归位或拖动超过一半松手:恢复原位
* */ * */
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) { override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder) super.clearView(recyclerView, viewHolder)
@@ -139,11 +178,16 @@ class LinkedUserActivity : AppCompatActivity() {
val position = viewHolder.adapterPosition val position = viewHolder.adapterPosition
val contentLayout = viewHolder.getContentLayout() val contentLayout = viewHolder.getContentLayout()
if (contentLayout.translationX < swipeThreshold / 2) { // 判断滑动距离是否超过一半阈值(注意都是负数,所以绝对值比较)
if (Math.abs(contentLayout.translationX) > Math.abs(swipeThresholdPx / 2)) {
heldPositions[position] = true heldPositions[position] = true
contentLayout.translationX = swipeThreshold currentlyHeldPosition = position
contentLayout.translationX = swipeThresholdPx
} else { } else {
heldPositions.remove(position) heldPositions.remove(position)
if (currentlyHeldPosition == position) {
currentlyHeldPosition = -1
}
contentLayout.animate() contentLayout.animate()
.translationX(0f) .translationX(0f)
.setDuration(300) .setDuration(300)

View File

@@ -4,8 +4,10 @@ import android.Manifest
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.camera.core.CameraSelector import androidx.camera.core.CameraSelector
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageAnalysis import androidx.camera.core.ImageAnalysis
import androidx.camera.core.Preview import androidx.camera.core.Preview
import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.ProcessCameraProvider
@@ -38,6 +40,9 @@ class FaceVerificationActivity : AppCompatActivity() {
.statusBarDarkFont(true) .statusBarDarkFont(true)
.titleBar(binding.topContainer) .titleBar(binding.topContainer)
.init() .init()
binding.backIcon.setOnClickListener {
finish()
}
// 初始化人脸检测器(选项可自行调整) // 初始化人脸检测器(选项可自行调整)
val options = FaceDetectorOptions.Builder() val options = FaceDetectorOptions.Builder()
.setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST) .setPerformanceMode(FaceDetectorOptions.PERFORMANCE_MODE_FAST)
@@ -65,6 +70,7 @@ class FaceVerificationActivity : AppCompatActivity() {
} }
} }
@OptIn(ExperimentalGetImage::class)
private fun startCamera() { private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(this) val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
cameraProviderFuture.addListener({ cameraProviderFuture.addListener({
@@ -81,8 +87,8 @@ class FaceVerificationActivity : AppCompatActivity() {
.build() .build()
imageAnalysis.setAnalyzer(cameraExecutor, FaceAnalyzer(faceDetector, binding.overlay)) imageAnalysis.setAnalyzer(cameraExecutor, FaceAnalyzer(faceDetector, binding.overlay))
// 选择置摄像头 // 选择置摄像头
val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA val cameraSelector = CameraSelector.DEFAULT_FRONT_CAMERA
// 绑定到生命周期 // 绑定到生命周期
cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis) cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis)

View File

@@ -11,7 +11,7 @@
<!-- 标题栏 --> <!-- 标题栏 -->
<LinearLayout <LinearLayout
android:id="@+id/topBar" android:id="@+id/topBar"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="56dp" android:layout_height="56dp"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
@@ -30,7 +30,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:gravity="center" android:gravity="center"
android:text="@string/login_password" android:text="@string/face_management"
android:textColor="@color/black" android:textColor="@color/black"
android:textSize="16dp" android:textSize="16dp"
android:textStyle="bold" /> android:textStyle="bold" />

View File

@@ -16,14 +16,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent" />
<View
android:id="@+id/statusBarPlaceholder"
android:layout_width="match_parent"
android:layout_height="36dp"
android:background="#26000000"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/topContainer" />
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/topContainer" android:id="@+id/topContainer"
android:layout_width="0dp" android:layout_width="0dp"
@@ -32,7 +24,7 @@
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingEnd="16dp" android:paddingEnd="16dp"
android:paddingBottom="8dp" android:paddingBottom="8dp"
app:layout_constraintTop_toBottomOf="@id/statusBarPlaceholder" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"> app:layout_constraintStart_toStartOf="parent">

View File

@@ -1,40 +1,48 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<!-- 背景删除按钮 --> <!-- 背景按钮层:删除 + 解除关联 两个按钮 -->
<LinearLayout <LinearLayout
android:id="@+id/llDelete" android:id="@+id/llActions"
android:layout_width="100dp" android:layout_width="match_parent"
android:layout_height="120dp" android:layout_height="120dp"
android:background="@color/red" android:gravity="end"
android:gravity="center" android:orientation="horizontal">
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView <TextView
android:id="@+id/tvDeleteAction" android:id="@+id/tvDeleteAction"
android:layout_width="wrap_content" android:layout_width="100dp"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:background="@color/red"
android:gravity="center"
android:text="@string/delete"
android:textColor="@color/white"
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:text="@string/unlink"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16sp" android:textSize="16sp"
android:textStyle="bold" /> android:textStyle="bold" />
</LinearLayout> </LinearLayout>
<!-- 前景内容 --> <!-- 前景内容层:随 swipe 移动 -->
<LinearLayout <LinearLayout
android:id="@+id/llContent" android:id="@+id/llContent"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="120dp" android:layout_height="120dp"
android:background="@color/white" android:background="@color/white"
android:orientation="vertical" android:orientation="vertical"
android:padding="16dp" android:padding="16dp">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@@ -62,17 +70,17 @@
android:id="@+id/tvPhone" android:id="@+id/tvPhone"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="@color/gray" android:textColor="@color/gray"
android:textSize="14sp" android:textSize="14sp" />
android:layout_marginTop="8dp"/>
<TextView <TextView
android:id="@+id/tvIdCard" android:id="@+id/tvIdCard"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:textColor="@color/gray" android:textColor="@color/gray"
android:textSize="14sp" android:textSize="14sp" />
android:layout_marginTop="4dp"/>
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </FrameLayout>