Convert PDF file to Android App


To create an android app to read a PDF file, follow the steps given below.

Step 1: Create a new project with name ‘PDF Book’ and package name com.mypdfreader.readpdf. Select File -> New -> New Project. Fill the forms and click “Finish” button.

Step 2: Create an ‘assets’ folder and put the PDF file in it. Let us say name of the PDF file is ‘WHO_CDS_RBM_2002.41.pdf’.

Step 3: In the drawable folder, add two arrow icons: ‘arrow_back_black.png’ and ‘arrow_forward_black.png’.

Step 4: Open res -> layout -> xml (or) main.xml and add following code. The linear1 created here will display ZoomableImageView.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="match_parent"
	android:layout_height="match_parent"
	android:gravity="center"
	android:orientation="vertical">
	<LinearLayout
		android:id="@+id/linear1"
		android:layout_width="match_parent"
		android:layout_height="match_parent"
		android:background="#FFFFFF"
		android:gravity="center_horizontal"
		android:orientation="vertical">
	</LinearLayout>
</LinearLayout>

Step 5: Open app -> java -> package and create a new java file ZoomableImageView.java. Here a new ImageView class, which is zoomable, is defined. Add following code here.

package com.mypdfreader.readpdf;

import android.widget.*;
import android.graphics.*;
import android.content.*;
import android.view.*;

public class ZoomableImageView extends ImageView {
	Matrix matrix = new Matrix();
	static final int NONE = 0;
	static final int DRAG = 1;
	static final int ZOOM = 2;
	static final int CLICK = 3;

	int mode = NONE;
	PointF last = new PointF();
	PointF start = new PointF();
	float minScale = 1f;
	float maxScale = 4f;
	float[] m;
	float redundantXSpace, redundantYSpace;
	float width, height;
	float saveScale = 1f;
	float right, bottom, origWidth, origHeight, bmWidth, bmHeight;
	ScaleGestureDetector mScaleDetector;
	Context context;

	public ZoomableImageView(Context context) {
		super(context);
		super.setClickable(true);
		this.context = context;
		mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
		matrix.setTranslate(1f, 1f);
		m = new float[9];
		setImageMatrix(matrix);
		setScaleType(ScaleType.MATRIX);

		setOnTouchListener(new OnTouchListener() {
				@Override
				public boolean onTouch(View v, MotionEvent event) {
					mScaleDetector.onTouchEvent(event);
					matrix.getValues(m);
					float x = m[Matrix.MTRANS_X];
					float y = m[Matrix.MTRANS_Y];
					PointF curr = new PointF(event.getX(), event.getY());
					switch (event.getAction()) {
						case MotionEvent.ACTION_DOWN: last.set(event.getX(), event.getY()); start.set(last); mode = DRAG;
							break;

						case MotionEvent.ACTION_POINTER_DOWN: last.set(event.getX(), event.getY()); start.set(last);
							mode = ZOOM;
							break;

						case MotionEvent.ACTION_MOVE:
							if (mode == ZOOM || (mode == DRAG && saveScale > minScale)) {
								float deltaX = curr.x - last.x;
								float deltaY = curr.y - last.y;
								float scaleWidth = Math.round(origWidth * saveScale);
								float scaleHeight = Math.round(origHeight * saveScale);
								if (scaleWidth < width) {
									deltaX = 0;
									if (y + deltaY > 0) deltaY = -y;
									else if (y + deltaY < -bottom) deltaY = -(y + bottom);
								} else if (scaleHeight < height) {
									deltaY = 0;
									if (x + deltaX > 0) deltaX = -x;
									else if (x + deltaX < -right) deltaX = -(x + right);
								} else {
									if (x + deltaX > 0) deltaX = -x;
									else if (x + deltaX < -right) deltaX = -(x + right);
									if (y + deltaY > 0) deltaY = -y;
									else if (y + deltaY < -bottom) deltaY = -(y + bottom);
								}
								matrix.postTranslate(deltaX, deltaY);
								last.set(curr.x, curr.y);
							}
							break;

						case MotionEvent.ACTION_UP:
							mode = NONE;
							int xDiff = (int) Math.abs(curr.x - start.x);
							int yDiff = (int) Math.abs(curr.y - start.y);
							if (xDiff < CLICK && yDiff < CLICK) performClick();
							break;

						case MotionEvent.ACTION_POINTER_UP:
							mode = NONE;
							break;
					}
					setImageMatrix(matrix);
					invalidate();
					return true;
				}
			});
	}

	@Override
	public void setImageBitmap(Bitmap bm) {
		super.setImageBitmap(bm);
		bmWidth = bm.getWidth();
		bmHeight = bm.getHeight(); }

	public void setMaxZoom(float x) {
		maxScale = x; }

	private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
		@Override
		public boolean onScaleBegin(ScaleGestureDetector detector) {		
			mode = ZOOM;
			return true;}

		@Override
		public boolean onScale(ScaleGestureDetector detector) {
			float mScaleFactor = detector.getScaleFactor();
			float origScale = saveScale;
			saveScale *= mScaleFactor;
			if (saveScale > maxScale){
				saveScale = maxScale;
				mScaleFactor = maxScale / origScale;
			} else if (saveScale < minScale) {	
				saveScale = minScale;
				mScaleFactor = minScale / origScale;}
			right = width * saveScale - width - (2 * redundantXSpace * saveScale);
			bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
			if (origWidth * saveScale <= width || origHeight * saveScale <= height) {
				matrix.postScale(mScaleFactor, mScaleFactor, width / 2, height / 2);
				if (mScaleFactor < 1) {	
					matrix.getValues(m);
					float x = m[Matrix.MTRANS_X];
					float y = m[Matrix.MTRANS_Y];
					if (mScaleFactor < 1) {
						if (Math.round(origWidth * saveScale) < width) {
							if (y < -bottom) matrix.postTranslate(0, -(y + bottom));
							else if (y > 0) matrix.postTranslate(0, -y);				
						} else {
							if (x < -right) matrix.postTranslate(-(x + right), 0);
							else if (x > 0) matrix.postTranslate(-x, 0);}				
					}				
				}		
			} else {
				matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY()); matrix.getValues(m);
				float x = m[Matrix.MTRANS_X];	
				float y = m[Matrix.MTRANS_Y];
				if (mScaleFactor < 1) {
					if (x < -right) matrix.postTranslate(-(x + right), 0);	
					else if (x > 0) matrix.postTranslate(-x, 0);
					if (y < -bottom) matrix.postTranslate(0, -(y + bottom));			
					else if (y > 0) matrix.postTranslate(0, -y);}			
			}
			return true;			
		}		
	}

	@Override
	protected void onMeasure (int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		width = MeasureSpec.getSize(widthMeasureSpec);
		height = MeasureSpec.getSize(heightMeasureSpec);
		float scale;
		float scaleX = width / bmWidth;
		float scaleY = height / bmHeight;
		scale = Math.min(scaleX, scaleY); matrix.setScale(scale, scale); setImageMatrix(matrix);
		saveScale = 1f;
		redundantYSpace = height - (scale * bmHeight) ;
		redundantXSpace = width - (scale * bmWidth);
		redundantYSpace /= 2;
		redundantXSpace /= 2; matrix.postTranslate(redundantXSpace, redundantYSpace);
		origWidth = width - 2 * redundantXSpace;
		origHeight = height - 2 * redundantYSpace;
		right = width * saveScale - width - (2 * redundantXSpace * saveScale);
		bottom = height * saveScale - height - (2 * redundantYSpace * saveScale);
		setImageMatrix(matrix);
	}
}

Step 6: Open app -> java -> package and open MainActivity.java. Add following code in it. Here we create a ZoomableImageView and add it to linear1. Then we copy PDF file from assets to external cache and open the PDF file using PdfRenderer. The pages are rendered one by one, and the forward and back buttons are added as OptionsMenu items to ActionBar.

package com.mypdfreader.readpdf;

import android.app.*;
import android.os.*;
import android.view.*;
import android.view.View.*;
import android.widget.*;
import android.content.*;
import android.graphics.*;
import android.text.*;
import android.util.*;
import java.util.*;
import java.text.*;
import java.io.*;
import android.graphics.pdf.*;
import com.mypdfreader.readpdf.ZoomableImageView;

public class MainActivity extends Activity {

	private double page = 0;
	private double pageCount = 0;

	private LinearLayout linear1;
	private LinearLayout action_linear;
	private EditText edittext1;
	private TextView textview5;
	private TextView textview4;
	ZoomableImageView touch;
	PdfRenderer renderer;
	
	@Override
	protected void onCreate(Bundle _savedInstanceState) {
		super.onCreate(_savedInstanceState);
		setContentView(R.layout.main);
		
		linear1 = (LinearLayout) findViewById(R.id.linear1);
		
		textview4 = new TextView(this);
		textview4.setPadding(8,4,4,4);
		textview4.setTextSize(16);

		textview5 = new TextView(this);
		textview5.setPadding(8,4,4,4);
		textview5.setTextSize(16);
		textview5.setTextColor(Color.BLACK);
		textview5.setText("GO");
		
		edittext1 = new EditText(this);
		edittext1.setHint("0");
		edittext1.setInputType(InputType.TYPE_CLASS_NUMBER|InputType.TYPE_NUMBER_FLAG_SIGNED);
		
		action_linear = new LinearLayout(this);
		action_linear.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT ));
		action_linear.setOrientation(LinearLayout.HORIZONTAL);

		action_linear.addView(edittext1);
		action_linear.addView(textview4);
		action_linear.addView(textview5);
		
		textview5.setOnClickListener(new View.OnClickListener() {
				@Override
				public void onClick(View view) {
					if (!edittext1.getText().toString().equals("")) {
						if ((Double.parseDouble(edittext1.getText().toString()) > 0) && (Double.parseDouble(edittext1.getText().toString()) < (1 + pageCount))) {
							page = Double.parseDouble(edittext1.getText().toString()) - 1;
							_display(page);
						}
					}
				}
			});
			
		// Define a ZoomableImageView and add it to linear1
		touch = new ZoomableImageView(this);
		linear1.addView(touch);
		
		File file = new File(getExternalCacheDir(), "pdffile.pdf");
		if (!file.exists()) {
			// copy the pdf file from assets folder into the cache directory
			try{
			InputStream asset = getAssets().open("WHO_CDS_RBM_2002.41.pdf");
			FileOutputStream output = new FileOutputStream(file);
			final byte[] buffer = new byte[1024];
			int size;
			while ((size = asset.read(buffer)) != -1) {
				output.write(buffer, 0, size);
			}
			asset.close();
			output.close();
			} catch (IOException e){
				showMessage(e.getMessage());
			}
		}
		
		page = 0;
		//Use PdfRenderer to open the pdf file from it's location in cache
		try {
			renderer = new PdfRenderer(new ParcelFileDescriptor(ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)));
			pageCount = renderer.getPageCount();
			// display page 0
			_display(page);
			textview4.setText("of " + String.valueOf((long)(pageCount)));
		} catch (Exception e){
			showMessage(e.toString());
		}
	}
	
	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		menu.add(0,0,0,"pages").setActionView(action_linear).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
		menu.add(1,1,1,"back").setIcon(R.drawable.arrow_back_black).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
		menu.add(2,2,2,"forward").setIcon(R.drawable.arrow_forward_black).setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
		return super.onCreateOptionsMenu(menu);
	}
	
	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		switch(item.getItemId()){
			case 1:
				// move to previous page
				if (page > 0) {
					page--;
					_display(page);
				}
				break;
			case 2:
				// move to next page
				if (page < (pageCount - 1)) {
					page++;
					_display(page);
				}
				break;

		}
		return super.onOptionsItemSelected(item);
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		// close PdfRenderer
		if (renderer != null){
			renderer.close(); }
	}
	
	// display a particular page number in ZoomableImageView
	private void _display (double _n) {
		PdfRenderer.Page page = renderer.openPage((int)_n);
		Bitmap mBitmap = Bitmap.createBitmap((int)getDip(page.getWidth()), (int)getDip(page.getHeight()), Bitmap.Config.ARGB_8888);
		page.render(mBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
		touch.setImageBitmap(mBitmap);
		page.close();
		edittext1.setText(String.valueOf((long)(_n + 1)));
	}
	
	public void showMessage(String _s) {
		Toast.makeText(getApplicationContext(), _s, Toast.LENGTH_SHORT).show();
	}
	
	public float getDip(int _input){
		return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, _input, getResources().getDisplayMetrics());
	}
	
}

Output:
Now run the app. The app opens the first page of the PDF file, and we can move to other pages by using forward and back buttons on the ActionBar.

To convert a PDF file with different file name, just change the name of the PDF file in MainActivity.java.