feat: pdf viewer

This commit is contained in:
2026-01-10 17:28:06 +08:00
parent 085af11706
commit f19b0d7c68
8 changed files with 347 additions and 3 deletions

View File

@@ -0,0 +1,18 @@
package com.lukouguoji.module_base.cache
import dev.DevUtils
/**
* PDF文件缓存管理
* 负责PDF文件的下载和本地缓存
*/
object PdfCacheModel : FileCacheModel() {
/**
* PDF文件缓存目录
* 路径: /data/data/com.lukouguoji.aerologic/files/cacheFiles/pdf/
*/
override fun folderPath(): String {
return DevUtils.getContext().filesDir.toString() + "/cacheFiles/pdf/"
}
}

View File

@@ -0,0 +1,223 @@
package com.lukouguoji.module_base.ui.page.preview
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Bundle
import android.view.View
import androidx.lifecycle.lifecycleScope
import com.github.barteksc.pdfviewer.listener.OnLoadCompleteListener
import com.github.barteksc.pdfviewer.listener.OnPageChangeListener
import com.github.barteksc.pdfviewer.listener.OnErrorListener
import com.lukouguoji.module_base.R
import com.lukouguoji.module_base.base.BaseBindingActivity
import com.lukouguoji.module_base.cache.PdfCacheModel
import com.lukouguoji.module_base.common.Constant
import com.lukouguoji.module_base.databinding.ActivityPdfPreviewBinding
import com.lukouguoji.module_base.impl.EmptyViewModel
import com.lukouguoji.module_base.ktx.showToast
import com.lukouguoji.module_base.util.MediaUtil
import dev.utils.app.BarUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
/**
* PDF预览界面
* 支持在线PDF下载缓存和本地PDF文件预览
*/
class PdfPreviewActivity : BaseBindingActivity<ActivityPdfPreviewBinding, EmptyViewModel>() {
// PDF文件路径 (可能是在线URL或本地路径)
private var pdfPath: String = ""
// 当前页码 (从0开始)
private var currentPage = 0
// 总页数
private var totalPages = 0
override fun layoutId() = R.layout.activity_pdf_preview
override fun viewModelClass() = EmptyViewModel::class.java
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 强制横屏显示 (与项目其他页面保持一致)
resources?.let {
if (it.displayMetrics.widthPixels < it.displayMetrics.heightPixels) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
} else {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
}
}
}
@SuppressLint("SourceLockedOrientationActivity")
override fun initOnCreate(savedInstanceState: Bundle?) {
// 设置沉浸式状态栏
BarUtils.transparentStatusBar(this)
// 获取传递的PDF路径
pdfPath = intent.getStringExtra(Constant.Key.DATA) ?: ""
if (pdfPath.isEmpty()) {
showToast("PDF路径为空")
finish()
return
}
// 设置关闭按钮
binding.ivClose.setOnClickListener {
finish()
}
// 加载PDF文件
loadPdfFile()
}
/**
* 加载PDF文件 (支持在线和本地)
*/
private fun loadPdfFile() {
// val fullUrl = MediaUtil.fillUrl(pdfPath)
val fullUrl = pdfPath
// 判断是否为在线资源
if (isOnlineResource(fullUrl)) {
// 在线PDF: 先下载再加载
downloadAndLoadPdf(fullUrl)
} else {
// 本地PDF: 直接加载
loadLocalPdf(File(pdfPath))
}
}
/**
* 判断是否为在线资源
*/
private fun isOnlineResource(url: String): Boolean {
return url.startsWith("http://") || url.startsWith("https://")
}
/**
* 下载在线PDF并加载
*/
private fun downloadAndLoadPdf(url: String) {
// 显示Loading
showLoading()
lifecycleScope.launch(Dispatchers.IO) {
try {
// 使用PdfCacheModel下载PDF
PdfCacheModel.saveFile(
url = url,
onProgress = { progress ->
// 更新下载进度
lifecycleScope.launch(Dispatchers.Main) {
binding.tvLoadingTip.text = "正在下载PDF... $progress%"
}
},
onSuccess = { file ->
// 下载成功切换到主线程加载PDF
lifecycleScope.launch(Dispatchers.Main) {
hideLoading()
loadLocalPdf(file)
}
},
onError = { errorMsg ->
// 下载失败
lifecycleScope.launch(Dispatchers.Main) {
hideLoading()
showToast("PDF下载失败: $errorMsg")
finish()
}
}
)
} catch (e: Exception) {
withContext(Dispatchers.Main) {
hideLoading()
showToast("PDF下载异常: ${e.message}")
finish()
}
}
}
}
/**
* 加载本地PDF文件
*/
private fun loadLocalPdf(file: File) {
if (!file.exists()) {
showToast("PDF文件不存在")
finish()
return
}
try {
binding.pdfView.fromFile(file)
.defaultPage(0) // 默认显示第一页
.enableSwipe(true) // 启用滑动翻页
.swipeHorizontal(false) // 垂直滑动
.enableDoubletap(true) // 启用双击缩放
.enableAnnotationRendering(false) // 禁用注释渲染(提升性能)
.password(null) // PDF密码(如需要)
.scrollHandle(null) // 禁用默认滚动条
.onLoad(OnLoadCompleteListener { nbPages ->
// PDF加载完成
totalPages = nbPages
updatePageIndicator()
})
.onPageChange(OnPageChangeListener { page, pageCount ->
// 页面切换
currentPage = page
updatePageIndicator()
})
.onError(OnErrorListener { t ->
// 加载错误
showToast("PDF加载失败: ${t.message}")
finish()
})
.load()
} catch (e: Exception) {
showToast("PDF渲染异常: ${e.message}")
finish()
}
}
/**
* 更新页码指示器
*/
private fun updatePageIndicator() {
binding.tvPageIndicator.text = "${currentPage + 1} / $totalPages"
}
/**
* 显示Loading
*/
private fun showLoading() {
binding.loadingLayout.visibility = View.VISIBLE
}
/**
* 隐藏Loading
*/
private fun hideLoading() {
binding.loadingLayout.visibility = View.GONE
}
companion object {
/**
* 启动PDF预览
* @param context 上下文
* @param pdfPath PDF路径 (支持在线URL或本地路径)
*/
@JvmStatic
fun start(context: Context, pdfPath: String) {
val starter = Intent(context, PdfPreviewActivity::class.java)
.putExtra(Constant.Key.DATA, pdfPath)
context.startActivity(starter)
}
}
}