ติดตั้งใช้งานโปรแกรมอ่าน PDF

PdfViewerFragment เป็น Fragment เฉพาะทางที่คุณใช้เพื่อแสดงเอกสาร PDF ภายในแอปพลิเคชัน Android ได้ PdfViewerFragment ช่วยให้การแสดงผล PDF ง่ายขึ้น คุณจึงมุ่งเน้นไปที่ ฟังก์ชันการทำงานอื่นๆ ของแอปได้

ผลลัพธ์

เอกสาร PDF ที่แสดงผลภายในแอปพลิเคชัน Android โดยใช้ PdfViewerFragment
เอกสาร PDF ที่แสดงในแอป

ความเข้ากันได้ของเวอร์ชัน

หากต้องการใช้ PdfViewerFragment แอปพลิเคชันของคุณต้องกำหนดเป้าหมายเป็น Android S (API ระดับ 31) และระดับส่วนขยาย SDK 13 เป็นอย่างน้อย หากไม่เป็นไปตามข้อกำหนดด้านความเข้ากันได้เหล่านี้ ไลบรารีจะแสดง UnsupportedOperationException

คุณสามารถตรวจสอบเวอร์ชันส่วนขยาย SDK ได้ที่รันไทม์โดยใช้โมดูล SdkExtensions ซึ่งช่วยให้คุณโหลด Fragment และเอกสาร PDF แบบมีเงื่อนไขได้ ก็ต่อเมื่ออุปกรณ์เป็นไปตามข้อกำหนดที่จำเป็นเท่านั้น

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

การขึ้นต่อกัน

หากต้องการรวมโปรแกรมดู PDF ไว้ในแอปพลิเคชัน ให้ประกาศทรัพยากร Dependency 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 ในรูปแบบที่มีการแบ่งหน้า ทำให้ไปยังส่วนต่างๆ ได้ง่าย เพื่อการโหลดที่มีประสิทธิภาพ Fragment ใช้กลยุทธ์การแสดงผลแบบ 2 รอบ ซึ่งจะโหลดมิติข้อมูลหน้าเว็บแบบทีละรายการ

PdfViewerFragment จะแสดงเฉพาะหน้าเว็บที่มองเห็นได้ในปัจจุบันและปล่อยบิตแมปสำหรับหน้าเว็บที่อยู่นอกหน้าจอเพื่อเพิ่มประสิทธิภาพการใช้หน่วยความจำ นอกจากนี้ PdfViewerFragment ยังมีปุ่มการทำงานแบบลอย (FAB) ที่ รองรับคำอธิบายประกอบโดยการเรียกใช้ android.intent.action.ANNOTATE Intent โดยนัยซึ่งมี 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() ของกิจกรรม ให้ตั้งค่ามุมมองเนื้อหาเป็นการจัดวางที่คุณสร้างขึ้น และเริ่มต้นองค์ประกอบ UI ที่จำเป็น

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 โดยใช้ FragmentManager ที่ได้จาก getSupportFragmentManager() ตรวจสอบว่ามีอินสแตนซ์ของ Fragment อยู่แล้วหรือไม่ก่อนสร้างอินสแตนซ์ใหม่ โดยเฉพาะอย่างยิ่ง ในระหว่างการเปลี่ยนแปลงการกำหนดค่า

ในตัวอย่างต่อไปนี้ ฟังก์ชัน initializePdfViewerFragment() จะจัดการ การสร้างและการคอมมิตธุรกรรมของ Fragment ฟังก์ชันนี้จะแทนที่ Fragment ที่มีอยู่ในคอนเทนเนอร์ด้วยอินสแตนซ์ของ 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 กับเครื่องมือเลือกไฟล์ของ 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 ด้วย 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"
        // ...
    }
}

ปรับแต่ง UI

คุณปรับแต่งอินเทอร์เฟซผู้ใช้ของ PdfViewerFragment ได้โดยการลบล้างแอตทริบิวต์ XML ที่ไลบรารีแสดง ซึ่งจะช่วยให้คุณปรับแต่งลักษณะที่ปรากฏขององค์ประกอบต่างๆ เช่น แถบเลื่อนและตัวบ่งหน้า ให้ตรงกับการออกแบบของแอปได้

แอตทริบิวต์ที่ปรับแต่งได้ ได้แก่

  • fastScrollVerticalThumbDrawable — ตั้งค่า Drawable สำหรับ แถบเลื่อน
  • fastScrollPageIndicatorBackgroundDrawable — ตั้งค่า Drawable พื้นหลังสำหรับตัวระบุหน้า
  • 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 เมื่อสร้างอินสแตนซ์ของ Fragment ด้วย 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 ให้ใช้ตัวสร้างที่ได้รับการป้องกันเพื่อ ระบุตัวเลือกการจัดรูปแบบ เพื่อให้มั่นใจว่าระบบจะใช้สไตล์ที่กำหนดเองกับ Fragment ที่ขยายอย่างถูกต้อง

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 ในกิจกรรมของคุณ ซึ่งรวมถึงการเริ่มต้น การผสานรวมเครื่องมือเลือกไฟล์ ฟังก์ชันการค้นหา และการปรับแต่ง UI

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 เพื่อเพิ่มลักษณะการทำงานที่กำหนดเองได้
  • ปรับแต่ง UI ของ PdfViewerFragment โดยการลบล้างแอตทริบิวต์ XML