feat: pdf viewer
This commit is contained in:
@@ -138,6 +138,9 @@ dependencies {
|
||||
api 'io.github.lucksiege:pictureselector:v3.11.2'
|
||||
api "com.github.bumptech.glide:glide:4.15.1"
|
||||
|
||||
// PDF预览库
|
||||
api 'com.github.barteksc:android-pdf-viewer:2.8.2'
|
||||
|
||||
|
||||
// DevApp - Android 工具类库
|
||||
api 'io.github.afkt:DevAppX:2.4.0'
|
||||
|
||||
18
module_base/src/main/java/com/lukouguoji/module_base/cache/PdfCacheModel.kt
vendored
Normal file
18
module_base/src/main/java/com/lukouguoji/module_base/cache/PdfCacheModel.kt
vendored
Normal 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/"
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,13 @@
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="true"/>
|
||||
|
||||
<!-- PDF预览Activity -->
|
||||
<activity
|
||||
android:name=".ui.page.preview.PdfPreviewActivity"
|
||||
android:configChanges="orientation|keyboardHidden"
|
||||
android:exported="false"
|
||||
android:screenOrientation="userLandscape" />
|
||||
|
||||
<!-- 屏幕适配 -->
|
||||
<meta-data
|
||||
android:name="design_width_in_dp"
|
||||
|
||||
86
module_base/src/main/res/layout/activity_pdf_preview.xml
Normal file
86
module_base/src/main/res/layout/activity_pdf_preview.xml
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<data>
|
||||
<variable
|
||||
name="viewModel"
|
||||
type="com.lukouguoji.module_base.impl.EmptyViewModel" />
|
||||
</data>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black">
|
||||
|
||||
<!-- PDF视图 -->
|
||||
<com.github.barteksc.pdfviewer.PDFView
|
||||
android:id="@+id/pdfView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<!-- Loading蒙层 (下载时显示) -->
|
||||
<RelativeLayout
|
||||
android:id="@+id/loadingLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black_tran50"
|
||||
android:visibility="gone">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:background="@color/black_tran70"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<ProgressBar
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:indeterminateTint="@color/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvLoadingTip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:text="正在加载PDF..."
|
||||
android:textColor="@color/white"
|
||||
android:textSize="14sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<!-- 页码指示器 -->
|
||||
<TextView
|
||||
android:id="@+id/tvPageIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginBottom="30dp"
|
||||
android:background="@color/black_tran50"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingTop="5dp"
|
||||
android:paddingRight="15dp"
|
||||
android:paddingBottom="5dp"
|
||||
android:text="1 / 10"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp" />
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/ivClose"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_marginTop="60dp"
|
||||
android:layout_marginRight="30dp"
|
||||
android:background="@color/black_tran30"
|
||||
android:padding="4dp"
|
||||
android:src="@drawable/img_close" />
|
||||
|
||||
</RelativeLayout>
|
||||
</layout>
|
||||
@@ -41,4 +41,9 @@
|
||||
|
||||
<color name="bottom_tool_tips_text_color">#797979</color>
|
||||
|
||||
<!-- 半透明黑色 (用于PDF预览) -->
|
||||
<color name="black_tran30">#4D000000</color> <!-- 30%透明度 -->
|
||||
<color name="black_tran50">#80000000</color> <!-- 50%透明度 -->
|
||||
<color name="black_tran70">#B3000000</color> <!-- 70%透明度 -->
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user