2 Commits
Author SHA1 Message Date
lvmengandClaude Sonnet 4.6 26d9eabc5f feat(activity): 优化图片加载为协程并新增大图左右切换功能
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:41:39 +08:00
lvmengandClaude Sonnet 4.6 301b91581b feat(activity): 新增本地图片预览功能及大图查看入口
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:22:38 +08:00
4 changed files with 195 additions and 25 deletions
@@ -210,6 +210,11 @@ class FoodCollectionActivity : BaseActivity() {
loadEmptyView()
// 点击"图片"按钮,跳转至本地图片预览页
binding.tvImagePreview.setOnClickListener {
startActivity<LocalImagePreviewActivity> { }
}
binding.tvTitle.setOnClickListener {
// startActivity<LocalImagePreviewActivity> { }
@@ -1,18 +1,27 @@
package com.sw.inbound.activity
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import android.view.WindowManager
import androidx.core.graphics.drawable.toDrawable
import androidx.lifecycle.lifecycleScope
import com.sw.inbound.base.BaseActivity
import androidx.recyclerview.widget.GridLayoutManager
import coil.load
import com.chad.library.adapter4.BaseQuickAdapter
import com.chad.library.adapter4.viewholder.QuickViewHolder
import com.sw.inbound.databinding.ActivityLocalImagePreviewBinding
import com.sw.inbound.databinding.DialogImageFullPreviewBinding
import com.sw.inbound.databinding.ListItemImagePreviewBinding
import com.sw.inbound.dialog.Loading
import com.sw.inbound.utils.ext.copyText
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class LocalImagePreviewActivity : BaseActivity() {
@@ -32,39 +41,96 @@ class LocalImagePreviewActivity : BaseActivity() {
loadData()
}
/** 从缓存 crop 目录加载所有图片文件 */
fun loadData() {
Thread {
runOnUiThread {
Loading.show(this)
}
val cropFile = File(this.cacheDir, "crop")
cropFile.listFiles()?.forEach {
if (it.isDirectory) {
return@forEach
}
val start = "IMG_CROP_".length
val end = it.name.indexOf(".jpg")
val time = it.name.substring(start, end).toLong()
if (time >= 1764086400L) {
imageList.add(ImageBean(file = it, name = "$time"))
lifecycleScope.launch {
// 主线程显示加载框
Loading.show(this@LocalImagePreviewActivity)
// 切换到 IO 线程执行文件读取
withContext(Dispatchers.IO) {
val cropFile = File(this@LocalImagePreviewActivity.cacheDir, "crop")
cropFile.listFiles()?.forEach {
// 跳过子目录,只处理文件
if (it.isDirectory) return@forEach
// 跳过非 jpg 图片
if (!it.name.endsWith(".jpg", ignoreCase = true)) return@forEach
val nameWithoutExt = it.nameWithoutExtension
// 兼容 IMG_CROP_{时间戳} 和其他命名格式
val displayName = if (nameWithoutExt.startsWith("IMG_CROP_")) {
nameWithoutExt.removePrefix("IMG_CROP_")
} else {
nameWithoutExt
}
imageList.add(ImageBean(file = it, name = displayName))
}
// 按文件名升序排列
imageList.sortBy { it.name }
}
imageList.sortBy { it.name.toLong() }
runOnUiThread {
imageAdapter.notifyDataSetChanged()
Loading.dismiss()
}
}.start()
// 回到主线程刷新列表
imageAdapter.notifyDataSetChanged()
// 延迟 500ms 后关闭加载框
delay(500)
Loading.dismiss()
}
}
private var isCycle = true
/** 弹出全屏大图预览 Dialog,支持左右切换 */
private fun showFullImagePreview(startPosition: Int) {
var currentIndex = startPosition
val dialogBinding = DialogImageFullPreviewBinding.inflate(layoutInflater)
val dialog = Dialog(this, android.R.style.Theme_Black_NoTitleBar_Fullscreen)
dialog.setContentView(dialogBinding.root)
dialog.setCancelable(true)
dialog.setCanceledOnTouchOutside(true)
dialog.window?.apply {
setBackgroundDrawable(Color.TRANSPARENT.toDrawable())
setLayout(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT
)
}
/** 根据 currentIndex 刷新图片、文件名、页码及按钮状态 */
fun updateContent() {
val item = imageList[currentIndex]
dialogBinding.ivFullImage.load(item.file)
dialogBinding.tvFileName.text = item.file.name
dialogBinding.tvPageIndicator.text = "${currentIndex + 1} / ${imageList.size}"
// 到达边界时隐藏对应方向的按钮
dialogBinding.btnPrev.alpha = if (currentIndex > 0) 1f else 0.2f
dialogBinding.btnNext.alpha = if (currentIndex < imageList.size - 1) 1f else 0.2f
}
// 点击图片本身关闭弹窗
dialogBinding.ivFullImage.setOnClickListener { dialog.dismiss() }
// 关闭按钮
dialogBinding.btnClose.setOnClickListener { dialog.dismiss() }
// 上一张
dialogBinding.btnPrev.setOnClickListener {
if (currentIndex > 0) {
currentIndex--
updateContent()
}
}
// 下一张
dialogBinding.btnNext.setOnClickListener {
if (currentIndex < imageList.size - 1) {
currentIndex++
updateContent()
}
}
updateContent()
dialog.show()
}
private val imageList = mutableListOf<ImageBean>()
private val imageAdapter: ImageAdapter by lazy {
ImageAdapter(imageList).apply {
setOnItemClickListener { _, _, position ->
imageList[position].name.copyText(this@LocalImagePreviewActivity)
// 点击缩略图,弹出大图预览,传入当前索引以支持左右切换
showFullImagePreview(position)
}
}
}
@@ -94,4 +160,4 @@ class LocalImagePreviewActivity : BaseActivity() {
}
}
}
@@ -20,6 +20,19 @@
android:textColor="#141428"
android:textSize="36sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvImagePreview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="40dp"
android:gravity="center_vertical"
android:paddingHorizontal="20dp"
android:text="图片"
android:textColor="#141428"
android:textSize="32sp"
android:textStyle="bold" />
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rootLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#CC000000">
<ImageView
android:id="@+id/ivFullImage"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="120dp"
android:scaleType="fitCenter" />
<!-- 左侧翻页按钮 -->
<ImageView
android:id="@+id/btnPrev"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center_vertical|start"
android:layout_marginBottom="120dp"
android:layout_marginStart="20dp"
android:padding="16dp"
android:src="@mipmap/ic_back_white2"
android:background="#55000000" />
<!-- 右侧翻页按钮 -->
<ImageView
android:id="@+id/btnNext"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center_vertical|end"
android:layout_marginBottom="120dp"
android:layout_marginEnd="20dp"
android:padding="16dp"
android:src="@mipmap/ic_back_white2"
android:rotation="180"
android:background="#55000000" />
<!-- 底部工具栏:文件名 + 页码指示器 + 关闭按钮 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_gravity="bottom"
android:background="#99000000"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingHorizontal="30dp">
<TextView
android:id="@+id/tvFileName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:ellipsize="middle"
android:maxLines="1"
android:textColor="#FFFFFFFF"
android:textSize="26sp" />
<TextView
android:id="@+id/tvPageIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="30dp"
android:textColor="#CCFFFFFF"
android:textSize="26sp" />
<FrameLayout
android:id="@+id/btnClose"
android:layout_width="200dp"
android:layout_height="80dp"
android:background="@drawable/bg_blue_ripple">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="关闭"
android:textColor="@color/white"
android:textSize="30sp"
android:textStyle="bold" />
</FrameLayout>
</LinearLayout>
</FrameLayout>