LoginSignup
0
0

More than 1 year has passed since last update.

ViewPager2+PdfRendererで簡単なPDFビューアを作る

Last updated at Posted at 2021-11-26

背景

とある Android アプリで PDF を閲覧する機能に AndroidPdfViewer (jcenter で配布されている) を使っていたのですが、2022年2月に jcenter が使えなくなるので、「その内、MavenCentral なりで publish してくれるだろう」 と呑気に構えていたのですが、README.md の先頭行に Looking for new maintainer! と書かれていたので、これはもう更新されないんだろうなと悟りました。

Android では iOS と違い OS 標準の WebView で PDF 表示とかしてくれません。

以下の記事にあるように、これで苦労したことがある Android プログラマは多いかもしれません。

ただし、Android 5.0 から API (PdfRenderer) が提供されているので、対応 OS が Android 5.0 以降であれば自前で PDF レンダリング処理を実装する必要が無くなりました。

でも、View までは提供してくれないのが Android スタイルw

Android ではよくあること...

別件ですが MediaCodec とかも同じようなパターンですね。
MediaCodec は使い方がムズくて苦労しました...
(今は ExoPlayer があるからだいぶ楽になりましたが)

欲しい PDF ビューアの機能

  • 1ページづつ表示
  • スワイプでページ切り替え

こんな感じの仕様の PDF ビューアであれば、ViewPager2 + PdfRenderer で割と簡単に作れたので、作成方法を紹介します。

実装

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>
view_holder.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:contentDescription="@string/pdf_page" />
</FrameLayout>
MainActivity.kt
package com.suzukiplan.pdfviewer

import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2

class MainActivity : AppCompatActivity() {
    private lateinit var viewPager: ViewPager2
    private var pdfRenderer: PdfRenderer? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewPager = findViewById(R.id.view_pager)
        // 横スワイプで切り替えたい場合は ViewPager2.ORIENTATION_HORIZONTAL にする
        viewPager.orientation = ViewPager2.ORIENTATION_VERTICAL

        // レンダラを作成(以下の処理は本当は非同期の方が良い)
        pdfRenderer = PdfRenderer(assets.openFd("example.pdf").parcelFileDescriptor)

        viewPager.adapter = Adapter()
    }

    inner class Adapter : RecyclerView.Adapter<ViewHolder>() {
        override fun getItemCount() = pdfRenderer?.pageCount ?: 0
        override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(position)
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
            ViewHolder(layoutInflater.inflate(R.layout.view_holder, parent, false))
    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val imageView = itemView.findViewById<ImageView>(R.id.image_view)
        private var bitmap: Bitmap? = null

        fun bind(position: Int) {
            val page = pdfRenderer?.openPage(position) ?: return
            if (page.width != bitmap?.width || page.height != bitmap?.height) {
                bitmap?.recycle()
                bitmap = Bitmap.createBitmap(page.width, page.height, Bitmap.Config.ARGB_8888)
            }
            val bitmap = this.bitmap
            if (null != bitmap) {
                page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
                imageView.setImageBitmap(bitmap)
            }
            page.close()
        }
    }
}

上記コードは GitHub で公開しています。

つまづきポイント

page.close() が漏れると、2ページ目を openPage した時に java.lang.IllegalStateException: Current page not closed でクラッシュするので注意しましょう。

ピンチイン・アウトで拡縮したい場合

view_holder.xml の ImageView を PhotoView にすれば OK です。

view_holder.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/black">

    <com.github.chrisbanes.photoview.PhotoView
        android:id="@+id/image_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</FrameLayout>
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0