添加pdf转换图片功能

优化一些其余项
This commit is contained in:
ocean 2025-10-17 17:38:26 +08:00
parent 2c88b4271d
commit d9dfa75b9b
14 changed files with 554 additions and 32 deletions

View File

@ -7,12 +7,13 @@
<uses-feature <uses-feature
android:name="android.hardware.camera" android:name="android.hardware.camera"
android:required="false" /> android:required="false" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE" />
<!-- Android 13+ 媒体权限 --> <!-- Android 13+ 媒体权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
@ -104,6 +105,13 @@
android:label="@string/app_name" android:label="@string/app_name"
android:screenOrientation="portrait" /> android:screenOrientation="portrait" />
<activity
android:name=".ui.act.PdfToImageActivity"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="portrait" />
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider" android:authorities="${applicationId}.fileprovider"

View File

@ -6,6 +6,7 @@ enum class PdfPickerSource {
SPLIT, // PDF拆分 SPLIT, // PDF拆分
LOCK, // PDF加密 LOCK, // PDF加密
UNLOCK, // PDF解密 UNLOCK, // PDF解密
TO_IMAGES, // PDF转图片 IMAGES_TO_PDF,// 图片转PDF
PDF_TO_IMAGES,// PDF转图片
TO_LONG_IMAGE // PDF转长图 TO_LONG_IMAGE // PDF转长图
} }

View File

@ -3,6 +3,7 @@ package com.all.pdfreader.pro.app.ui.act
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
@ -55,6 +56,7 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
setupDoubleBackExit()
initObserve() initObserve()
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true) ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(R.color.white).init() .navigationBarColor(R.color.white).init()
@ -475,4 +477,25 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
binding.multiSelectMergeBtn.isClickable = false binding.multiSelectMergeBtn.isClickable = false
} }
} }
private var lastBackPressedTime: Long = 0
private val EXIT_INTERVAL = 2000L // 2 秒内双击退出
private fun setupDoubleBackExit() {
onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastBackPressedTime < EXIT_INTERVAL) {
// 双击退出
isEnabled = false // 解除拦截
onBackPressedDispatcher.onBackPressed() // 调用系统默认返回逻辑
} else {
lastBackPressedTime = currentTime
showToast(getString(R.string.press_again_to_exit))
}
}
})
}
} }

View File

@ -105,9 +105,14 @@ class PdfPickerActivity : BaseActivity() {
}).show(supportFragmentManager, "PdfRemovePasswordDialog") }).show(supportFragmentManager, "PdfRemovePasswordDialog")
} }
PdfPickerSource.TO_IMAGES -> {} PdfPickerSource.IMAGES_TO_PDF -> {}
PdfPickerSource.TO_LONG_IMAGE -> {} PdfPickerSource.TO_LONG_IMAGE -> {}
PdfPickerSource.NONE -> {} PdfPickerSource.NONE -> {}
PdfPickerSource.PDF_TO_IMAGES -> {
val intent = PdfToImageActivity.createIntent(this, pdf.filePath)
startActivity(intent)
finish()
}
} }
}, },
onSelectModelItemClick = { onSelectModelItemClick = {

View File

@ -13,6 +13,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import com.all.pdfreader.pro.app.PRApp import com.all.pdfreader.pro.app.PRApp
import com.all.pdfreader.pro.app.R import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivityPdfSplitResultBinding import com.all.pdfreader.pro.app.databinding.ActivityPdfSplitResultBinding
import com.all.pdfreader.pro.app.model.PdfPageItem
import com.all.pdfreader.pro.app.model.PdfPickerSource import com.all.pdfreader.pro.app.model.PdfPickerSource
import com.all.pdfreader.pro.app.model.PdfSelectedPagesItem import com.all.pdfreader.pro.app.model.PdfSelectedPagesItem
import com.all.pdfreader.pro.app.model.PdfSplitResultItem import com.all.pdfreader.pro.app.model.PdfSplitResultItem
@ -43,6 +44,8 @@ class PdfResultActivity : BaseActivity() {
private const val EXTRA_LOCK_UNLOCK_PASSWORD = "extra_lock_unlock_password" private const val EXTRA_LOCK_UNLOCK_PASSWORD = "extra_lock_unlock_password"
private const val EXTRA_FILE_PATH = "extra_file_path" private const val EXTRA_FILE_PATH = "extra_file_path"
private const val EXTRA_SPLIT_PASSWORD = "extra_split_password" private const val EXTRA_SPLIT_PASSWORD = "extra_split_password"
private const val EXTRA_PDF_TO_IMAGE_LIST = "extra_pdf_to_image_list"
private const val EXTRA_PDF_TO_IMAGE_PASSWORD = "extra_pdf_to_image_password"
fun createIntentSplitPdfActivityToResult( fun createIntentSplitPdfActivityToResult(
context: Context, context: Context,
@ -84,6 +87,21 @@ class PdfResultActivity : BaseActivity() {
putExtra(EXTRA_SOURCE, source) putExtra(EXTRA_SOURCE, source)
} }
} }
fun createIntentPdfToImageActivityToResult(
context: Context,
filepath: String,
list: ArrayList<PdfPageItem>,
source: PdfPickerSource,
password: String? = null
): Intent {
return Intent(context, PdfResultActivity::class.java).apply {
putExtra(EXTRA_FILE_PATH, filepath)
putParcelableArrayListExtra(EXTRA_PDF_TO_IMAGE_LIST, list)
putExtra(EXTRA_SOURCE, source)
putExtra(EXTRA_PDF_TO_IMAGE_PASSWORD, password)
}
}
} }
private lateinit var binding: ActivityPdfSplitResultBinding private lateinit var binding: ActivityPdfSplitResultBinding
@ -248,7 +266,7 @@ class PdfResultActivity : BaseActivity() {
) )
resultList.add(result) resultList.add(result)
} }
} else if (source == PdfPickerSource.TO_IMAGES) { } else if (source == PdfPickerSource.IMAGES_TO_PDF) {
val files = requireStringArrayList(EXTRA_FILE_LIST) val files = requireStringArrayList(EXTRA_FILE_LIST)
if (files.isEmpty()) { if (files.isEmpty()) {
showToast(getString(R.string.pdf_loading_failed)) showToast(getString(R.string.pdf_loading_failed))
@ -267,9 +285,7 @@ class PdfResultActivity : BaseActivity() {
outputDir = outputDir, outputDir = outputDir,
outputFileName = outputFileName, outputFileName = outputFileName,
onProgress = { current, total -> onProgress = { current, total ->
logDebug("current->$current total->$total")
val progressPercent = current * 100 / total val progressPercent = current * 100 / total
logDebug("progressPercent->$progressPercent")
runOnUiThread { runOnUiThread {
binding.progressBar.progress = progressPercent binding.progressBar.progress = progressPercent
binding.progressTv.text = "$progressPercent" binding.progressTv.text = "$progressPercent"
@ -281,6 +297,41 @@ class PdfResultActivity : BaseActivity() {
resultList.add(result) resultList.add(result)
} }
} }
} else if (source == PdfPickerSource.PDF_TO_IMAGES) {
val filepath = intent.getStringExtra(EXTRA_FILE_PATH) ?: ""
if (filepath.isEmpty()) {
showToast(getString(R.string.pdf_loading_failed))
finish()
return@launch
}
binding.congratulationsDesc.text = getString(R.string.converted_successfully)
val selectedPages: ArrayList<PdfPageItem> =
requireParcelableArrayList(EXTRA_PDF_TO_IMAGE_LIST)
val pdfToImgPassword = intent.getStringExtra(EXTRA_PDF_TO_IMAGE_PASSWORD) ?: ""
val outputDir = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS),
"PDFReaderPro/pdf2Img"
).apply { if (!exists()) mkdirs() }
PdfUtils.exportSelectedPagesToImages(
context = this@PdfResultActivity,
inputFile = File(filepath),
selectedPages = selectedPages,
outputDir = outputDir,
password = pdfToImgPassword,
onProgress = { current, total ->
val progressPercent = current * 100 / total
runOnUiThread {
binding.progressBar.progress = progressPercent
binding.progressTv.text = "$progressPercent"
}
}).let { resultFiles ->
if (resultFiles.isNotEmpty()) {
resultFiles.forEach {
val result = PdfSplitResultItem(it.absolutePath, it.absolutePath, false)
resultList.add(result)
}
}
}
} }
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
binding.processingLayout.visibility = View.GONE binding.processingLayout.visibility = View.GONE
@ -307,6 +358,16 @@ class PdfResultActivity : BaseActivity() {
} }
} }
binding.okBtn.setOnClickListener { binding.okBtn.setOnClickListener {
if (source == PdfPickerSource.PDF_TO_IMAGES) {
val selectedItem = adapter.getSelectedItem()
selectedItem?.let {
val string = AppUtils.openImageFile(this, File(selectedItem.filePath))
if (string.isNotEmpty()) {
showToast(string)
}
}
finish()
} else {
val selectedItem = adapter.getSelectedItem() val selectedItem = adapter.getSelectedItem()
selectedItem?.let { selectedItem?.let {
val intent = PdfViewActivity.createIntent(this, selectedItem.filePath) val intent = PdfViewActivity.createIntent(this, selectedItem.filePath)
@ -315,6 +376,7 @@ class PdfResultActivity : BaseActivity() {
finish() finish()
} }
} }
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()

View File

@ -0,0 +1,175 @@
package com.all.pdfreader.pro.app.ui.act
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager
import com.all.pdfreader.pro.app.R
import com.all.pdfreader.pro.app.databinding.ActivityPdfToImgBinding
import com.all.pdfreader.pro.app.model.PdfPageItem
import com.all.pdfreader.pro.app.model.PdfPickerSource
import com.all.pdfreader.pro.app.model.PdfSelectedPagesItem
import com.all.pdfreader.pro.app.ui.adapter.SplitPdfAdapter
import com.all.pdfreader.pro.app.ui.dialog.PdfPasswordProtectionDialogFragment
import com.all.pdfreader.pro.app.util.AppUtils.setOnSingleClickListener
import com.all.pdfreader.pro.app.util.FileUtils.isPdfEncrypted
import com.all.pdfreader.pro.app.util.FileUtils.toUnderscoreDateTime
import com.all.pdfreader.pro.app.util.PdfUtils
import com.gyf.immersionbar.ImmersionBar
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
class PdfToImageActivity : BaseActivity() {
override val TAG: String = "PdfToImageActivity"
var currentPassword: String? = null//只会选择一个文件。直接进行密码传递
companion object {
private const val EXTRA_PDF_PATH = "extra_pdf_path"
fun createIntent(context: Context, filePath: String): Intent {
return Intent(context, PdfToImageActivity::class.java).apply {
putExtra(EXTRA_PDF_PATH, filePath)
}
}
}
private lateinit var binding: ActivityPdfToImgBinding
private lateinit var adapter: SplitPdfAdapter
private var pdfPageList: MutableList<PdfPageItem> = mutableListOf()
private lateinit var filePath: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPdfToImgBinding.inflate(layoutInflater)
setContentView(binding.root)
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(R.color.bg_color).init()
filePath = intent.getStringExtra(EXTRA_PDF_PATH)
?: throw IllegalArgumentException("PDF file hash is required")
if (filePath.isEmpty()) {
showToast(getString(R.string.file_not))
finish()
}
initView()
setupClick()
initSplitData(File(filePath))
}
private fun initView() {
binding.continueNowBtn.isEnabled = false
binding.title.text = getString(R.string.selected_page, 0)
binding.loadingRoot.root.visibility = View.VISIBLE//初始显示loading
binding.splitRv.layoutManager = GridLayoutManager(this, 2)
adapter = SplitPdfAdapter(pdfPageList) { item, pos ->
item.isSelected = !item.isSelected
adapter.setItemSelected(pos, item.isSelected)
binding.title.text = getString(R.string.selected_page, adapter.getSelPages())
// 只有存在选中的item则为true
val anySelected = pdfPageList.any { it.isSelected }
binding.continueNowBtn.isEnabled = anySelected
updateContinueNowBtnState(anySelected)
}
binding.splitRv.adapter = adapter
}
private fun setupClick() {
binding.backBtn.setOnSingleClickListener { onBackPressedDispatcher.onBackPressed() }
binding.selectAllBtn.setOnSingleClickListener {
val selectAll = pdfPageList.any { !it.isSelected }//如果列表里有一页没选中 → 返回 true
adapter.setAllSelected(selectAll)
binding.title.text = getString(R.string.selected_page, adapter.getSelPages())
binding.continueNowBtn.isEnabled = selectAll
updateSelectAllState(selectAll)
updateContinueNowBtnState(selectAll)
}
binding.continueNowBtn.setOnSingleClickListener {
val selectedPages = pdfPageList.filter { it.isSelected }.map { it.copy() }
val intent = PdfResultActivity.createIntentPdfToImageActivityToResult(
context = this,
filepath = filePath,
list = ArrayList(selectedPages),
source = PdfPickerSource.PDF_TO_IMAGES,
password = currentPassword
)
startActivity(intent)
finish()
}
}
private fun initSplitData(file: File) {
lifecycleScope.launch {
// 是否存在密码
val isEncrypted = withContext(Dispatchers.IO) {
isPdfEncrypted(file)
}
if (isEncrypted) {
PdfPasswordProtectionDialogFragment(file, onOkClick = { password ->
initSplitDataWithPassword(file, password)
}, onCancelClick = {
finish()
}).show(supportFragmentManager, TAG)
} else {
initSplitDataWithPassword(file)
}
}
}
private fun initSplitDataWithPassword(file: File, password: String? = null) {
if (!password.isNullOrEmpty()) {
currentPassword = password
}
lifecycleScope.launch {
pdfPageList.clear()
// 先切换到 IO 线程执行删除,等待完成
withContext(Dispatchers.IO) {
PdfUtils.clearPdfThumbsCache(this@PdfToImageActivity)
}
// 删除完成后,开始收集数据
var firstPageLoaded = false
PdfUtils.splitPdfToPageItemsFlow(
context = this@PdfToImageActivity,
inputFile = file,
password = password
).collect { pageItem ->
logDebug("splitPdfToPageItemsFlow pageItem->$pageItem")
if (pdfPageList.size <= pageItem.pageIndex) {
pdfPageList.add(pageItem)
adapter.notifyItemInserted(pdfPageList.size - 1)
} else {
pdfPageList[pageItem.pageIndex] = pageItem
adapter.updateItem(pageItem.pageIndex)
}
if (!firstPageLoaded) {
binding.loadingRoot.root.visibility = View.GONE
firstPageLoaded = true
}
}
}
}
private fun updateSelectAllState(b: Boolean) {
binding.selectAll.setBackgroundResource(
if (b) R.drawable.dr_circular_sel_on_bg
else R.drawable.dr_circular_sel_off_bg
)
}
private fun updateContinueNowBtnState(b: Boolean) {
binding.continueNowBtn.setBackgroundResource(
if (b) R.drawable.dr_click_btn_bg
else R.drawable.dr_btn_not_clickable_bg
)
}
override fun onDestroy() {
super.onDestroy()
}
}

View File

@ -183,8 +183,9 @@ class SearchActivity : BaseActivity() {
}).show(supportFragmentManager, "PdfRemovePasswordDialog") }).show(supportFragmentManager, "PdfRemovePasswordDialog")
} }
PdfPickerSource.TO_IMAGES -> {} PdfPickerSource.IMAGES_TO_PDF -> {}
PdfPickerSource.TO_LONG_IMAGE -> {} PdfPickerSource.TO_LONG_IMAGE -> {}
PdfPickerSource.PDF_TO_IMAGES -> {}
} }
}, onMoreClick = { pdf -> }, onMoreClick = { pdf ->
ListMoreDialogFragment(pdf.filePath).show(supportFragmentManager, FRAG_TAG) ListMoreDialogFragment(pdf.filePath).show(supportFragmentManager, FRAG_TAG)

View File

@ -68,6 +68,10 @@ class ToolsFrag : BaseFrag() {
binding.imgToPdfBtn.setOnClickListener { binding.imgToPdfBtn.setOnClickListener {
openImagePicker() openImagePicker()
} }
binding.pdfToImgBtn.setOnClickListener {
val intent = PdfPickerActivity.createIntent(requireActivity(), PdfPickerSource.PDF_TO_IMAGES)
startActivity(intent)
}
} }
private fun openImagePicker() { private fun openImagePicker() {
@ -154,7 +158,7 @@ class ToolsFrag : BaseFrag() {
val intent = PdfResultActivity.createIntentInputFile( val intent = PdfResultActivity.createIntentInputFile(
requireActivity(), requireActivity(),
ArrayList(paths), ArrayList(paths),
PdfPickerSource.TO_IMAGES PdfPickerSource.IMAGES_TO_PDF
) )
startActivity(intent) startActivity(intent)
} }

View File

@ -1,11 +1,13 @@
package com.all.pdfreader.pro.app.util package com.all.pdfreader.pro.app.util
import android.app.Activity import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Color import android.graphics.Color
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.print.PrintAttributes import android.print.PrintAttributes
import android.print.PrintManager import android.print.PrintManager
@ -271,4 +273,41 @@ object AppUtils {
return spannable return spannable
} }
/**
* 打开图片文件返回提示信息
*
* @param context Context
* @param file 要打开的图片文件
* @return 提示信息
*/
fun openImageFile(context: Context, file: File): String {
return try {
val uri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(
context,
"${context.packageName}.fileprovider",
file
)
} else {
Uri.fromFile(file)
}
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(uri, "image/*")
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
context.startActivity(Intent.createChooser(intent, context.getString(R.string.open)))
"" // 成功返回空字符串
} catch (e: ActivityNotFoundException) {
context.getString(R.string.no_app_to_open_image)
} catch (e: Exception) {
e.printStackTrace()
context.getString(R.string.failed_to_open_image)
}
}
} }

View File

@ -15,7 +15,6 @@ import com.tom_roush.pdfbox.pdmodel.PDPage
import com.tom_roush.pdfbox.pdmodel.PDPageContentStream import com.tom_roush.pdfbox.pdmodel.PDPageContentStream
import com.tom_roush.pdfbox.pdmodel.common.PDRectangle import com.tom_roush.pdfbox.pdmodel.common.PDRectangle
import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory import com.tom_roush.pdfbox.pdmodel.graphics.image.JPEGFactory
import com.tom_roush.pdfbox.pdmodel.graphics.image.LosslessFactory
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -313,11 +312,7 @@ object PdfUtils {
val pdImage = JPEGFactory.createFromImage(document, bitmap) val pdImage = JPEGFactory.createFromImage(document, bitmap)
PDPageContentStream(document, page).use { contentStream -> PDPageContentStream(document, page).use { contentStream ->
contentStream.drawImage( contentStream.drawImage(
pdImage, pdImage, offsetX, offsetY, targetWidth, targetHeight
offsetX,
offsetY,
targetWidth,
targetHeight
) )
} }
@ -344,4 +339,84 @@ object PdfUtils {
document?.close() document?.close()
} }
} }
/**
* 导出选中的 PDF 页到图片文件支持加密进度回调防重名格式可选
*
* @param context 上下文
* @param inputFile PDF 文件
* @param selectedPages 选中的页对象列表包含 pageIndex
* @param outputDir 输出目录
* @param password PDF 密码可为空
* @param format 输出格式"JPG" "PNG"
* @param quality 图片质量仅对 JPG 有效0~100
* @param onProgress 当前进度回调 (current , total )
* @return 导出的图片文件列表
*/
suspend fun exportSelectedPagesToImages(
context: Context,
inputFile: File,
selectedPages: List<PdfPageItem>,
outputDir: File,
password: String? = null,
format: String = "JPG",
quality: Int = 100,
onProgress: ((current: Int, total: Int) -> Unit)? = null
): List<File> = withContext(Dispatchers.IO) {
if (selectedPages.isEmpty()) return@withContext emptyList()
if (!outputDir.exists()) outputDir.mkdirs()
val pdfiumCore = PdfiumCore(context)
val fd = ParcelFileDescriptor.open(inputFile, ParcelFileDescriptor.MODE_READ_ONLY)
val document = if (password.isNullOrEmpty()) {
pdfiumCore.newDocument(fd)
} else {
pdfiumCore.newDocument(fd, password)
}
val sortedPages = selectedPages.sortedBy { it.pageIndex }
val total = sortedPages.size
val resultList = mutableListOf<File>()
val baseName = inputFile.nameWithoutExtension
val compressFormat = if (format.equals("PNG", ignoreCase = true)) {
Bitmap.CompressFormat.PNG
} else {
Bitmap.CompressFormat.JPEG
}
val extension = if (format.equals("PNG", ignoreCase = true)) "png" else "jpg"
sortedPages.forEachIndexed { index, pageItem ->
try {
pdfiumCore.openPage(document, pageItem.pageIndex)
val width = pdfiumCore.getPageWidthPoint(document, pageItem.pageIndex)
val height = pdfiumCore.getPageHeightPoint(document, pageItem.pageIndex)
val bitmap = createBitmap(width, height)
pdfiumCore.renderPageBitmap(document, bitmap, pageItem.pageIndex, 0, 0, width, height)
// 防止重名
var outFile = File(outputDir, "${baseName}_page_${pageItem.pageIndex + 1}.$extension")
var counter = 1
while (outFile.exists()) {
outFile = File(outputDir, "${baseName}_page_${pageItem.pageIndex + 1}($counter).$extension")
counter++
}
FileOutputStream(outFile).use {
bitmap.compress(compressFormat, quality, it)
}
resultList.add(outFile)
onProgress?.invoke(index + 1, total)
delay(1)
} catch (e: Exception) {
e.printStackTrace()
}
}
pdfiumCore.closeDocument(document)
fd.close()
resultList
}
} }

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bg_color"
android:orientation="vertical">
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="0dp" />
<LinearLayout
android:id="@+id/statusLayout"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/backBtn"
android:layout_width="40dp"
android:background="@drawable/dr_click_effect_oval_transparent"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:gravity="center">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/back_black" />
</LinearLayout>
<TextView
android:id="@+id/title"
style="@style/TextViewFont_PopMedium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_vertical"
android:text="@string/selected_page"
android:textColor="@color/black"
android:textSize="16sp" />
<LinearLayout
android:id="@+id/selectAllBtn"
android:layout_width="44dp"
android:layout_height="44dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:gravity="center">
<ImageView
android:id="@+id/selectAll"
android:layout_width="24dp"
android:layout_height="24dp"
android:background="@drawable/dr_circular_sel_off_bg"
android:padding="2dp"
android:src="@drawable/gou_white" />
</LinearLayout>
</LinearLayout>
<include
android:id="@+id/loadingRoot"
layout="@layout/layout_loading"
android:visibility="gone" />
<RelativeLayout
android:id="@+id/splitListLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/splitRv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/continue_now_btn"
android:layout_margin="8dp"
android:overScrollMode="never"
android:scrollbars="none"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:itemCount="6"
tools:listitem="@layout/adapter_split_page_item" />
<LinearLayout
android:id="@+id/continue_now_btn"
android:layout_width="match_parent"
android:layout_height="48dp"
android:layout_alignParentBottom="true"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/dr_btn_not_clickable_bg"
android:gravity="center">
<TextView
style="@style/TextViewFont_PopSemiBold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/convert"
android:textColor="@color/white"
android:textSize="16sp" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>

View File

@ -41,17 +41,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/img_to_pdf" /> android:text="@string/img_to_pdf" />
<LinearLayout <Button
android:id="@+id/pdfToImgBtn"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="wrap_content"
android:layout_weight="1" android:text="@string/pdf_to_img" />
android:orientation="vertical">
<FrameLayout
android:id="@+id/fragment_fl"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -158,4 +158,9 @@
<string name="unknown_source">Unknown Source</string> <string name="unknown_source">Unknown Source</string>
<string name="img_to_pdf">Image to PDF</string> <string name="img_to_pdf">Image to PDF</string>
<string name="pdf_to_img">PDF to image</string> <string name="pdf_to_img">PDF to image</string>
<string name="convert">CONVERT</string>
<string name="converted_successfully">Converted successfully!</string>
<string name="no_app_to_open_image">No app found to open image</string>
<string name="failed_to_open_image">Failed to open image</string>
<string name="press_again_to_exit">Press again to exit</string>
</resources> </resources>

View File

@ -1,7 +1,24 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android"> <paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 允许分享外部存储中的文件 -->
<!-- 外部公共存储目录,例如 Documents、Pictures、Download -->
<external-path <external-path
name="external_files" name="external_public"
path="." /> path="." />
<!-- 应用专属外部存储目录 -->
<external-files-path
name="external_private"
path="." />
<!-- 应用内部 files 目录 -->
<files-path
name="internal_files"
path="." />
<!-- 应用缓存目录 -->
<cache-path
name="cache"
path="." />
</paths> </paths>