تنفيذ عارض ملفات PDF

PdfViewerFragment هي Fragment متخصّصة يمكنك استخدامها لعرض مستندات PDF داخل تطبيق Android. تسهّل PdfViewerFragment عرض ملفات PDF، ما يتيح لك التركيز على الجوانب الأخرى من وظائف تطبيقك.

النتائج

مستند PDF معروض داخل تطبيق Android باستخدام PdfViewerFragment
مستند PDF معروض في تطبيق

التوافق مع الإصدارات

لاستخدام PdfViewerFragment، يجب أن يستهدف تطبيقك الإصدار Android S (المستوى 31 لواجهة برمجة التطبيقات) على الأقل والمستوى 13 من إضافة حزمة تطوير البرامج (SDK). في حال عدم استيفاء متطلبات التوافق هذه، ستعرض المكتبة الخطأ UnsupportedOperationException.

يمكنك التحقّق من إصدار إضافة حزمة تطوير البرامج (SDK) في وقت التشغيل باستخدام الوحدة SdkExtensions. يتيح لك ذلك تحميل الجزء ومستند PDF بشكل مشروط فقط إذا كان الجهاز يستوفي المتطلبات اللازمة.

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

التبعيات

لدمج عارض ملفات PDF في تطبيقك، عليك تعريف التبعية androidx.pdf في ملف build.gradle الخاص بوحدة تطبيقك. يمكن الوصول إلى مكتبة PDF من مستودع Maven من Google.

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

PdfViewerFragment ميزة

تعرض PdfViewerFragment مستندات PDF بتنسيق مقسّم إلى صفحات، ما يسهّل التنقّل فيها. لتحميل الأجزاء بكفاءة، تستخدم هذه الأجزاء استراتيجية عرض على مرحلتين، حيث يتم تحميل أبعاد الصفحة بشكل تدريجي.

لتحسين استخدام الذاكرة، لا تعرض PdfViewerFragment سوى الصفحات المرئية حاليًا، وتزيل الصور النقطية للصفحات غير الظاهرة على الشاشة. بالإضافة إلى ذلك، يتضمّن PdfViewerFragment زر إجراء عائمًا (FAB) يتيح إضافة تعليقات توضيحية من خلال إطلاق هدف ضمني android.intent.action.ANNOTATE يحتوي على معرّف الموارد المنتظم (URI) الخاص بالمستند.

التنفيذ

تتضمّن إضافة عارض ملفات PDF إلى تطبيق Android عدة خطوات.

إنشاء تخطيط النشاط

ابدأ بتحديد رمز 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

أنشئ مثيلاً من PdfViewerFragment باستخدام أداة إدارة الأجزاء التي تم الحصول عليها من getSupportFragmentManager(). تحقَّق مما إذا كانت هناك نسخة من الجزء متوفّرة قبل إنشاء نسخة جديدة، خاصةً أثناء تغييرات الإعداد.

في المثال التالي، تعالج الدالة initializePdfViewerFragment() عملية إنشاء معاملة الجزء وتنفيذها. تستبدل الدالة جزءًا حاليًا في حاوية بنسخة من 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. لتفعيل البحث في المستندات، عليك ضبط السمة 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 مع أداة اختيار الملفات في Android. أولاً، عدِّل ملف XML الخاص بتصميم النشاط ليتضمّن زرًا يفتح أداة اختيار الملفات.

<...>
    <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 باستخدام معرّف الموارد الموحّد هذا لتحميل ملف 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 باستخدام PdfStylingOptions عند إنشاء مثيل للجزء باستخدام PdfViewerFragment.newInstance(stylingOptions).

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، استخدِم الدالة الإنشائية المحمية لتوفير خيارات التنسيق. يضمن ذلك تطبيق الأنماط المخصّصة بشكل صحيح على الجزء الموسّع.

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"
    }
}

النقاط الرئيسية حول الرمز

  • تأكَّد من أنّ مشروعك يستوفي الحد الأدنى من متطلبات مستوى واجهة برمجة التطبيقات وإضافة حزمة تطوير البرامج (SDK).
  • يجب أن يتيح مضيف النشاط PdfViewerFragment استخدام AppCompatActivity.
  • يمكنك توسيع PdfViewerFragment لإضافة سلوكيات مخصّصة.
  • تخصيص واجهة مستخدم PdfViewerFragment من خلال إلغاء سمات XML