前言
Android Jetpack Compose没有相关解析PDF的库,所以我们使用WebView进行加载。
使用pdf.js来解析PDF。
访问跨域
pdf.js 加载 PDF的时候就算,PDF的地址是允许跨域的,但是只要域不一样,依旧会报错,直接在viewer.js,找到下面的代码,把抛出异常的地方注释掉就行。
1 2 3
| if (fileOrigin !== viewerOrigin) { }
|
添加依赖
这个主要用于使用模板生成HTML
1 2 3
| dependencies { implementation 'com.x5dev:chunk-templates:3.6.2' }
|
添加模板
PDF解析库
assets 中添加相关库文件
内链防跳转
在viewer.html中添加
1 2 3 4 5 6 7 8 9
| const viewerContainer = document.getElementById("viewerContainer"); viewerContainer.addEventListener("click", function (e) { if (e.target.tagName === "A") { e.preventDefault(); e.stopPropagation(); console.log("拦截 PDF 内链跳转:", e.target.href); } });
|
模板
模板文件要放在src/main/assets/themes文件夹下
注意文件必须以.chtml作为后缀
assets/themes 中添加 zpdf.chtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| <!DOCTYPE html> <html lang="cn"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>PDF预览</title>
<style> html, body { margin: 0; padding: 0; overflow: hidden; }
#pdfIframe { width: 100%; height: 100vh; }
#hideDiv{ display: none; } </style> </head> <body> <div id="hideDiv">{$pdf_url}</div> <iframe frameborder="0" id="pdfIframe"></iframe>
<script> let hideDiv = document.getElementById("hideDiv"); let pdfIframe = document.getElementById("pdfIframe");
let pdfUrl = hideDiv.innerHTML; pdfIframe.src = "file:///android_asset/pdfjs/viewer.html?file=" + encodeURIComponent(pdfUrl); </script> </body> </html>
|
自定义组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| import android.annotation.SuppressLint import android.content.Context import android.graphics.Color import android.util.AttributeSet import android.webkit.WebSettings import android.webkit.WebView import com.x5.template.Chunk import com.x5.template.Theme import com.x5.template.providers.AndroidTemplates
@SuppressLint("SetJavaScriptEnabled") public class ZPdfView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : WebView(context, attrs, defStyleAttr) { val TAG = "ZPdfView" private val TAG_PDF_URL: String = "pdf_url" private val TAG_TP_NAME: String = "zpdf"
private var mPdfUrl: String = ""
init { settings.cacheMode = WebSettings.LOAD_NO_CACHE settings.displayZoomControls = false settings.builtInZoomControls = false settings.setSupportZoom(false) settings.useWideViewPort = true settings.loadWithOverviewMode = true
settings.javaScriptEnabled = true settings.allowFileAccess = true settings.domStorageEnabled = true settings.loadsImagesAutomatically = true settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
setBackgroundColor(Color.TRANSPARENT) }
fun loadPdf(url: String) { mPdfUrl = url loadHTML() }
private fun getChunk(): Chunk { return Theme(AndroidTemplates(context)).makeChunk(TAG_TP_NAME) }
private fun loadHTML() { val mChunk: Chunk = getChunk() mChunk.set(TAG_PDF_URL, mPdfUrl) this.loadDataWithBaseURL(null, mChunk.toString(), "text/html", "utf-8", "about:blank") } }
|
注意
下面的配置确保图片的正常加载
1 2 3 4 5
| settings.javaScriptEnabled = true settings.allowFileAccess = true settings.domStorageEnabled = true settings.loadsImagesAutomatically = true settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
Compose封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| import android.content.Context import android.view.ViewGroup import android.webkit.WebViewClient import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier import androidx.compose.ui.viewinterop.AndroidView import cn.psvmc.zpdfview.ZPdfView
private fun getPdfView(context: Context): ZPdfView { val pdfView = ZPdfView(context).apply { webViewClient = WebViewClient() } pdfView.layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) return pdfView }
@Composable fun ZPdfViewComp( pdfUrl: String, modifier: Modifier = Modifier ) { var pdfView: ZPdfView? = null AndroidView( modifier = modifier, factory = { context -> pdfView = getPdfView(context) pdfView }, update = { texView -> texView.loadPdf(pdfUrl) } )
DisposableEffect(Unit) { onDispose { pdfView?.stopLoading() pdfView?.webViewClient = WebViewClient() pdfView?.destroy() } } }
|
注意
销毁时释放Webview资源,避免内存泄漏导致的加载异常。
使用
1 2 3 4
| ZPdfViewComp( pdfUrl = CommonData.fileShowUrl+ file.pdfUrl, modifier = Modifier.fillMaxSize() )
|