پیاده‌سازی یک نمایشگر PDF

PdfViewerFragment یک Fragment تخصصی است که می‌توانید از آن برای نمایش اسناد PDF در برنامه اندروید خود استفاده کنید. PdfViewerFragment رندر کردن PDF را ساده می‌کند و به شما این امکان را می‌دهد که روی جنبه‌های دیگر عملکرد برنامه خود تمرکز کنید.

نتایج

یک سند PDF که با استفاده از PdfViewerFragment در یک برنامه اندروید رندر شده است.
سند PDF نمایش داده شده در یک برنامه.

سازگاری نسخه

برای استفاده از PdfViewerFragment ، برنامه شما باید حداقل Android S (سطح API 31) و SDK extension سطح 13 را هدف قرار دهد. اگر این الزامات سازگاری برآورده نشوند، کتابخانه خطای UnsupportedOperationException را صادر می‌کند.

شما می‌توانید نسخه افزونه SDK را در زمان اجرا با استفاده از ماژول SdkExtensions بررسی کنید. این به شما امکان می‌دهد که فقط در صورتی که دستگاه الزامات لازم را برآورده کند، قطعه کد و سند PDF را به صورت مشروط بارگذاری کنید.

if (SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 13) {
    // Load the fragment and document.
}

وابستگی‌ها

برای ادغام نمایشگر PDF در برنامه خود، وابستگی androidx.pdf را در فایل build.gradle ماژول برنامه خود تعریف کنید. کتابخانه PDF از مخزن Google Maven قابل دسترسی است.

dependencies {
    val pdfVersion = "1.0.0-alpha0X"
    implementation("androidx.pdf:pdf:pdf-viewer-fragment:$pdfVersion")
}

ویژگی‌های PdfViewerFragment

PdfViewerFragment اسناد PDF را در قالب صفحه‌بندی شده ارائه می‌دهد و پیمایش آنها را آسان می‌کند. برای بارگذاری کارآمد، این قطعه از یک استراتژی رندر دو مرحله‌ای استفاده می‌کند که به تدریج ابعاد صفحه را بارگذاری می‌کند.

برای بهینه‌سازی استفاده از حافظه، PdfViewerFragment فقط صفحات قابل مشاهده فعلی را رندر می‌کند و بیت‌مپ‌های صفحاتی را که خارج از صفحه هستند، منتشر می‌کند. علاوه بر این، PdfViewerFragment شامل یک دکمه اکشن شناور (FAB) است که با اجرای یک intent ضمنی android.intent.action.ANNOTATE حاوی URI سند، از حاشیه‌نویسی‌ها پشتیبانی می‌کند.

پیاده‌سازی

افزودن نمایشگر PDF به برنامه اندروید شما یک فرآیند چند مرحله‌ای است.

طرح‌بندی فعالیت را ایجاد کنید

با تعریف طرح‌بندی XML برای فعالیتی که نمایشگر PDF را میزبانی می‌کند، شروع کنید. طرح‌بندی باید شامل یک FrameLayout باشد که شامل PdfViewerFragment و دکمه‌هایی برای تعاملات کاربر، مانند جستجو در سند، باشد.

<androidx.constraintlayout.widget.ConstraintLayout 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:id="@+id/pdf_container_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <FrameLayout
        android:id="@+id/fragment_container_view"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>

    <com.google.android.material.button.MaterialButton
        android:id="@+id/search_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/search_string"
        app:strokeWidth="1dp"
        android:layout_marginStart="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

فعالیت را تنظیم کنید

اکتیویتی که میزبان PdfViewerFragment است باید AppCompatActivity ارث بری کند. در متد onCreate() اکتیویتی، نمای محتوا را روی طرح‌بندی ایجاد شده تنظیم کنید و عناصر رابط کاربری لازم را مقداردهی اولیه کنید.

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val getContentButton: MaterialButton = findViewById(R.id.launch_button)
        val searchButton: MaterialButton = findViewById(R.id.search_button)
    }
}

مقداردهی اولیه‌ی PdfViewerFragment

با استفاده از یک مدیر قطعه که از getSupportFragmentManager() به دست می‌آید، یک نمونه از PdfViewerFragment ایجاد کنید. قبل از ایجاد یک قطعه جدید، به خصوص هنگام تغییرات پیکربندی، بررسی کنید که آیا نمونه‌ای از قطعه از قبل وجود دارد یا خیر.

در مثال زیر، تابع initializePdfViewerFragment() ایجاد و ثبت تراکنش fragment را مدیریت می‌کند. این تابع یک fragment موجود در یک container را با نمونه‌ای از PdfViewerFragment شما جایگزین می‌کند.

class MainActivity : AppCompatActivity() {
    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
    private var pdfViewerFragment: PdfViewerFragment? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...

        if (pdfViewerFragment == null) {
            pdfViewerFragment =
                supportFragmentManager
                    .findFragmentByTag(PDF_VIEWER_FRAGMENT_TAG) as PdfViewerFragment?
        }

    }

    // Used to instantiate and commit the fragment.
    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
    private fun initializePdfViewerFragment() {
        // This condition can be skipped if you want to create a new fragment every time.
        if (pdfViewerFragment == null) {
            val fragmentManager: FragmentManager = supportFragmentManager

          // Fragment initialization.
          pdfViewerFragment = PdfViewerFragmentExtended()
          val transaction: FragmentTransaction = fragmentManager.beginTransaction()

          // Replace an existing fragment in a container with an instance of a new fragment.
          transaction.replace(
              R.id.fragment,4_container_view,
              pdfViewerFragment!!,
              PDF_VIEWER_FRAGMENT_TAG
          )
          transaction.commitAllowingStateLoss()
          fragmentManager.executePendingTransactions()
        }
    }

    companion object {
        private const val MIME_TYPE_PDF = "application/pdf"
        private const val PDF_VIEWER_FRAGMENT_TAG = "pdf_viewer_fragment_tag"
    }
}

گسترش قابلیت PdfViewerFragment

PdfViewerFragment توابع عمومی را در اختیار شما قرار می‌دهد که می‌توانید برای گسترش قابلیت‌های آن، آن‌ها را بازنویسی کنید. یک کلاس جدید ایجاد کنید که از PdfViewerFragment ارث‌بری کند. در زیرکلاس خود، متدهایی مانند onLoadDocumentSuccess() و onLoadDocumentError() برای اضافه کردن منطق سفارشی، مانند ثبت معیارها، بازنویسی کنید.

@RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
class PdfViewerFragmentExtended : PdfViewerFragment() {
          private val someLogger : SomeLogger = // ... used to log metrics

          override fun onLoadDocumentSuccess() {
                someLogger.log(/** log document success */)
          }

          override fun onLoadDocumentError(error: Throwable) {
                someLogger.log(/** log document error */, error)
          }
}

اگرچه PdfViewerFragment منوی جستجوی داخلی ندارد، اما از نوار جستجو پشتیبانی می‌کند. شما می‌توانید با استفاده از isTextSearchActive API، میزان نمایش نوار جستجو را کنترل کنید. برای فعال کردن جستجوی سند، ویژگی isTextSearchActive از نمونه PdfViewerFragment خود را تنظیم می‌کنید.

از WindowCompat.setDecorFitsSystemWindows() برای اطمینان از ارسال صحیح WindowInsetsCompat به نماهای محتوا استفاده کنید، که برای قرارگیری صحیح نمای جستجو ضروری است.

class MainActivity : AppCompatActivity() {
    @RequiresExtension(extension = Build.VERSION_CODES.S, version = 13)
    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        searchButton.setOnClickListener {
            pdfViewerFragment?.isTextSearchActive =
                pdfViewerFragment?.isTextSearchActive == false
        }

        // Ensure WindowInsetsCompat are passed to content views without being
        // consumed by the decor view. These insets are used to calculate the
        // position of the search view.
        WindowCompat.setDecorFitsSystemWindows(window, false)
    }
}

ادغام با انتخابگر فایل

برای اینکه کاربران بتوانند فایل‌های PDF را از دستگاه خود انتخاب کنند، PdfViewerFragment با انتخابگر فایل اندروید ادغام کنید. ابتدا، طرح‌بندی XML مربوط به activity خود را به‌روزرسانی کنید تا دکمه‌ای که انتخابگر فایل را اجرا می‌کند، در آن گنجانده شود.

<...>
    <FrameLayout
        ...
        app:layout_constraintBottom_toTopOf="@+id/launch_button"/>
    // Adding a button to open file picker.
    <com.google.android.material.button.MaterialButton
        android:id="@+id/launch_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/launch_string"
        app:strokeWidth="1dp"
        android:layout_marginEnd="8dp"
        android:layout_marginBottom="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toStartOf="@id/search_button"/>

    <com.google.android.material.button.MaterialButton
        ...
        app:layout_constraintStart_toEndOf="@id/launch_button" />

</androidx.constraintlayout.widget.ConstraintLayout>

سپس، در اکتیویتی خود، با استفاده از registerForActivityResult(GetContent()) انتخابگر فایل را اجرا کنید. هنگامی که کاربر یک فایل را انتخاب می‌کند، تابع فراخوانی یک URI ارائه می‌دهد. سپس ویژگی documentUri از نمونه PdfViewerFragment خود را با این URI تنظیم می‌کنید تا PDF انتخاب شده بارگیری و نمایش داده شود.

class MainActivity : AppCompatActivity() {
    // ...

    private var filePicker: ActivityResultLauncher<String> =
        registerForActivityResult(GetContent()) { uri: Uri? ->
            uri?.let {
                initializePdfViewerFragment()
                pdfViewerFragment?.documentUri = uri
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        // ...
        getContentButton.setOnClickListener { filePicker.launch(MIME_TYPE_PDF) }
    }

    private fun initializePdfViewerFragment() {
        // ...
    }

    companion object {
        private const val MIME_TYPE_PDF = "application/pdf"
        // ...
    }
}

رابط کاربری را سفارشی کنید

شما می‌توانید رابط کاربری PdfViewerFragment را با بازنویسی ویژگی‌های XML که کتابخانه ارائه می‌دهد، سفارشی کنید. این به شما امکان می‌دهد ظاهر عناصری مانند نوار پیمایش و نشانگر صفحه را متناسب با طراحی برنامه خود تنظیم کنید.

ویژگی‌های قابل تنظیم عبارتند از:

  • fastScrollVerticalThumbDrawable — قابلیت ترسیم برای نوار پیمایش را تنظیم می‌کند.
  • fastScrollPageIndicatorBackgroundDrawable — پس‌زمینه‌ی قابل ترسیم را برای نشانگر صفحه تنظیم می‌کند.
  • fastScrollPageIndicatorMarginEnd — حاشیه مناسب را برای نشانگر صفحه تنظیم می‌کند. مطمئن شوید که مقادیر حاشیه مثبت باشند.
  • fastScrollVerticalThumbMarginEnd — حاشیه مناسب را برای نوار پیمایش عمودی تنظیم می‌کند. مطمئن شوید که مقادیر حاشیه مثبت باشند.

برای اعمال این سفارشی‌سازی‌ها، یک سبک سفارشی در منابع XML خود تعریف کنید.

<resources>
    <style name="pdfContainerStyle">
        <item name="fastScrollVerticalThumbDrawable">@drawable/custom_thumb_drawable</item>
        <item name="fastScrollPageIndicatorBackgroundDrawable">@drawable/custom_page_indicator_background</item>
        <item name="fastScrollVerticalThumbMarginEnd">8dp</item>
    </style>
</resources>

سپس، هنگام ایجاد نمونه‌ای از قطعه کد با PdfViewerFragment.newInstance(stylingOptions) ، با استفاده از PdfStylingOptions ، منبع استایل سفارشی را در اختیار PdfViewerFragment قرار دهید.

private fun initializePdfViewerFragment() {
    // This condition can be skipped if you want to create a new fragment every time.
    if (pdfViewerFragment == null) {
      val fragmentManager: FragmentManager = supportFragmentManager

      // Create styling options.
      val stylingOptions = PdfStylingOptions(R.style.pdfContainerStyle)

      // Fragment initialization.
      pdfViewerFragment = PdfViewerFragment.newInstance(stylingOptions)

      // Execute fragment transaction.
    }
}

اگر از PdfViewerFragment زیرکلاس ساخته‌اید، از سازنده protected برای ارائه گزینه‌های استایل‌بندی استفاده کنید. این کار تضمین می‌کند که استایل‌های سفارشی شما به درستی به قطعه توسعه‌یافته‌تان اعمال می‌شوند.

class StyledPdfViewerFragment: PdfViewerFragment {

    constructor() : super()

    private constructor(pdfStylingOptions: PdfStylingOptions) : super(pdfStylingOptions)

    companion object {
        fun newInstance(): StyledPdfViewerFragment {
            val stylingOptions = PdfStylingOptions(R.style.pdfContainerStyle)
            return StyledPdfViewerFragment(stylingOptions)
        }
    }
}

اجرای کامل

کد زیر مثال کاملی از نحوه پیاده‌سازی PdfViewerFragment در اکتیویتی شما، شامل مقداردهی اولیه، ادغام انتخابگر فایل، قابلیت جستجو و سفارشی‌سازی رابط کاربری ارائه می‌دهد.

class MainActivity : AppCompatActivity() {

    private var pdfViewerFragment: PdfViewerFragment? = null
    private var filePicker: ActivityResultLauncher<String> =
        registerForActivityResult(GetContent()) { uri: Uri? ->
            uri?.let {
                initializePdfViewerFragment()
                pdfViewerFragment?.documentUri = uri
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if (pdfViewerFragment == null) {
            pdfViewerFragment =
                supportFragmentManager
                   .findFragmentByTag(PDF_VIEWER_FRAGMENT_TAG) as PdfViewerFragment?
        }

        val getContentButton: MaterialButton = findViewById(R.id.launch_button)
        val searchButton: MaterialButton = findViewById(R.id.search_button)

        getContentButton.setOnClickListener { filePicker.launch(MIME_TYPE_PDF) }
        searchButton.setOnClickListener {
            pdfViewerFragment?.isTextSearchActive = pdfViewerFragment?.isTextSearchActive == false
        }
    }

    private fun initializePdfViewerFragment() {
        // This condition can be skipped if you want to create a new fragment every time.
        if (pdfViewerFragment == null) {
            val fragmentManager: FragmentManager = supportFragmentManager

          // Create styling options.
          // val stylingOptions = PdfStylingOptions(R.style.pdfContainerStyle)

          // Fragment initialization.
          // For customization:
          // pdfViewerFragment = PdfViewerFragment.newInstance(stylingOptions)
          pdfViewerFragment = PdfViewerFragmentExtended()
          val transaction: FragmentTransaction = fragmentManager.beginTransaction()

          // Replace an existing fragment in a container with an instance of a new fragment.
          transaction.replace(
              R.id.fragment_container_view,
              pdfViewerFragment!!,
              PDF_VIEWER_FRAGMENT_TAG
          )
          transaction.commitAllowingStateLoss()
          fragmentManager.executePendingTransactions()
        }
    }

    companion object {
        private const val MIME_TYPE_PDF = "application/pdf"
        private const val PDF_VIEWER_FRAGMENT_TAG = "pdf_viewer_fragment_tag"
    }
}

نکات کلیدی در مورد کد

  • مطمئن شوید که پروژه شما حداقل الزامات سطح API و افزونه SDK را برآورده می‌کند.
  • اکتیویتی میزبان PdfViewerFragment باید AppCompatActivity ارث‌بری کند.
  • شما می‌توانید PdfViewerFragment برای افزودن رفتارهای سفارشی، گسترش دهید.
  • رابط کاربری PdfViewerFragment را با نادیده گرفتن ویژگی‌های XML سفارشی کنید.