ติดตั้งใช้งานโปรแกรมอ่าน 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 ของเลย์เอาต์ กิจกรรมให้มีปุ่มที่เปิดเครื่องมือเลือกไฟล์

<androidx.constraintlayout.widget.ConstraintLayout
    ...
    tools:context=".MainActivity">

    <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