添加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,6 +7,7 @@
<uses-feature
android:name="android.hardware.camera"
android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
@ -104,6 +105,13 @@
android:label="@string/app_name"
android:screenOrientation="portrait" />
<activity
android:name=".ui.act.PdfToImageActivity"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="portrait" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"

View File

@ -6,6 +6,7 @@ enum class PdfPickerSource {
SPLIT, // PDF拆分
LOCK, // PDF加密
UNLOCK, // PDF解密
TO_IMAGES, // PDF转图片
IMAGES_TO_PDF,// 图片转PDF
PDF_TO_IMAGES,// 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.Bundle
import android.view.View
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
@ -55,6 +56,7 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
setupDoubleBackExit()
initObserve()
ImmersionBar.with(this).statusBarView(binding.view).statusBarDarkFont(true)
.navigationBarColor(R.color.white).init()
@ -475,4 +477,25 @@ class MainActivity : BaseActivity(), PermissionDialogFragment.PermissionCallback
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")
}
PdfPickerSource.TO_IMAGES -> {}
PdfPickerSource.IMAGES_TO_PDF -> {}
PdfPickerSource.TO_LONG_IMAGE -> {}
PdfPickerSource.NONE -> {}
PdfPickerSource.PDF_TO_IMAGES -> {
val intent = PdfToImageActivity.createIntent(this, pdf.filePath)
startActivity(intent)
finish()
}
}
},
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.R
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.PdfSelectedPagesItem
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_FILE_PATH = "extra_file_path"
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(
context: Context,
@ -84,6 +87,21 @@ class PdfResultActivity : BaseActivity() {
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
@ -248,7 +266,7 @@ class PdfResultActivity : BaseActivity() {
)
resultList.add(result)
}
} else if (source == PdfPickerSource.TO_IMAGES) {
} else if (source == PdfPickerSource.IMAGES_TO_PDF) {
val files = requireStringArrayList(EXTRA_FILE_LIST)
if (files.isEmpty()) {
showToast(getString(R.string.pdf_loading_failed))
@ -267,9 +285,7 @@ class PdfResultActivity : BaseActivity() {
outputDir = outputDir,
outputFileName = outputFileName,
onProgress = { current, total ->
logDebug("current->$current total->$total")
val progressPercent = current * 100 / total
logDebug("progressPercent->$progressPercent")
runOnUiThread {
binding.progressBar.progress = progressPercent
binding.progressTv.text = "$progressPercent"
@ -281,6 +297,41 @@ class PdfResultActivity : BaseActivity() {
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) {
binding.processingLayout.visibility = View.GONE
@ -307,6 +358,16 @@ class PdfResultActivity : BaseActivity() {
}
}
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()
selectedItem?.let {
val intent = PdfViewActivity.createIntent(this, selectedItem.filePath)
@ -315,6 +376,7 @@ class PdfResultActivity : BaseActivity() {
finish()
}
}
}
override fun 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")
}
PdfPickerSource.TO_IMAGES -> {}
PdfPickerSource.IMAGES_TO_PDF -> {}
PdfPickerSource.TO_LONG_IMAGE -> {}
PdfPickerSource.PDF_TO_IMAGES -> {}
}
}, onMoreClick = { pdf ->
ListMoreDialogFragment(pdf.filePath).show(supportFragmentManager, FRAG_TAG)

View File

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

View File

@ -1,11 +1,13 @@
package com.all.pdfreader.pro.app.util
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.ParcelFileDescriptor
import android.print.PrintAttributes
import android.print.PrintManager
@ -271,4 +273,41 @@ object AppUtils {
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.common.PDRectangle
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.delay
import kotlinx.coroutines.flow.Flow
@ -313,11 +312,7 @@ object PdfUtils {
val pdImage = JPEGFactory.createFromImage(document, bitmap)
PDPageContentStream(document, page).use { contentStream ->
contentStream.drawImage(
pdImage,
offsetX,
offsetY,
targetWidth,
targetHeight
pdImage, offsetX, offsetY, targetWidth, targetHeight
)
}
@ -344,4 +339,84 @@ object PdfUtils {
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:text="@string/img_to_pdf" />
<LinearLayout
<Button
android:id="@+id/pdfToImgBtn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:orientation="vertical">
<FrameLayout
android:id="@+id/fragment_fl"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
android:layout_height="wrap_content"
android:text="@string/pdf_to_img" />
</LinearLayout>

View File

@ -158,4 +158,9 @@
<string name="unknown_source">Unknown Source</string>
<string name="img_to_pdf">Image to PDF</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>

View File

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