PDFView with ViewPager Examples
Learn how to render PDFs and swipe through the pages via ViewPager2 or ViewPager.
Concepts You will learn
The examples in this section are to teach the following concepts:
- How to create a swipeable
PDFReader
. - How to load PDFs from online or locally.
- Load PDFs in an android device.
- Use PDFView with
ViewPager2
orViewPager
.
Here are the examples:
Use Android-Pdf-Viewer
Android Pdf Viewer Library that uses the PdfRenderer and a ViewPager.
A simple library that wraps the Android's PdfRenderer
, ViewPager
to swipe between pages and the PhotoViewer for pinch and zoom support.
Step 1: Add as a Dependency
Add this in your root build.gradle file (not your module build.gradle file):
Then, add the library to your module build.gradle
Step 2: Usage
Add the following in your layout:
<me.ameriod.lib.pdfviewer.PdfViewerView
android:id="@+id/pdfViewer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Then call one of the setPdf()
methods to display the PDF.
Full Example
For a full PDFView with ViewPager example project based on this library follow the following steps.
Start by instaling the library as has been discussed.
Step 1. Design Layouts
In Android we design our UI interfaces using XML. So let's create the following layouts:
(a). activity_main.xml
Our
activity_main
layout.
The first step is to inside your /res/layout/
directory create an xml layout file named activity_main.xml
.
Afterwhich design your XML layout using the following 2 UI widgets and ViewGroups:
LinearLayout
- Our outer layoutme.ameriod.lib.pdfviewer.PdfViewerView
- Our custom PDF View supplied by the library.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="me.ameriod.pdfviewer.MainActivity">
<me.ameriod.lib.pdfviewer.PdfViewerView
android:id="@+id/pdfViewer"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Step 2. Write Code
Finally we need to write our code as follows:
(a). MainActivity.kt
To set a pdf all you need is:
Create a class that derives from AppCompatActivity
and add its contents as follows:
We will be overriding the following functions:
onCreate(savedInstanceState: Bundle?)
.
Just copy the code below and replace the package with your app's package name.
package replace_with_your_package_name
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
pdfViewer.setPdfFromAsset("sample.pdf")
}
}
Reference
Download the code below:
No. | Link |
---|---|
1. | Download Full Code |
2. | Read more here. |
3. | Follow code author here. |
Android PDF Reader with Viewpager2
Simple PDF reader in ViewPager2.
This example will teach you how to download and render PDF, then navigate the PDF pages by swiping through ViewPager2
.
Full Example
Let us look at a full android PDFView with ViewPager sample project.
Step 1. Dependencies
We need to add some dependencies in our app/build.gradle
file as shown below:
(a). build.gradle
Our app-level
build.gradle
.
We Prepare our dependencies as shown below. You may use later versions.
At the top of our app/build.gradle
we will apply the following 3 plugins:
- Our
com.android.application
plugin. - Our
kotlin-android
plugin. - Our
kotlin-android-extensions
plugin.
As usual, inside the android closure we will set the compileSDKVersion
and buildToolsVersion
. We will also set the application ID, minimum SDK version, target SDK version, version code and version name inside a defaultConfig
closure. We will also set the build types: to either debug or release. For each build type you can enable minification of resources, as well as enable proguard.
Afterwhich we will declare our app dependencies in under the dependencies
closure. We will need the following 10 dependencies:
- Our
Kotlin-stdlib
library. - Our
Core-ktx
library. - Our
Appcompat
library. - Our
Constraintlayout
library. - Our
Material
library. - Our
Viewpager2
library. - Our
Okhttp
library. - Our
Rxjava
library. - Our
Rxandroid
library. - Our
PhotoView
library.
Here is our full app/build.gradle
:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "30.0.0"
defaultConfig {
applicationId "com.example.pdfreader"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.3.0-alpha02'
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'com.squareup.okhttp3:okhttp:4.8.1'
implementation 'io.reactivex.rxjava2:rxjava:2.2.19'
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
Step 2. Our Android Manifest
We will need to look at our AndroidManifest.xml
.
(a). AndroidManifest.xml
Our
AndroidManifest
file.
Here we can specify permissions if necessary, register our activities, define the package name as well as register any other component like Services, ContentProviders and BroadcastReceivers. We also specify other app properties like app icon, app label and app theme. If we register an activity
we can specify if it is a launcher activity using intent filters.
Here we will add the following permission:
- Our INTERNET permission.
Here is the full Android Manifest file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.pdfreader">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Step 3. Design Layouts
In Android we design our UI interfaces using XML. So let's create the following layouts:
(a). item_page.xml
Our
item_page
layout.
inside your /res/layout/
directory create an xml layout file named item_page.xml
.
On top of that design your XML layout using the following 2 UI widgets and ViewGroups:
androidx.constraintlayout.widget.ConstraintLayout
com.github.chrisbanes.photoview.PhotoView
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/pdf_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
(b). activity_main.xml
Our
activity_main
layout.
The first step is to inside your /res/layout/
directory create an xml layout file named activity_main.xml
.
After that design your XML layout using the following 3 UI widgets and ViewGroups:
androidx.constraintlayout.widget.ConstraintLayout
- This is our root container.com.google.android.material.tabs.TabLayout
- This will will allow us show Tabsandroidx.viewpager2.widget.ViewPager2
- This will allow us to swipe our pages
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.tabs.TabLayout
android:id="@+id/pdf_page_tab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toTopOf="@id/pdf_view_pager"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:tabMode="auto" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pdf_view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/pdf_page_tab" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 4. Write Code
Finally we need to write our code as follows:
(a). PdfReader.kt
Our
PdfReader
class.
Create a Kotlin file named PdfReader.kt
. This class will receive a File
object as a constructor
On top of that we will add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Bitmap
from theandroid.graphics
package.PdfRenderer
from theandroid.graphics.pdf
package.ParcelFileDescriptor
from theandroid.os
package.ImageView
from theandroid.widget
package.
On top of that we will be creating the following methods:
openPage(page: Int, pdfImage: ImageView)
- This will allow us open a page. We pass in the target page as an integer and the PDF ImageView.close()
- This will allow us close the current page, theFileDescriptor
as well as the PDFRenderer
Just copy the code below and replace the package with your app's package name.
package replace_with_your_package_name
import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import android.widget.ImageView
import java.io.File
class PdfReader(file: File) {
private var currentPage: PdfRenderer.Page? = null
private val fileDescriptor =
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
private val pdfRenderer = PdfRenderer(fileDescriptor)
val pageCount = pdfRenderer.pageCount
fun openPage(page: Int, pdfImage: ImageView) {
if (page >= pageCount) return
currentPage?.close()
currentPage = pdfRenderer.openPage(page).apply {
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
pdfImage.setImageBitmap(bitmap)
}
}
fun close() {
currentPage?.close()
fileDescriptor.close()
pdfRenderer.close()
}
}
(b). PageHolder.kt
Our
PageHolder
class.
This class will have the following method
openPage(page: Int, pdfReader: PdfReader)
- To open a PDF page.
Just copy the code below and replace the package with your app's package name.
package replace_with_your_package_name
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.item_page.view.*
class PageHolder(view: View) : RecyclerView.ViewHolder(view) {
fun openPage(page: Int, pdfReader: PdfReader) {
pdfReader.openPage(page, itemView.pdf_image)
}
}
(c). PageAdaptor.kt
Our
PageAdaptor
class.
We will be overriding the following routine recyclerview functions:
onCreateViewHolder(parent: ViewGroup, viewType: Int): PageHolder
.getItemCount(): Int
.onBindViewHolder(holder: PageHolder, position: Int)
.
After that we will be creating the following methods:
setupPdfRenderer(parameter)
- Let's pass aPdfReader
object as a parameter.
Just copy the code below and replace the package with your app's package name.
package replace_with_your_package_name
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
class PageAdaptor: RecyclerView.Adapter<PageHolder>() {
private var pdfReader: PdfReader? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PageHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_page, parent, false)
return PageHolder(view)
}
fun setupPdfRenderer(pdfReader: PdfReader) {
this.pdfReader = pdfReader
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return pdfReader?.pageCount ?: 0
}
override fun onBindViewHolder(holder: PageHolder, position: Int) {
pdfReader?.let {
holder.openPage(position, it)
}
}
}
(d). FileDownloader.kt
Our
FileDownloader
class.
This class will allow us to download our PDF file.
It will contain the following important method:
download(url: String, file: File): Observable<Int>
- The function to download our PDF file via OkHttp and return it as an Observable.
Just copy the code below and replace the package with your app's package name.
package replace_with_your_package_name
import io.reactivex.Observable
import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.File
import java.net.HttpURLConnection
import java.util.concurrent.TimeUnit
class FileDownloader(okHttpClient: OkHttpClient) {
companion object {
private const val BUFFER_LENGTH_BYTES = 1024 * 8
private const val HTTP_TIMEOUT = 30
}
private var okHttpClient: OkHttpClient
init {
val okHttpBuilder = okHttpClient.newBuilder()
.connectTimeout(HTTP_TIMEOUT.toLong(), TimeUnit.SECONDS)
.readTimeout(HTTP_TIMEOUT.toLong(), TimeUnit.SECONDS)
this.okHttpClient = okHttpBuilder.build()
}
fun download(url: String, file: File): Observable<Int> {
return Observable.create<Int> { emitter ->
val request = Request.Builder().url(url).build()
val response = okHttpClient.newCall(request).execute()
val body = response.body
val responseCode = response.code
if (responseCode >= HttpURLConnection.HTTP_OK &&
responseCode < HttpURLConnection.HTTP_MULT_CHOICE &&
body != null) {
val length = body.contentLength()
body.byteStream().apply {
file.outputStream().use { fileOut ->
var bytesCopied = 0
val buffer = ByteArray(BUFFER_LENGTH_BYTES)
var bytes = read(buffer)
while (bytes >= 0) {
fileOut.write(buffer, 0, bytes)
bytesCopied += bytes
bytes = read(buffer)
emitter.onNext(((bytesCopied * 100)/length).toInt())
}
}
emitter.onComplete()
}
} else {
throw IllegalArgumentException("Error occurred when do http get $url")
}
}
}
}
(e). MainActivity.kt
Our
MainActivity
class.
Just copy the code below and replace the package with your app's package name.
package replace_with_your_package_name
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.android.material.tabs.TabLayoutMediator
import io.reactivex.BackpressureStrategy
import io.reactivex.android.schedulers.AndroidSchedulers.*
import io.reactivex.disposables.Disposables
import io.reactivex.plugins.RxJavaPlugins
import io.reactivex.schedulers.Schedulers
import kotlinx.android.synthetic.main.activity_main.*
import okhttp3.*
import java.io.File
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
companion object {
private const val FILE_NAME = "TestPdf.pdf"
private const val URL = "https://www.hq.nasa.gov/alsj/a17/A17_FlightPlan.pdf"
}
private var disposable = Disposables.disposed()
private var pdfReader: PdfReader? = null
private val fileDownloader by lazy {
FileDownloader(
OkHttpClient.Builder().build()
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
RxJavaPlugins.setErrorHandler {
Log.e("Error", it.localizedMessage)
}
pdf_view_pager.adapter = PageAdaptor()
val targetFile = File(cacheDir, FILE_NAME)
disposable = fileDownloader.download(URL, targetFile)
.throttleFirst(2, TimeUnit.SECONDS)
.toFlowable(BackpressureStrategy.LATEST)
.subscribeOn(Schedulers.io())
.observeOn(mainThread())
.subscribe({
Toast.makeText(this, "$it% Downloaded", Toast.LENGTH_SHORT).show()
}, {
Toast.makeText(this, it.localizedMessage, Toast.LENGTH_SHORT).show()
}, {
Toast.makeText(this, "Complete Downloaded", Toast.LENGTH_SHORT).show()
pdfReader = PdfReader(targetFile).apply {
(pdf_view_pager.adapter as PageAdaptor).setupPdfRenderer(this)
}
})
TabLayoutMediator(pdf_page_tab, pdf_view_pager) { tab, position ->
tab.text = (position + 1).toString()
}.attach()
}
override fun onDestroy() {
super.onDestroy()
disposable.dispose()
pdfReader?.close()
}
}
Reference
Download the code below:
No. | Link |
---|---|
1. | Download Full Code |
2. | Read more here. |
3. | Follow code author here. |
PDFRenderer with ViewPager2
PDFRenderer with ViewPager 2.
PDFRenderer class enables rendering a PDF document and this class is not thread safe. A typical use of the APIs to render a PDF looks like this:
// create a new renderer
PdfRenderer renderer = new PdfRenderer(getSeekableFileDescriptor());
// let us just render all pages
final int pageCount = renderer.getPageCount();
for (int i = 0; i < pageCount; i++) {
Page page = renderer.openPage(i);
// say we render for showing on the screen
page.render(mBitmap, null, null, Page.RENDER_MODE_FOR_DISPLAY);
// do stuff with the bitmap
// close the page
page.close();
}
// close the renderer
renderer.close();
Other Libraries used in this Project:
-
PhotoView: a custom implementation of ImageView that handles smoothly pinch-to-zoom and double-tap to zoom PhotoView: a custom implementation of ImageView that handles smoothly pinch-to-zoom and double-tap to zoom ViewPager 2: is now using the RecyclerView components and also brings in a lot of interesting features and improvements, such as:
-
Right-to-Left (RTL) support
- Support for vertical paging
- Ability to programmatically scroll the page
- Added MarginPageTransformer and CompositePageTransformer, which allows you to achieve beautiful custom animations for your pages
Demo
Here is the demo:
Full Example
Let us look at a full PDFView with ViewPager Example below.
Step 1. Dependencies
We need to add some dependencies in our app/build.gradle
file as shown below:
(a). build.gradle
Our app-level
build.gradle
.
We Prepare our dependencies as shown below. You may use later versions.
At the top of our app/build.gradle
we will apply the following 3 plugins:
- Our
com.android.application
plugin. - Our
kotlin-android
plugin. - Our
kotlin-android-extensions
plugin.
Among the dependencies we add include:
- Our
Viewpager2
library - Our
PhotoView
library.
Here is our full app/build.gradle
:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
applicationId "com.jorgecasariego.pdfrenderer"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1'
//External Dependencies
implementation 'androidx.viewpager2:viewpager2:1.0.0'
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
implementation "com.google.android.material:material:1.3.0-alpha02"
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
testImplementation 'junit:junit:4.13'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
Step 2. Our Android Manifest
We will need to look at our AndroidManifest.xml
.
(a). AndroidManifest.xml
Our
AndroidManifest
file.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jorgecasariego.pdfrenderer">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Step 3. Design Layouts
In Android we design our UI interfaces using XML. So let's create the following layouts:
(a). pdf_page_item.xml
Our
pdf_page_item
layout.
inside your /res/layout/
directory create an xml layout file named pdf_page_item.xml
.
Afterwhich design your XML layout using the following 2 UI widgets and ViewGroups:
LinearLayout
- Our root Viewcom.github.chrisbanes.photoview.PhotoView
- Will help in displaying PDF with zooming capabilities.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:padding="5dp">
<com.github.chrisbanes.photoview.PhotoView
android:id="@+id/pageImgView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="@dimen/pageMarginAndOffset"
android:layout_marginBottom="@dimen/pageMarginAndOffset"/>
</LinearLayout>
(b). pdf_activity.xml
Our
pdf_activity
layout.
inside your /res/layout/
directory create an xml layout file named pdf_activity.xml
.
After that design your XML layout using the following 5 UI widgets and ViewGroups:
RelativeLayout
- Root elementandroidx.appcompat.widget.Toolbar
- Our toolbarLinearLayout
ProgressBar
- To show rendering progressandroidx.viewpager2.widget.ViewPager2
- To help provide swipe capabilities.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
android:background="#808E9B"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/pdfToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/CustomActionBar"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<ProgressBar
android:id="@+id/baseProgressBar"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:layout_height="wrap_content"
android:indeterminateTint="@android:color/white"/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pageViewPager"
android:visibility="gone"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"/>
</LinearLayout>
</RelativeLayout>
(c). activity_main.xml
Our
activity_main
layout.
Inside your /res/layout/
directory create an xml layout file named activity_main.xml
.
After that design your XML layout using the following 2 UI widgets and ViewGroups:
androidx.constraintlayout.widget.ConstraintLayout
TextView
<?xml version="1.0" encoding="utf-8"?>
<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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Step 4. Write Code
Finally we need to write our code as follows:
(a). PdfBitmapPool.kt
Our
PdfBitmapPool
class.
create a Kotlin file named PdfBitmapPool.kt
.
Subsequently we will add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Bitmap
from theandroid.graphics
package.Canvas
from theandroid.graphics
package.Color
from theandroid.graphics
package.PdfRenderer
from theandroid.graphics.pdf
package.Log
from theandroid.util
package.SparseArray
from theandroid.util
package.getOrElse
from theandroidx.core.util
package.
Besides create a class that derives from Int)
and add its contents as follows:
Besides we will be creating the following methods:
getPage(parameter)
- Our function will take aInt
object as a parameter which represents the page we are gettingloadMore(parameter)
- Pass to this method aInt
object as a parameter which is the next page we are loading.getCurrentRange(parameter)
- Let's pass aInt
object as a parameter . Get current range of pages.removeOutOfRangeElements(parameter)
- Let's pass aIntProgression
object as a parameter.loadPage(parameter)
- This function will take aInt
object as a parameter which is the page we are loading.newWhiteBitmap(width: Int, height: Int): Bitmap
- Takes a width and height and returns a BitmapInt.toPixelDimension(parameter)
- Our function will take aFloat
type as a parameter.
Just copy the code below and replace the package with your app's package name.
package replace_with_your_package_name
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.pdf.PdfRenderer
import android.util.Log
import android.util.SparseArray
import androidx.core.util.getOrElse
class PdfBitmapPool(val pdfRenderer: PdfRenderer, val config: Bitmap.Config, val densityDpi : Int) {
val bitmaps: SparseArray<Bitmap> = SparseArray()
init {
val initialLoadCount =
if (pdfRenderer.pageCount < POOL_SIZE) pdfRenderer.pageCount else POOL_SIZE
for (i in 0 until initialLoadCount) {
bitmaps.append(i, loadPage(i))
}
}
var currentIndex = 0
companion object {
const val POOL_SIZE = 5
val PDF_RESOLUTION_DPI = 72
}
fun getPage(index: Int): Bitmap {
return bitmaps.getOrElse(index) {
loadPage(index)
}
}
fun loadMore(newLimitIndex: Int) {
val newRange = getCurrentRange(newLimitIndex)
removeOutOfRangeElements(newRange)
for (i in newRange) {
if (i != newLimitIndex && i in 0 until bitmaps.size() && bitmaps.indexOfKey(i) < 0)
bitmaps.append(i, loadPage(i))
}
currentIndex = newLimitIndex
}
fun getCurrentRange(currentIndex: Int): IntProgression {
val sectionSize = (POOL_SIZE - 1) / 2
return (currentIndex - sectionSize)..(currentIndex + sectionSize)
}
fun removeOutOfRangeElements(newRange: IntProgression) {
//Removing elements that are out of range, the bitmap is cleared and pushed back to the unused bitmaps stack
getCurrentRange(currentIndex).filter { !newRange.contains(it) }.forEach {
val removingBitmap = bitmaps[it]
removingBitmap?.let { bitmap ->
bitmaps.remove(it)
}
}
}
fun loadPage(pageIndex: Int): Bitmap {
Log.d(PdfBitmapPool::class.java.simpleName, "Loading page at index $pageIndex")
val page = pdfRenderer.openPage(pageIndex)
val bitmap = newWhiteBitmap(page.width.toPixelDimension(), page.height.toPixelDimension())
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)
page.close()
return bitmap
}
fun newWhiteBitmap(width: Int, height: Int): Bitmap {
val bitmap = Bitmap.createBitmap(width, height, config)
val canvas = Canvas(bitmap)
canvas.drawColor(Color.WHITE)
canvas.drawBitmap(bitmap, 0f, 0f, null)
return bitmap
}
fun Int.toPixelDimension(scaleFactor: Float = 0.4f): Int {
return ((densityDpi * this / PDF_RESOLUTION_DPI) * scaleFactor).toInt()
}
}
(b). PDFAdapter.kt
Our
PDFAdapter
class.
Okay create a Kotlin file named PDFAdapter.kt
.
Next we will add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Context
from theandroid.content
package.Bitmap
from theandroid.graphics
package.PdfRenderer
from theandroid.graphics.pdf
package.ParcelFileDescriptor
from theandroid.os
package.LayoutInflater
from theandroid.view
package.View
from theandroid.view
package.ViewGroup
from theandroid.view
package.ImageView
from theandroid.widget
package.RecyclerView
from theandroidx.recyclerview.widget
package.
On top of that create a class that derives from Context)
and add its contents as follows:
We will be overriding the following functions:
onBindViewHolder(holder: PDFPageViewHolder, position: Int)
.onCreateViewHolder(parent: ViewGroup, viewType: Int): PDFPageViewHolder
.
On top of that we will be creating the following methods:
clear()
.bind(parameter) - This function will take a
Int` object as a parameter.
Just copy the code below and replace the package with your app's package name.
package replace_with_your_package_name
import android.content.Context
import android.graphics.Bitmap
import android.graphics.pdf.PdfRenderer
import android.os.ParcelFileDescriptor
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
class PDFAdapter(pdfParcelDescriptor: ParcelFileDescriptor, context : Context) : RecyclerView.Adapter<PDFAdapter.PDFPageViewHolder>() {
private var bitmapPool: PdfBitmapPool? = null
private val pdfRenderer: PdfRenderer = PdfRenderer(pdfParcelDescriptor)
init {
bitmapPool = PdfBitmapPool(pdfRenderer, Bitmap.Config.ARGB_8888,
context.resources.displayMetrics.densityDpi)
}
override fun getItemCount(): Int = pdfRenderer.pageCount
override fun onBindViewHolder(holder: PDFPageViewHolder, position: Int) {
holder.bind(position)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PDFPageViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.pdf_page_item, parent, false)
return PDFPageViewHolder(view)
}
fun clear() {
pdfRenderer.close()
bitmapPool?.bitmaps?.clear()
}
inner class PDFPageViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(pagePosition: Int) {
val imgPage = view.findViewById<ImageView>(R.id.pageImgView)
imgPage.setImageBitmap(bitmapPool!!.getPage(pagePosition))
bitmapPool!!.loadMore(pagePosition)
}
}
}
(c). MainActivity.kt
Our
MainActivity
class.
Prepare a Kotlin file named MainActivity.kt
.
After that we will add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Bundle
from theandroid.os
package.ParcelFileDescriptor
from theandroid.os
package.MenuItem
from theandroid.view
package.View
from theandroid.view
package.AppCompatActivity
from theandroidx.appcompat.app
package.ViewCompat
from theandroidx.core.view
package.ViewPager2
from theandroidx.viewpager2.widget
package.ORIENTATION_HORIZONTAL
from theandroidx.viewpager2.widget.ViewPager2
package.*
from thekotlinx.android.synthetic.main.pdf_activity
package.
Moreover create a class that derives from AppCompatActivity
and add its contents as follows:
We will be overriding the following functions:
onCreate(savedInstanceState: Bundle?)
.onOptionsItemSelected(item: MenuItem?): Boolean
.onDestroy()
.
Afterwhich we will be creating the following methods:
initPdfViewer(parameter) - Pass to this method a
File` object as a parameter.getFile() : File
.File.copyInputStreamToFile(parameter) - Pass to this method a
InputStream` object as a parameter.
Just copy the code below and replace the package with your app's package name.
package replace_with_your_package_name
import android.os.Bundle
import android.os.ParcelFileDescriptor
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import kotlinx.android.synthetic.main.pdf_activity.*
import java.io.File
import java.io.InputStream
class MainActivity : AppCompatActivity() {
lateinit var pageViewPager: ViewPager2
var parcelFileDescriptor: ParcelFileDescriptor? = null
var pdfAdapter: PDFAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.pdf_activity)
pageViewPager = findViewById(R.id.pageViewPager)
with(pageViewPager) {
clipToPadding = false
clipChildren = false
offscreenPageLimit = 3
}
val pageMarginPx = resources.getDimensionPixelOffset(R.dimen.pageMargin)
val offsetPx = resources.getDimensionPixelOffset(R.dimen.offset)
pageViewPager.setPageTransformer { page, position ->
val viewPager = page.parent.parent as ViewPager2
val offset = position * -(2 * offsetPx + pageMarginPx)
if (viewPager.orientation == ORIENTATION_HORIZONTAL) {
if (ViewCompat.getLayoutDirection(viewPager) == ViewCompat.LAYOUT_DIRECTION_RTL) {
page.translationX = -offset
} else {
page.translationX = offset
}
} else {
page.translationY = offset
}
}
initPdfViewer(getFile())
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
when (item?.itemId) {
android.R.id.home -> onBackPressed()
}
return true
}
fun initPdfViewer(pdfFile : File){
try {
pageViewPager.visibility = View.VISIBLE
baseProgressBar.visibility = View.GONE
parcelFileDescriptor = ParcelFileDescriptor.open(pdfFile, ParcelFileDescriptor.MODE_READ_ONLY)
pdfAdapter = PDFAdapter(parcelFileDescriptor!!, this@MainActivity)
pageViewPager.adapter = pdfAdapter
}catch (e: Exception){
pdfFile.delete()
}
}
fun getFile() : File{
val inputStream = assets.open("dc_comics.pdf")
return File(filesDir.absolutePath + "dc_comics.pdf").apply {
copyInputStreamToFile(inputStream)
}
}
fun File.copyInputStreamToFile(inputStream: InputStream) {
this.outputStream().use { fileOut ->
inputStream.copyTo(fileOut)
}
}
override fun onDestroy() {
super.onDestroy()
parcelFileDescriptor?.close()
pdfAdapter?.clear()
}
}
Reference
Download the code below:
No. | Link |
---|---|
1. | Download Full Code |
2. | Read more here. |
3. | Follow code author here. |