├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── android-pdf-viewer ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── github │ │ └── pdfviewer │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── github │ │ │ └── pdfviewer │ │ │ ├── IShowPage.java │ │ │ ├── PDFAdapter.java │ │ │ ├── PDFConfig.java │ │ │ ├── PDFView.java │ │ │ ├── PDFViewActivity.java │ │ │ ├── PDFViewPager.java │ │ │ └── PDFZoomImageView.java │ └── res │ │ ├── layout │ │ ├── activity_pdf.xml │ │ └── each_page.xml │ │ └── values │ │ ├── attrs.xml │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── github │ └── pdfviewer │ └── ExampleUnitTest.java ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── pdfrenderer │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── sample.pdf │ │ └── sample101.pdf │ ├── java │ │ └── com │ │ │ └── pdfrenderer │ │ │ ├── PdfLoad.java │ │ │ └── RetrofitInterface.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── pdfrenderer │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshot_01.png ├── screenshot_02.png └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manishkummar21/AndroidPdfViewer/dad3052caa55e2fa48a625bd4800e200f3354bba/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidPdfViewer 2 | Lightweight PDF Renderer Android 3 | 4 | Library for displaying PDF documents on Android, with animations, gestures, zoom and double tap support.Works on API 21 (Android 5.0) and higher. 5 | 6 | [![](https://www.jitpack.io/v/manishkummar21/AndroidPdfViewer.svg)](https://www.jitpack.io/#manishkummar21/AndroidPdfViewer) 7 | 8 | 9 | # Installation 10 | Step 1. Add it in your root build.gradle at the end of repositories: 11 | ``` 12 | allprojects { 13 | repositories { 14 | ... 15 | maven { url 'https://www.jitpack.io' } 16 | } 17 | } 18 | ``` 19 | Step 2. Add the dependency 20 | ``` 21 | dependencies { 22 | implementation 'com.github.manishkummar21:AndroidPdfViewer:1.0' 23 | } 24 | ``` 25 | 26 | # Include PDFViewActivity in your manifest file 27 | 28 | ``` 29 | 30 | 31 | ``` 32 | 33 | # Load a PDF file from anywhere in your code 34 | 35 | ``` 36 | PDFView.with(context) 37 | .fromfilepath(fileapth) 38 | .swipeHorizontal(false) //if false pageswipe is vertical otherwise its horizontal 39 | .start(); 40 | ``` 41 | 42 | 43 | //Example of loading pdf file from asset folder 44 | ``` 45 | 46 | File file = new File(getCacheDir(), "sample.pdf"); 47 | if (!file.exists()) { 48 | 49 | try { 50 | InputStream asset = getAssets().open("sample.pdf"); 51 | FileOutputStream output = null; 52 | output = new FileOutputStream(file); 53 | final byte[] buffer = new byte[1024]; 54 | int size; 55 | while ((size = asset.read(buffer)) != -1) { 56 | output.write(buffer, 0, size); 57 | } 58 | asset.close(); 59 | output.close(); 60 | } catch (IOException e) { 61 | e.printStackTrace(); 62 | } 63 | 64 | } 65 | 66 | 67 | PDFView.with(this) 68 | .fromfilepath(file.getAbsolutePath())) 69 | .swipeHorizontal(false) 70 | .start() 71 | ``` 72 | ![ScreenShot](https://github.com/manishkummar21/AndroidPdfViewer/blob/master/screenshot_01.png) 73 | 74 | ![ScreenShot](https://github.com/manishkummar21/AndroidPdfViewer/blob/master/screenshot_02.png) 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /android-pdf-viewer/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android-pdf-viewer/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | group='com.github.manishkummar21' 5 | 6 | 7 | android { 8 | compileSdkVersion 28 9 | 10 | 11 | 12 | defaultConfig { 13 | minSdkVersion 21 14 | targetSdkVersion 28 15 | versionCode 1 16 | versionName "1.1" 17 | 18 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 19 | 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | 34 | implementation 'com.android.support:appcompat-v7:28.0.0-rc01' 35 | testImplementation 'junit:junit:4.12' 36 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 37 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 38 | } 39 | -------------------------------------------------------------------------------- /android-pdf-viewer/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/androidTest/java/com/github/pdfviewer/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.github.pdfviewer; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.github.pdfviewer.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/java/com/github/pdfviewer/IShowPage.java: -------------------------------------------------------------------------------- 1 | package com.github.pdfviewer; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | public interface IShowPage { 6 | 7 | public Bitmap showPage(int index); 8 | } 9 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/java/com/github/pdfviewer/PDFAdapter.java: -------------------------------------------------------------------------------- 1 | package com.github.pdfviewer; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.NonNull; 5 | import android.support.v4.view.PagerAdapter; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.LinearLayout; 10 | 11 | public class PDFAdapter extends PagerAdapter { 12 | 13 | private int page_count; 14 | private Context context; 15 | private IShowPage listener; 16 | 17 | 18 | public PDFAdapter(Context context, IShowPage listener, int page_count) { 19 | this.context = context; 20 | this.listener = listener; 21 | this.page_count = page_count; 22 | } 23 | 24 | @Override 25 | public int getCount() { 26 | return page_count; 27 | } 28 | 29 | @NonNull 30 | @Override 31 | public Object instantiateItem(@NonNull ViewGroup container, int position) { 32 | LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 33 | 34 | View itemView = inflater.inflate(R.layout.each_page, container, false); 35 | 36 | PDFZoomImageView imageView = (PDFZoomImageView ) itemView.findViewById(R.id.image); 37 | 38 | imageView.setImageBitmap(listener.showPage(position)); 39 | 40 | container.addView(itemView); 41 | 42 | return itemView; 43 | } 44 | 45 | public void destroyItem(ViewGroup container, int position, Object object) { 46 | container.removeView((LinearLayout) object); 47 | } 48 | 49 | public boolean isViewFromObject(View view, Object object) { 50 | return view == object; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/java/com/github/pdfviewer/PDFConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.pdfviewer; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | public class PDFConfig implements Parcelable { 7 | 8 | public static final String EXTRA_CONFIG = "PDFConfig"; 9 | 10 | private String filepath; 11 | private int swipeorientation; 12 | // private String network_url; 13 | 14 | public PDFConfig() { 15 | 16 | } 17 | 18 | protected PDFConfig(Parcel in) { 19 | this.filepath = in.readString(); 20 | this.swipeorientation = in.readInt(); 21 | // this.network_url = in.readString(); 22 | } 23 | 24 | 25 | public String getFilepath() { 26 | return filepath; 27 | } 28 | 29 | public void setFilepath(String filepath) { 30 | this.filepath = filepath; 31 | } 32 | 33 | public int getSwipeorientation() { 34 | return swipeorientation; 35 | } 36 | 37 | public void setSwipeorientation(int swipeorientation) { 38 | this.swipeorientation = swipeorientation; 39 | } 40 | 41 | // public String getNetwork_url() { 42 | // return network_url; 43 | // } 44 | // 45 | // public void setNetwork_url(String network_url) { 46 | // this.network_url = network_url; 47 | // } 48 | 49 | @Override 50 | public void writeToParcel(Parcel dest, int flags) { 51 | dest.writeString(filepath); 52 | dest.writeInt(swipeorientation); 53 | // dest.writeString(network_url); 54 | 55 | } 56 | 57 | @Override 58 | public int describeContents() { 59 | return 0; 60 | } 61 | 62 | public static final Creator CREATOR = new Creator() { 63 | @Override 64 | public PDFConfig createFromParcel(Parcel in) { 65 | return new PDFConfig(in); 66 | } 67 | 68 | @Override 69 | public PDFConfig[] newArray(int size) { 70 | return new PDFConfig[size]; 71 | } 72 | }; 73 | } 74 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/java/com/github/pdfviewer/PDFView.java: -------------------------------------------------------------------------------- 1 | package com.github.pdfviewer; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.support.v4.app.Fragment; 7 | 8 | public class PDFView { 9 | 10 | public static abstract class BaseBuilder { 11 | 12 | protected PDFConfig config; 13 | 14 | public BaseBuilder(Context context) { 15 | this.config = new PDFConfig(); 16 | } 17 | } 18 | 19 | public static abstract class Builder extends BaseBuilder { 20 | 21 | public Builder(Activity activity) { 22 | super(activity); 23 | } 24 | 25 | public Builder(Fragment fragment) { 26 | super(fragment.getActivity()); 27 | } 28 | 29 | public Builder fromfilepath(String filepath) { 30 | config.setFilepath(filepath); 31 | return this; 32 | } 33 | 34 | 35 | public Builder swipeHorizontal(boolean swipeOrientation) { 36 | config.setSwipeorientation(swipeOrientation ? 0 : 1); 37 | return this; 38 | } 39 | 40 | // public Builder fromurl(String url) { 41 | // config.setNetwork_url(url); 42 | // return this; 43 | // } 44 | 45 | public abstract void start(); 46 | 47 | } 48 | 49 | static class ActivityBuilder extends Builder { 50 | private Activity activity; 51 | 52 | public ActivityBuilder(Activity activity) { 53 | super(activity); 54 | this.activity = activity; 55 | } 56 | 57 | @Override 58 | public void start() { 59 | Intent intent = new Intent(activity, PDFViewActivity.class); 60 | intent.putExtra(PDFConfig.EXTRA_CONFIG, config); 61 | activity.startActivity(intent); 62 | } 63 | } 64 | 65 | public static Builder with(Activity activity) { 66 | return new ActivityBuilder(activity); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/java/com/github/pdfviewer/PDFViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.github.pdfviewer; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.graphics.Bitmap; 6 | import android.graphics.pdf.PdfRenderer; 7 | import android.os.Bundle; 8 | import android.os.ParcelFileDescriptor; 9 | import android.support.annotation.Nullable; 10 | import android.support.v7.app.AppCompatActivity; 11 | import android.widget.Toast; 12 | 13 | import java.io.File; 14 | import java.io.FileNotFoundException; 15 | import java.io.IOException; 16 | 17 | public class PDFViewActivity extends AppCompatActivity implements IShowPage { 18 | 19 | private PDFViewPager pdfviewpager; 20 | private PDFAdapter adapter; 21 | 22 | private static final String STATE_CURRENT_PAGE_INDEX = "current_page_index"; 23 | private ParcelFileDescriptor mFileDescriptor; 24 | private PdfRenderer mPdfRenderer; 25 | private PdfRenderer.Page mCurrentPage; 26 | private int mPageIndex; 27 | private PDFConfig config; 28 | 29 | 30 | @Override 31 | protected void onCreate(@Nullable Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | Intent intent = getIntent(); 34 | if (intent == null) { 35 | finish(); 36 | return; 37 | } 38 | 39 | config = intent.getParcelableExtra(PDFConfig.EXTRA_CONFIG); 40 | 41 | setContentView(R.layout.activity_pdf); 42 | pdfviewpager = (PDFViewPager) findViewById(R.id.pdfviewfpager); 43 | pdfviewpager.setSwipeOrientation(config.getSwipeorientation()); 44 | 45 | mPageIndex = 0; 46 | // If there is a savedInstanceState (screen orientations, etc.), we restore the page index. 47 | if (null != savedInstanceState) { 48 | mPageIndex = savedInstanceState.getInt(STATE_CURRENT_PAGE_INDEX, 0); 49 | } 50 | 51 | 52 | //render the pdf view 53 | try { 54 | openRenderer(this); 55 | setUpViewPager(); 56 | } catch (FileNotFoundException e) { 57 | Toast.makeText(this, "Error! " + e.getMessage(), Toast.LENGTH_SHORT).show(); 58 | finish(); 59 | } catch (IOException e) { 60 | e.printStackTrace(); 61 | Toast.makeText(this, "Error! " + e.getMessage(), Toast.LENGTH_SHORT).show(); 62 | finish(); 63 | } 64 | } 65 | 66 | @Override 67 | public void onStart() { 68 | super.onStart(); 69 | 70 | } 71 | 72 | private void setUpViewPager() { 73 | adapter = new PDFAdapter(PDFViewActivity.this, this, mPdfRenderer.getPageCount()); 74 | pdfviewpager.setAdapter(adapter); 75 | pdfviewpager.setCurrentItem(mPageIndex); 76 | } 77 | 78 | @Override 79 | protected void onDestroy() { 80 | try { 81 | closeRenderer(); 82 | } catch (IOException e) { 83 | e.printStackTrace(); 84 | } 85 | super.onDestroy(); 86 | } 87 | 88 | @Override 89 | public void onSaveInstanceState(Bundle outState) { 90 | super.onSaveInstanceState(outState); 91 | if (null != mCurrentPage) { 92 | outState.putInt(STATE_CURRENT_PAGE_INDEX, mCurrentPage.getIndex()); 93 | } 94 | } 95 | 96 | 97 | private void openRenderer(Context context) throws IOException { 98 | File file = new File(config.getFilepath()); 99 | mFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); 100 | if (mFileDescriptor != null) { 101 | mPdfRenderer = new PdfRenderer(mFileDescriptor); 102 | } 103 | 104 | } 105 | 106 | 107 | private void closeRenderer() throws IOException { 108 | if (null != mCurrentPage) 109 | mCurrentPage.close(); 110 | 111 | if (null != mPdfRenderer) 112 | mPdfRenderer.close(); 113 | 114 | if (null != mFileDescriptor) 115 | mFileDescriptor.close(); 116 | } 117 | 118 | public Bitmap showPage(int index) { 119 | if (mPdfRenderer.getPageCount() <= index) { 120 | return null; 121 | } 122 | // Make sure to close the current page before opening another one. 123 | if (null != mCurrentPage) { 124 | mCurrentPage.close(); 125 | } 126 | // Use `openPage` to open a specific page in PDF. 127 | mCurrentPage = mPdfRenderer.openPage(index); 128 | // Important: the destination bitmap must be ARGB (not RGB). 129 | Bitmap bitmap = Bitmap.createBitmap(mCurrentPage.getWidth(), mCurrentPage.getHeight(), 130 | Bitmap.Config.ARGB_8888); 131 | mCurrentPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY); 132 | return bitmap; 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/java/com/github/pdfviewer/PDFViewPager.java: -------------------------------------------------------------------------------- 1 | package com.github.pdfviewer; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.support.v4.view.ViewPager; 6 | import android.util.AttributeSet; 7 | import android.view.MotionEvent; 8 | import android.view.View; 9 | 10 | public class PDFViewPager extends ViewPager { 11 | public static final int HORIZONTAL = 0; 12 | public static final int VERTICAL = 1; 13 | 14 | private int mSwipeOrientation; 15 | // private ScrollerCustomDuration mScroller = null; 16 | 17 | public PDFViewPager(Context context) { 18 | super(context); 19 | mSwipeOrientation = HORIZONTAL; 20 | } 21 | 22 | public PDFViewPager(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | setSwipeOrientation(context, attrs); 25 | } 26 | 27 | @Override 28 | public boolean onTouchEvent(MotionEvent event) { 29 | return super.onTouchEvent(mSwipeOrientation == VERTICAL ? swapXY(event) : event); 30 | } 31 | 32 | @Override 33 | public boolean onInterceptTouchEvent(MotionEvent event) { 34 | if (mSwipeOrientation == VERTICAL) { 35 | boolean intercepted = super.onInterceptTouchEvent(swapXY(event)); 36 | swapXY(event); 37 | return intercepted; 38 | } 39 | return super.onInterceptTouchEvent(event); 40 | } 41 | 42 | public void setSwipeOrientation(int swipeOrientation) { 43 | if (swipeOrientation == HORIZONTAL || swipeOrientation == VERTICAL) 44 | mSwipeOrientation = swipeOrientation; 45 | else 46 | throw new IllegalStateException("Swipe Orientation can be either CustomViewPager.HORIZONTAL" + 47 | " or CustomViewPager.VERTICAL"); 48 | initSwipeMethods(); 49 | } 50 | 51 | private void setSwipeOrientation(Context context, AttributeSet attrs) { 52 | TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PDFViewPager); 53 | mSwipeOrientation = typedArray.getInteger(R.styleable.PDFViewPager_swipe_orientation, 0); 54 | typedArray.recycle(); 55 | initSwipeMethods(); 56 | } 57 | 58 | private void initSwipeMethods() { 59 | if (mSwipeOrientation == VERTICAL) { 60 | // The majority of the work is done over here 61 | setPageTransformer(true, new VerticalPageTransformer()); 62 | // The easiest way to get rid of the overscroll drawing that happens on the left and right 63 | setOverScrollMode(OVER_SCROLL_NEVER); 64 | } 65 | } 66 | 67 | /** 68 | * Set the factor by which the duration will change 69 | */ 70 | // public void setScrollDurationFactor(double scrollFactor) { 71 | // mScroller.setScrollDurationFactor(scrollFactor); 72 | // } 73 | 74 | private MotionEvent swapXY(MotionEvent event) { 75 | float width = getWidth(); 76 | float height = getHeight(); 77 | 78 | float newX = (event.getY() / height) * width; 79 | float newY = (event.getX() / width) * height; 80 | 81 | event.setLocation(newX, newY); 82 | return event; 83 | } 84 | 85 | private class VerticalPageTransformer implements PageTransformer { 86 | 87 | @Override 88 | public void transformPage(View page, float position) { 89 | if (position < -1) { 90 | // This page is way off-screen to the left 91 | page.setAlpha(0); 92 | } else if (position <= 1) { 93 | page.setAlpha(1); 94 | 95 | // Counteract the default slide transition 96 | page.setTranslationX(page.getWidth() * -position); 97 | 98 | // set Y position to swipe in from top 99 | float yPosition = position * page.getHeight(); 100 | page.setTranslationY(yPosition); 101 | } else { 102 | // This page is way off screen to the right 103 | page.setAlpha(0); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/java/com/github/pdfviewer/PDFZoomImageView.java: -------------------------------------------------------------------------------- 1 | package com.github.pdfviewer; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.content.res.Configuration; 6 | import android.graphics.Bitmap; 7 | import android.graphics.Canvas; 8 | import android.graphics.Matrix; 9 | import android.graphics.PointF; 10 | import android.graphics.RectF; 11 | import android.graphics.drawable.Drawable; 12 | import android.net.Uri; 13 | import android.os.Build.VERSION; 14 | import android.os.Build.VERSION_CODES; 15 | import android.os.Bundle; 16 | import android.os.Parcelable; 17 | import android.util.AttributeSet; 18 | import android.util.Log; 19 | import android.view.GestureDetector; 20 | import android.view.MotionEvent; 21 | import android.view.ScaleGestureDetector; 22 | import android.view.View; 23 | import android.view.animation.AccelerateDecelerateInterpolator; 24 | import android.widget.ImageView; 25 | import android.widget.OverScroller; 26 | import android.widget.Scroller; 27 | 28 | public class PDFZoomImageView extends ImageView { 29 | 30 | private static final String DEBUG = "DEBUG"; 31 | 32 | // 33 | // SuperMin and SuperMax multipliers. Determine how much the image can be 34 | // zoomed below or above the zoom boundaries, before animating back to the 35 | // min/max zoom boundary. 36 | // 37 | private static final float SUPER_MIN_MULTIPLIER = .75f; 38 | private static final float SUPER_MAX_MULTIPLIER = 1.25f; 39 | 40 | // 41 | // Scale of image ranges from minScale to maxScale, where minScale == 1 42 | // when the image is stretched to fit view. 43 | // 44 | private float normalizedScale; 45 | 46 | // 47 | // Matrix applied to image. MSCALE_X and MSCALE_Y should always be equal. 48 | // MTRANS_X and MTRANS_Y are the other values used. prevMatrix is the matrix 49 | // saved prior to the screen rotating. 50 | // 51 | private Matrix matrix, prevMatrix; 52 | 53 | private static enum State { NONE, DRAG, ZOOM, FLING, ANIMATE_ZOOM }; 54 | private State state; 55 | 56 | private float minScale; 57 | private float maxScale; 58 | private float superMinScale; 59 | private float superMaxScale; 60 | private float[] m; 61 | 62 | private Context context; 63 | private Fling fling; 64 | 65 | private ScaleType mScaleType; 66 | 67 | private boolean imageRenderedAtLeastOnce; 68 | private boolean onDrawReady; 69 | 70 | private ZoomVariables delayedZoomVariables; 71 | 72 | // 73 | // Size of view and previous view size (ie before rotation) 74 | // 75 | private int viewWidth, viewHeight, prevViewWidth, prevViewHeight; 76 | 77 | // 78 | // Size of image when it is stretched to fit view. Before and After rotation. 79 | // 80 | private float matchViewWidth, matchViewHeight, prevMatchViewWidth, prevMatchViewHeight; 81 | 82 | private ScaleGestureDetector mScaleDetector; 83 | private GestureDetector mGestureDetector; 84 | private GestureDetector.OnDoubleTapListener doubleTapListener = null; 85 | private OnTouchListener userTouchListener = null; 86 | private OnTouchImageViewListener touchImageViewListener = null; 87 | 88 | public PDFZoomImageView(Context context) { 89 | super(context); 90 | sharedConstructing(context); 91 | } 92 | 93 | public PDFZoomImageView(Context context, AttributeSet attrs) { 94 | super(context, attrs); 95 | sharedConstructing(context); 96 | } 97 | 98 | public PDFZoomImageView(Context context, AttributeSet attrs, int defStyle) { 99 | super(context, attrs, defStyle); 100 | sharedConstructing(context); 101 | } 102 | 103 | private void sharedConstructing(Context context) { 104 | super.setClickable(true); 105 | this.context = context; 106 | mScaleDetector = new ScaleGestureDetector(context, new ScaleListener()); 107 | mGestureDetector = new GestureDetector(context, new GestureListener()); 108 | matrix = new Matrix(); 109 | prevMatrix = new Matrix(); 110 | m = new float[9]; 111 | normalizedScale = 1; 112 | if (mScaleType == null) { 113 | mScaleType = ScaleType.FIT_CENTER; 114 | } 115 | minScale = 1; 116 | maxScale = 3; 117 | superMinScale = SUPER_MIN_MULTIPLIER * minScale; 118 | superMaxScale = SUPER_MAX_MULTIPLIER * maxScale; 119 | setImageMatrix(matrix); 120 | setScaleType(ScaleType.MATRIX); 121 | setState(State.NONE); 122 | onDrawReady = false; 123 | super.setOnTouchListener(new PrivateOnTouchListener()); 124 | } 125 | 126 | @Override 127 | public void setOnTouchListener(OnTouchListener l) { 128 | userTouchListener = l; 129 | } 130 | 131 | public void setOnTouchImageViewListener(OnTouchImageViewListener l) { 132 | touchImageViewListener = l; 133 | } 134 | 135 | public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener l) { 136 | doubleTapListener = l; 137 | } 138 | 139 | @Override 140 | public void setImageResource(int resId) { 141 | super.setImageResource(resId); 142 | savePreviousImageValues(); 143 | fitImageToView(); 144 | } 145 | 146 | @Override 147 | public void setImageBitmap(Bitmap bm) { 148 | super.setImageBitmap(bm); 149 | savePreviousImageValues(); 150 | fitImageToView(); 151 | } 152 | 153 | @Override 154 | public void setImageDrawable(Drawable drawable) { 155 | super.setImageDrawable(drawable); 156 | savePreviousImageValues(); 157 | fitImageToView(); 158 | } 159 | 160 | @Override 161 | public void setImageURI(Uri uri) { 162 | super.setImageURI(uri); 163 | savePreviousImageValues(); 164 | fitImageToView(); 165 | } 166 | 167 | @Override 168 | public void setScaleType(ScaleType type) { 169 | if (type == ScaleType.FIT_START || type == ScaleType.FIT_END) { 170 | throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END"); 171 | } 172 | if (type == ScaleType.MATRIX) { 173 | super.setScaleType(ScaleType.MATRIX); 174 | 175 | } else { 176 | mScaleType = type; 177 | if (onDrawReady) { 178 | // 179 | // If the image is already rendered, scaleType has been called programmatically 180 | // and the TouchImageView should be updated with the new scaleType. 181 | // 182 | setZoom(this); 183 | } 184 | } 185 | } 186 | 187 | @Override 188 | public ScaleType getScaleType() { 189 | return mScaleType; 190 | } 191 | 192 | /** 193 | * Returns false if image is in initial, unzoomed state. False, otherwise. 194 | * @return true if image is zoomed 195 | */ 196 | public boolean isZoomed() { 197 | return normalizedScale != 1; 198 | } 199 | 200 | /** 201 | * Return a Rect representing the zoomed image. 202 | * @return rect representing zoomed image 203 | */ 204 | public RectF getZoomedRect() { 205 | if (mScaleType == ScaleType.FIT_XY) { 206 | throw new UnsupportedOperationException("getZoomedRect() not supported with FIT_XY"); 207 | } 208 | PointF topLeft = transformCoordTouchToBitmap(0, 0, true); 209 | PointF bottomRight = transformCoordTouchToBitmap(viewWidth, viewHeight, true); 210 | 211 | float w = getDrawable().getIntrinsicWidth(); 212 | float h = getDrawable().getIntrinsicHeight(); 213 | return new RectF(topLeft.x / w, topLeft.y / h, bottomRight.x / w, bottomRight.y / h); 214 | } 215 | 216 | /** 217 | * Save the current matrix and view dimensions 218 | * in the prevMatrix and prevView variables. 219 | */ 220 | private void savePreviousImageValues() { 221 | if (matrix != null && viewHeight != 0 && viewWidth != 0) { 222 | matrix.getValues(m); 223 | prevMatrix.setValues(m); 224 | prevMatchViewHeight = matchViewHeight; 225 | prevMatchViewWidth = matchViewWidth; 226 | prevViewHeight = viewHeight; 227 | prevViewWidth = viewWidth; 228 | } 229 | } 230 | 231 | @Override 232 | public Parcelable onSaveInstanceState() { 233 | Bundle bundle = new Bundle(); 234 | bundle.putParcelable("instanceState", super.onSaveInstanceState()); 235 | bundle.putFloat("saveScale", normalizedScale); 236 | bundle.putFloat("matchViewHeight", matchViewHeight); 237 | bundle.putFloat("matchViewWidth", matchViewWidth); 238 | bundle.putInt("viewWidth", viewWidth); 239 | bundle.putInt("viewHeight", viewHeight); 240 | matrix.getValues(m); 241 | bundle.putFloatArray("matrix", m); 242 | bundle.putBoolean("imageRendered", imageRenderedAtLeastOnce); 243 | return bundle; 244 | } 245 | 246 | @Override 247 | public void onRestoreInstanceState(Parcelable state) { 248 | if (state instanceof Bundle) { 249 | Bundle bundle = (Bundle) state; 250 | normalizedScale = bundle.getFloat("saveScale"); 251 | m = bundle.getFloatArray("matrix"); 252 | prevMatrix.setValues(m); 253 | prevMatchViewHeight = bundle.getFloat("matchViewHeight"); 254 | prevMatchViewWidth = bundle.getFloat("matchViewWidth"); 255 | prevViewHeight = bundle.getInt("viewHeight"); 256 | prevViewWidth = bundle.getInt("viewWidth"); 257 | imageRenderedAtLeastOnce = bundle.getBoolean("imageRendered"); 258 | super.onRestoreInstanceState(bundle.getParcelable("instanceState")); 259 | return; 260 | } 261 | 262 | super.onRestoreInstanceState(state); 263 | } 264 | 265 | @Override 266 | protected void onDraw(Canvas canvas) { 267 | onDrawReady = true; 268 | imageRenderedAtLeastOnce = true; 269 | if (delayedZoomVariables != null) { 270 | setZoom(delayedZoomVariables.scale, delayedZoomVariables.focusX, delayedZoomVariables.focusY, delayedZoomVariables.scaleType); 271 | delayedZoomVariables = null; 272 | } 273 | super.onDraw(canvas); 274 | } 275 | 276 | @Override 277 | public void onConfigurationChanged(Configuration newConfig) { 278 | super.onConfigurationChanged(newConfig); 279 | savePreviousImageValues(); 280 | } 281 | 282 | /** 283 | * Get the max zoom multiplier. 284 | * @return max zoom multiplier. 285 | */ 286 | public float getMaxZoom() { 287 | return maxScale; 288 | } 289 | 290 | /** 291 | * Set the max zoom multiplier. Default value: 3. 292 | * @param max max zoom multiplier. 293 | */ 294 | public void setMaxZoom(float max) { 295 | maxScale = max; 296 | superMaxScale = SUPER_MAX_MULTIPLIER * maxScale; 297 | } 298 | 299 | /** 300 | * Get the min zoom multiplier. 301 | * @return min zoom multiplier. 302 | */ 303 | public float getMinZoom() { 304 | return minScale; 305 | } 306 | 307 | /** 308 | * Get the current zoom. This is the zoom relative to the initial 309 | * scale, not the original resource. 310 | * @return current zoom multiplier. 311 | */ 312 | public float getCurrentZoom() { 313 | return normalizedScale; 314 | } 315 | 316 | /** 317 | * Set the min zoom multiplier. Default value: 1. 318 | * @param min min zoom multiplier. 319 | */ 320 | public void setMinZoom(float min) { 321 | minScale = min; 322 | superMinScale = SUPER_MIN_MULTIPLIER * minScale; 323 | } 324 | 325 | /** 326 | * Reset zoom and translation to initial state. 327 | */ 328 | public void resetZoom() { 329 | normalizedScale = 1; 330 | fitImageToView(); 331 | } 332 | 333 | /** 334 | * Set zoom to the specified scale. Image will be centered by default. 335 | * @param scale 336 | */ 337 | public void setZoom(float scale) { 338 | setZoom(scale, 0.5f, 0.5f); 339 | } 340 | 341 | /** 342 | * Set zoom to the specified scale. Image will be centered around the point 343 | * (focusX, focusY). These floats range from 0 to 1 and denote the focus point 344 | * as a fraction from the left and top of the view. For example, the top left 345 | * corner of the image would be (0, 0). And the bottom right corner would be (1, 1). 346 | * @param scale 347 | * @param focusX 348 | * @param focusY 349 | */ 350 | public void setZoom(float scale, float focusX, float focusY) { 351 | setZoom(scale, focusX, focusY, mScaleType); 352 | } 353 | 354 | /** 355 | * Set zoom to the specified scale. Image will be centered around the point 356 | * (focusX, focusY). These floats range from 0 to 1 and denote the focus point 357 | * as a fraction from the left and top of the view. For example, the top left 358 | * corner of the image would be (0, 0). And the bottom right corner would be (1, 1). 359 | * @param scale 360 | * @param focusX 361 | * @param focusY 362 | * @param scaleType 363 | */ 364 | public void setZoom(float scale, float focusX, float focusY, ScaleType scaleType) { 365 | // 366 | // setZoom can be called before the image is on the screen, but at this point, 367 | // image and view sizes have not yet been calculated in onMeasure. Thus, we should 368 | // delay calling setZoom until the view has been measured. 369 | // 370 | if (!onDrawReady) { 371 | delayedZoomVariables = new ZoomVariables(scale, focusX, focusY, scaleType); 372 | return; 373 | } 374 | 375 | if (scaleType != mScaleType) { 376 | setScaleType(scaleType); 377 | } 378 | resetZoom(); 379 | scaleImage(scale, viewWidth / 2, viewHeight / 2, true); 380 | matrix.getValues(m); 381 | m[Matrix.MTRANS_X] = -((focusX * getImageWidth()) - (viewWidth * 0.5f)); 382 | m[Matrix.MTRANS_Y] = -((focusY * getImageHeight()) - (viewHeight * 0.5f)); 383 | matrix.setValues(m); 384 | fixTrans(); 385 | setImageMatrix(matrix); 386 | } 387 | 388 | 389 | public void setZoom(PDFZoomImageView img) { 390 | PointF center = img.getScrollPosition(); 391 | setZoom(img.getCurrentZoom(), center.x, center.y, img.getScaleType()); 392 | } 393 | 394 | /** 395 | * Return the point at the center of the zoomed image. The PointF coordinates range 396 | * in value between 0 and 1 and the focus point is denoted as a fraction from the left 397 | * and top of the view. For example, the top left corner of the image would be (0, 0). 398 | * And the bottom right corner would be (1, 1). 399 | * @return PointF representing the scroll position of the zoomed image. 400 | */ 401 | public PointF getScrollPosition() { 402 | Drawable drawable = getDrawable(); 403 | if (drawable == null) { 404 | return null; 405 | } 406 | int drawableWidth = drawable.getIntrinsicWidth(); 407 | int drawableHeight = drawable.getIntrinsicHeight(); 408 | 409 | PointF point = transformCoordTouchToBitmap(viewWidth / 2, viewHeight / 2, true); 410 | point.x /= drawableWidth; 411 | point.y /= drawableHeight; 412 | return point; 413 | } 414 | 415 | /** 416 | * Set the focus point of the zoomed image. The focus points are denoted as a fraction from the 417 | * left and top of the view. The focus points can range in value between 0 and 1. 418 | * @param focusX 419 | * @param focusY 420 | */ 421 | public void setScrollPosition(float focusX, float focusY) { 422 | setZoom(normalizedScale, focusX, focusY); 423 | } 424 | 425 | /** 426 | * Performs boundary checking and fixes the image matrix if it 427 | * is out of bounds. 428 | */ 429 | private void fixTrans() { 430 | matrix.getValues(m); 431 | float transX = m[Matrix.MTRANS_X]; 432 | float transY = m[Matrix.MTRANS_Y]; 433 | 434 | float fixTransX = getFixTrans(transX, viewWidth, getImageWidth()); 435 | float fixTransY = getFixTrans(transY, viewHeight, getImageHeight()); 436 | 437 | if (fixTransX != 0 || fixTransY != 0) { 438 | matrix.postTranslate(fixTransX, fixTransY); 439 | } 440 | } 441 | 442 | /** 443 | * When transitioning from zooming from focus to zoom from center (or vice versa) 444 | * the image can become unaligned within the view. This is apparent when zooming 445 | * quickly. When the content size is less than the view size, the content will often 446 | * be centered incorrectly within the view. fixScaleTrans first calls fixTrans() and 447 | * then makes sure the image is centered correctly within the view. 448 | */ 449 | private void fixScaleTrans() { 450 | fixTrans(); 451 | matrix.getValues(m); 452 | if (getImageWidth() < viewWidth) { 453 | m[Matrix.MTRANS_X] = (viewWidth - getImageWidth()) / 2; 454 | } 455 | 456 | if (getImageHeight() < viewHeight) { 457 | m[Matrix.MTRANS_Y] = (viewHeight - getImageHeight()) / 2; 458 | } 459 | matrix.setValues(m); 460 | } 461 | 462 | private float getFixTrans(float trans, float viewSize, float contentSize) { 463 | float minTrans, maxTrans; 464 | 465 | if (contentSize <= viewSize) { 466 | minTrans = 0; 467 | maxTrans = viewSize - contentSize; 468 | 469 | } else { 470 | minTrans = viewSize - contentSize; 471 | maxTrans = 0; 472 | } 473 | 474 | if (trans < minTrans) 475 | return -trans + minTrans; 476 | if (trans > maxTrans) 477 | return -trans + maxTrans; 478 | return 0; 479 | } 480 | 481 | private float getFixDragTrans(float delta, float viewSize, float contentSize) { 482 | if (contentSize <= viewSize) { 483 | return 0; 484 | } 485 | return delta; 486 | } 487 | 488 | private float getImageWidth() { 489 | return matchViewWidth * normalizedScale; 490 | } 491 | 492 | private float getImageHeight() { 493 | return matchViewHeight * normalizedScale; 494 | } 495 | 496 | @Override 497 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 498 | Drawable drawable = getDrawable(); 499 | if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) { 500 | setMeasuredDimension(0, 0); 501 | return; 502 | } 503 | 504 | int drawableWidth = drawable.getIntrinsicWidth(); 505 | int drawableHeight = drawable.getIntrinsicHeight(); 506 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 507 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 508 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 509 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 510 | viewWidth = setViewSize(widthMode, widthSize, drawableWidth); 511 | viewHeight = setViewSize(heightMode, heightSize, drawableHeight); 512 | 513 | // 514 | // Set view dimensions 515 | // 516 | setMeasuredDimension(viewWidth, viewHeight); 517 | 518 | // 519 | // Fit content within view 520 | // 521 | fitImageToView(); 522 | } 523 | 524 | /** 525 | * If the normalizedScale is equal to 1, then the image is made to fit the screen. Otherwise, 526 | * it is made to fit the screen according to the dimensions of the previous image matrix. This 527 | * allows the image to maintain its zoom after rotation. 528 | */ 529 | private void fitImageToView() { 530 | Drawable drawable = getDrawable(); 531 | if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0) { 532 | return; 533 | } 534 | if (matrix == null || prevMatrix == null) { 535 | return; 536 | } 537 | 538 | int drawableWidth = drawable.getIntrinsicWidth(); 539 | int drawableHeight = drawable.getIntrinsicHeight(); 540 | 541 | // 542 | // Scale image for view 543 | // 544 | float scaleX = (float) viewWidth / drawableWidth; 545 | float scaleY = (float) viewHeight / drawableHeight; 546 | 547 | switch (mScaleType) { 548 | case CENTER: 549 | scaleX = scaleY = 1; 550 | break; 551 | 552 | case CENTER_CROP: 553 | scaleX = scaleY = Math.max(scaleX, scaleY); 554 | break; 555 | 556 | case CENTER_INSIDE: 557 | scaleX = scaleY = Math.min(1, Math.min(scaleX, scaleY)); 558 | 559 | case FIT_CENTER: 560 | scaleX = scaleY = Math.min(scaleX, scaleY); 561 | break; 562 | 563 | case FIT_XY: 564 | break; 565 | 566 | default: 567 | // 568 | // FIT_START and FIT_END not supported 569 | // 570 | throw new UnsupportedOperationException("TouchImageView does not support FIT_START or FIT_END"); 571 | 572 | } 573 | 574 | // 575 | // Center the image 576 | // 577 | float redundantXSpace = viewWidth - (scaleX * drawableWidth); 578 | float redundantYSpace = viewHeight - (scaleY * drawableHeight); 579 | matchViewWidth = viewWidth - redundantXSpace; 580 | matchViewHeight = viewHeight - redundantYSpace; 581 | if (!isZoomed() && !imageRenderedAtLeastOnce) { 582 | // 583 | // Stretch and center image to fit view 584 | // 585 | matrix.setScale(scaleX, scaleY); 586 | matrix.postTranslate(redundantXSpace / 2, redundantYSpace / 2); 587 | normalizedScale = 1; 588 | 589 | } else { 590 | // 591 | // These values should never be 0 or we will set viewWidth and viewHeight 592 | // to NaN in translateMatrixAfterRotate. To avoid this, call savePreviousImageValues 593 | // to set them equal to the current values. 594 | // 595 | if (prevMatchViewWidth == 0 || prevMatchViewHeight == 0) { 596 | savePreviousImageValues(); 597 | } 598 | 599 | prevMatrix.getValues(m); 600 | 601 | // 602 | // Rescale Matrix after rotation 603 | // 604 | m[Matrix.MSCALE_X] = matchViewWidth / drawableWidth * normalizedScale; 605 | m[Matrix.MSCALE_Y] = matchViewHeight / drawableHeight * normalizedScale; 606 | 607 | // 608 | // TransX and TransY from previous matrix 609 | // 610 | float transX = m[Matrix.MTRANS_X]; 611 | float transY = m[Matrix.MTRANS_Y]; 612 | 613 | // 614 | // Width 615 | // 616 | float prevActualWidth = prevMatchViewWidth * normalizedScale; 617 | float actualWidth = getImageWidth(); 618 | translateMatrixAfterRotate(Matrix.MTRANS_X, transX, prevActualWidth, actualWidth, prevViewWidth, viewWidth, drawableWidth); 619 | 620 | // 621 | // Height 622 | // 623 | float prevActualHeight = prevMatchViewHeight * normalizedScale; 624 | float actualHeight = getImageHeight(); 625 | translateMatrixAfterRotate(Matrix.MTRANS_Y, transY, prevActualHeight, actualHeight, prevViewHeight, viewHeight, drawableHeight); 626 | 627 | // 628 | // Set the matrix to the adjusted scale and translate values. 629 | // 630 | matrix.setValues(m); 631 | } 632 | fixTrans(); 633 | setImageMatrix(matrix); 634 | } 635 | 636 | /** 637 | * Set view dimensions based on layout params 638 | * 639 | * @param mode 640 | * @param size 641 | * @param drawableWidth 642 | * @return 643 | */ 644 | private int setViewSize(int mode, int size, int drawableWidth) { 645 | int viewSize; 646 | switch (mode) { 647 | case MeasureSpec.EXACTLY: 648 | viewSize = size; 649 | break; 650 | 651 | case MeasureSpec.AT_MOST: 652 | viewSize = Math.min(drawableWidth, size); 653 | break; 654 | 655 | case MeasureSpec.UNSPECIFIED: 656 | viewSize = drawableWidth; 657 | break; 658 | 659 | default: 660 | viewSize = size; 661 | break; 662 | } 663 | return viewSize; 664 | } 665 | 666 | /** 667 | * After rotating, the matrix needs to be translated. This function finds the area of image 668 | * which was previously centered and adjusts translations so that is again the center, post-rotation. 669 | * 670 | * @param axis Matrix.MTRANS_X or Matrix.MTRANS_Y 671 | * @param trans the value of trans in that axis before the rotation 672 | * @param prevImageSize the width/height of the image before the rotation 673 | * @param imageSize width/height of the image after rotation 674 | * @param prevViewSize width/height of view before rotation 675 | * @param viewSize width/height of view after rotation 676 | * @param drawableSize width/height of drawable 677 | */ 678 | private void translateMatrixAfterRotate(int axis, float trans, float prevImageSize, float imageSize, int prevViewSize, int viewSize, int drawableSize) { 679 | if (imageSize < viewSize) { 680 | // 681 | // The width/height of image is less than the view's width/height. Center it. 682 | // 683 | m[axis] = (viewSize - (drawableSize * m[Matrix.MSCALE_X])) * 0.5f; 684 | 685 | } else if (trans > 0) { 686 | // 687 | // The image is larger than the view, but was not before rotation. Center it. 688 | // 689 | m[axis] = -((imageSize - viewSize) * 0.5f); 690 | 691 | } else { 692 | // 693 | // Find the area of the image which was previously centered in the view. Determine its distance 694 | // from the left/top side of the view as a fraction of the entire image's width/height. Use that percentage 695 | // to calculate the trans in the new view width/height. 696 | // 697 | float percentage = (Math.abs(trans) + (0.5f * prevViewSize)) / prevImageSize; 698 | m[axis] = -((percentage * imageSize) - (viewSize * 0.5f)); 699 | } 700 | } 701 | 702 | private void setState(State state) { 703 | this.state = state; 704 | } 705 | 706 | public boolean canScrollHorizontallyFroyo(int direction) { 707 | return canScrollHorizontally(direction); 708 | } 709 | 710 | @Override 711 | public boolean canScrollHorizontally(int direction) { 712 | matrix.getValues(m); 713 | float x = m[Matrix.MTRANS_X]; 714 | 715 | if (getImageWidth() < viewWidth) { 716 | return false; 717 | 718 | } else if (x >= -1 && direction < 0) { 719 | return false; 720 | 721 | } else if (Math.abs(x) + viewWidth + 1 >= getImageWidth() && direction > 0) { 722 | return false; 723 | } 724 | 725 | return true; 726 | } 727 | 728 | /** 729 | * Gesture Listener detects a single click or long click and passes that on 730 | * to the view's listener. 731 | * @author Ortiz 732 | * 733 | */ 734 | private class GestureListener extends GestureDetector.SimpleOnGestureListener { 735 | 736 | @Override 737 | public boolean onSingleTapConfirmed(MotionEvent e) 738 | { 739 | if(doubleTapListener != null) { 740 | return doubleTapListener.onSingleTapConfirmed(e); 741 | } 742 | return performClick(); 743 | } 744 | 745 | @Override 746 | public void onLongPress(MotionEvent e) 747 | { 748 | performLongClick(); 749 | } 750 | 751 | @Override 752 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) 753 | { 754 | if (fling != null) { 755 | // 756 | // If a previous fling is still active, it should be cancelled so that two flings 757 | // are not run simultaenously. 758 | // 759 | fling.cancelFling(); 760 | } 761 | fling = new Fling((int) velocityX, (int) velocityY); 762 | compatPostOnAnimation(fling); 763 | return super.onFling(e1, e2, velocityX, velocityY); 764 | } 765 | 766 | @Override 767 | public boolean onDoubleTap(MotionEvent e) { 768 | boolean consumed = false; 769 | if(doubleTapListener != null) { 770 | consumed = doubleTapListener.onDoubleTap(e); 771 | } 772 | if (state == State.NONE) { 773 | float targetZoom = (normalizedScale == minScale) ? maxScale : minScale; 774 | DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, e.getX(), e.getY(), false); 775 | compatPostOnAnimation(doubleTap); 776 | consumed = true; 777 | } 778 | return consumed; 779 | } 780 | 781 | @Override 782 | public boolean onDoubleTapEvent(MotionEvent e) { 783 | if(doubleTapListener != null) { 784 | return doubleTapListener.onDoubleTapEvent(e); 785 | } 786 | return false; 787 | } 788 | } 789 | 790 | public interface OnTouchImageViewListener { 791 | public void onMove(); 792 | } 793 | 794 | /** 795 | * Responsible for all touch events. Handles the heavy lifting of drag and also sends 796 | * touch events to Scale Detector and Gesture Detector. 797 | * @author Ortiz 798 | * 799 | */ 800 | private class PrivateOnTouchListener implements OnTouchListener { 801 | 802 | // 803 | // Remember last point position for dragging 804 | // 805 | private PointF last = new PointF(); 806 | 807 | @Override 808 | public boolean onTouch(View v, MotionEvent event) { 809 | mScaleDetector.onTouchEvent(event); 810 | mGestureDetector.onTouchEvent(event); 811 | PointF curr = new PointF(event.getX(), event.getY()); 812 | 813 | if (state == State.NONE || state == State.DRAG || state == State.FLING) { 814 | switch (event.getAction()) { 815 | case MotionEvent.ACTION_DOWN: 816 | last.set(curr); 817 | if (fling != null) 818 | fling.cancelFling(); 819 | setState(State.DRAG); 820 | break; 821 | 822 | case MotionEvent.ACTION_MOVE: 823 | if (state == State.DRAG) { 824 | float deltaX = curr.x - last.x; 825 | float deltaY = curr.y - last.y; 826 | float fixTransX = getFixDragTrans(deltaX, viewWidth, getImageWidth()); 827 | float fixTransY = getFixDragTrans(deltaY, viewHeight, getImageHeight()); 828 | matrix.postTranslate(fixTransX, fixTransY); 829 | fixTrans(); 830 | last.set(curr.x, curr.y); 831 | } 832 | break; 833 | 834 | case MotionEvent.ACTION_UP: 835 | case MotionEvent.ACTION_POINTER_UP: 836 | setState(State.NONE); 837 | break; 838 | } 839 | } 840 | 841 | setImageMatrix(matrix); 842 | 843 | // 844 | // User-defined OnTouchListener 845 | // 846 | if(userTouchListener != null) { 847 | userTouchListener.onTouch(v, event); 848 | } 849 | 850 | // 851 | // OnTouchImageViewListener is set: TouchImageView dragged by user. 852 | // 853 | if (touchImageViewListener != null) { 854 | touchImageViewListener.onMove(); 855 | } 856 | 857 | // 858 | // indicate event was handled 859 | // 860 | return true; 861 | } 862 | } 863 | 864 | /** 865 | * ScaleListener detects user two finger scaling and scales image. 866 | * @author Ortiz 867 | * 868 | */ 869 | private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener { 870 | @Override 871 | public boolean onScaleBegin(ScaleGestureDetector detector) { 872 | setState(State.ZOOM); 873 | return true; 874 | } 875 | 876 | @Override 877 | public boolean onScale(ScaleGestureDetector detector) { 878 | scaleImage(detector.getScaleFactor(), detector.getFocusX(), detector.getFocusY(), true); 879 | 880 | // 881 | // OnTouchImageViewListener is set: TouchImageView pinch zoomed by user. 882 | // 883 | if (touchImageViewListener != null) { 884 | touchImageViewListener.onMove(); 885 | } 886 | return true; 887 | } 888 | 889 | @Override 890 | public void onScaleEnd(ScaleGestureDetector detector) { 891 | super.onScaleEnd(detector); 892 | setState(State.NONE); 893 | boolean animateToZoomBoundary = false; 894 | float targetZoom = normalizedScale; 895 | if (normalizedScale > maxScale) { 896 | targetZoom = maxScale; 897 | animateToZoomBoundary = true; 898 | 899 | } else if (normalizedScale < minScale) { 900 | targetZoom = minScale; 901 | animateToZoomBoundary = true; 902 | } 903 | 904 | if (animateToZoomBoundary) { 905 | DoubleTapZoom doubleTap = new DoubleTapZoom(targetZoom, viewWidth / 2, viewHeight / 2, true); 906 | compatPostOnAnimation(doubleTap); 907 | } 908 | } 909 | } 910 | 911 | private void scaleImage(double deltaScale, float focusX, float focusY, boolean stretchImageToSuper) { 912 | 913 | float lowerScale, upperScale; 914 | if (stretchImageToSuper) { 915 | lowerScale = superMinScale; 916 | upperScale = superMaxScale; 917 | 918 | } else { 919 | lowerScale = minScale; 920 | upperScale = maxScale; 921 | } 922 | 923 | float origScale = normalizedScale; 924 | normalizedScale *= deltaScale; 925 | if (normalizedScale > upperScale) { 926 | normalizedScale = upperScale; 927 | deltaScale = upperScale / origScale; 928 | } else if (normalizedScale < lowerScale) { 929 | normalizedScale = lowerScale; 930 | deltaScale = lowerScale / origScale; 931 | } 932 | 933 | matrix.postScale((float) deltaScale, (float) deltaScale, focusX, focusY); 934 | fixScaleTrans(); 935 | } 936 | 937 | /** 938 | * DoubleTapZoom calls a series of runnables which apply 939 | * an animated zoom in/out graphic to the image. 940 | * @author Ortiz 941 | * 942 | */ 943 | private class DoubleTapZoom implements Runnable { 944 | 945 | private long startTime; 946 | private static final float ZOOM_TIME = 500; 947 | private float startZoom, targetZoom; 948 | private float bitmapX, bitmapY; 949 | private boolean stretchImageToSuper; 950 | private AccelerateDecelerateInterpolator interpolator = new AccelerateDecelerateInterpolator(); 951 | private PointF startTouch; 952 | private PointF endTouch; 953 | 954 | DoubleTapZoom(float targetZoom, float focusX, float focusY, boolean stretchImageToSuper) { 955 | setState(State.ANIMATE_ZOOM); 956 | startTime = System.currentTimeMillis(); 957 | this.startZoom = normalizedScale; 958 | this.targetZoom = targetZoom; 959 | this.stretchImageToSuper = stretchImageToSuper; 960 | PointF bitmapPoint = transformCoordTouchToBitmap(focusX, focusY, false); 961 | this.bitmapX = bitmapPoint.x; 962 | this.bitmapY = bitmapPoint.y; 963 | 964 | // 965 | // Used for translating image during scaling 966 | // 967 | startTouch = transformCoordBitmapToTouch(bitmapX, bitmapY); 968 | endTouch = new PointF(viewWidth / 2, viewHeight / 2); 969 | } 970 | 971 | @Override 972 | public void run() { 973 | float t = interpolate(); 974 | double deltaScale = calculateDeltaScale(t); 975 | scaleImage(deltaScale, bitmapX, bitmapY, stretchImageToSuper); 976 | translateImageToCenterTouchPosition(t); 977 | fixScaleTrans(); 978 | setImageMatrix(matrix); 979 | 980 | // 981 | // OnTouchImageViewListener is set: double tap runnable updates listener 982 | // with every frame. 983 | // 984 | if (touchImageViewListener != null) { 985 | touchImageViewListener.onMove(); 986 | } 987 | 988 | if (t < 1f) { 989 | // 990 | // We haven't finished zooming 991 | // 992 | compatPostOnAnimation(this); 993 | 994 | } else { 995 | // 996 | // Finished zooming 997 | // 998 | setState(State.NONE); 999 | } 1000 | } 1001 | 1002 | /** 1003 | * Interpolate between where the image should start and end in order to translate 1004 | * the image so that the point that is touched is what ends up centered at the end 1005 | * of the zoom. 1006 | * @param t 1007 | */ 1008 | private void translateImageToCenterTouchPosition(float t) { 1009 | float targetX = startTouch.x + t * (endTouch.x - startTouch.x); 1010 | float targetY = startTouch.y + t * (endTouch.y - startTouch.y); 1011 | PointF curr = transformCoordBitmapToTouch(bitmapX, bitmapY); 1012 | matrix.postTranslate(targetX - curr.x, targetY - curr.y); 1013 | } 1014 | 1015 | /** 1016 | * Use interpolator to get t 1017 | * @return 1018 | */ 1019 | private float interpolate() { 1020 | long currTime = System.currentTimeMillis(); 1021 | float elapsed = (currTime - startTime) / ZOOM_TIME; 1022 | elapsed = Math.min(1f, elapsed); 1023 | return interpolator.getInterpolation(elapsed); 1024 | } 1025 | 1026 | /** 1027 | * Interpolate the current targeted zoom and get the delta 1028 | * from the current zoom. 1029 | * @param t 1030 | * @return 1031 | */ 1032 | private double calculateDeltaScale(float t) { 1033 | double zoom = startZoom + t * (targetZoom - startZoom); 1034 | return zoom / normalizedScale; 1035 | } 1036 | } 1037 | 1038 | /** 1039 | * This function will transform the coordinates in the touch event to the coordinate 1040 | * system of the drawable that the imageview contain 1041 | * @param x x-coordinate of touch event 1042 | * @param y y-coordinate of touch event 1043 | * @param clipToBitmap Touch event may occur within view, but outside image content. True, to clip return value 1044 | * to the bounds of the bitmap size. 1045 | * @return Coordinates of the point touched, in the coordinate system of the original drawable. 1046 | */ 1047 | private PointF transformCoordTouchToBitmap(float x, float y, boolean clipToBitmap) { 1048 | matrix.getValues(m); 1049 | float origW = getDrawable().getIntrinsicWidth(); 1050 | float origH = getDrawable().getIntrinsicHeight(); 1051 | float transX = m[Matrix.MTRANS_X]; 1052 | float transY = m[Matrix.MTRANS_Y]; 1053 | float finalX = ((x - transX) * origW) / getImageWidth(); 1054 | float finalY = ((y - transY) * origH) / getImageHeight(); 1055 | 1056 | if (clipToBitmap) { 1057 | finalX = Math.min(Math.max(finalX, 0), origW); 1058 | finalY = Math.min(Math.max(finalY, 0), origH); 1059 | } 1060 | 1061 | return new PointF(finalX , finalY); 1062 | } 1063 | 1064 | /** 1065 | * Inverse of transformCoordTouchToBitmap. This function will transform the coordinates in the 1066 | * drawable's coordinate system to the view's coordinate system. 1067 | * @param bx x-coordinate in original bitmap coordinate system 1068 | * @param by y-coordinate in original bitmap coordinate system 1069 | * @return Coordinates of the point in the view's coordinate system. 1070 | */ 1071 | private PointF transformCoordBitmapToTouch(float bx, float by) { 1072 | matrix.getValues(m); 1073 | float origW = getDrawable().getIntrinsicWidth(); 1074 | float origH = getDrawable().getIntrinsicHeight(); 1075 | float px = bx / origW; 1076 | float py = by / origH; 1077 | float finalX = m[Matrix.MTRANS_X] + getImageWidth() * px; 1078 | float finalY = m[Matrix.MTRANS_Y] + getImageHeight() * py; 1079 | return new PointF(finalX , finalY); 1080 | } 1081 | 1082 | /** 1083 | * Fling launches sequential runnables which apply 1084 | * the fling graphic to the image. The values for the translation 1085 | * are interpolated by the Scroller. 1086 | * @author Ortiz 1087 | * 1088 | */ 1089 | private class Fling implements Runnable { 1090 | 1091 | CompatScroller scroller; 1092 | int currX, currY; 1093 | 1094 | Fling(int velocityX, int velocityY) { 1095 | setState(State.FLING); 1096 | scroller = new CompatScroller(context); 1097 | matrix.getValues(m); 1098 | 1099 | int startX = (int) m[Matrix.MTRANS_X]; 1100 | int startY = (int) m[Matrix.MTRANS_Y]; 1101 | int minX, maxX, minY, maxY; 1102 | 1103 | if (getImageWidth() > viewWidth) { 1104 | minX = viewWidth - (int) getImageWidth(); 1105 | maxX = 0; 1106 | 1107 | } else { 1108 | minX = maxX = startX; 1109 | } 1110 | 1111 | if (getImageHeight() > viewHeight) { 1112 | minY = viewHeight - (int) getImageHeight(); 1113 | maxY = 0; 1114 | 1115 | } else { 1116 | minY = maxY = startY; 1117 | } 1118 | 1119 | scroller.fling(startX, startY, (int) velocityX, (int) velocityY, minX, 1120 | maxX, minY, maxY); 1121 | currX = startX; 1122 | currY = startY; 1123 | } 1124 | 1125 | public void cancelFling() { 1126 | if (scroller != null) { 1127 | setState(State.NONE); 1128 | scroller.forceFinished(true); 1129 | } 1130 | } 1131 | 1132 | @Override 1133 | public void run() { 1134 | 1135 | // 1136 | // OnTouchImageViewListener is set: TouchImageView listener has been flung by user. 1137 | // Listener runnable updated with each frame of fling animation. 1138 | // 1139 | if (touchImageViewListener != null) { 1140 | touchImageViewListener.onMove(); 1141 | } 1142 | 1143 | if (scroller.isFinished()) { 1144 | scroller = null; 1145 | return; 1146 | } 1147 | 1148 | if (scroller.computeScrollOffset()) { 1149 | int newX = scroller.getCurrX(); 1150 | int newY = scroller.getCurrY(); 1151 | int transX = newX - currX; 1152 | int transY = newY - currY; 1153 | currX = newX; 1154 | currY = newY; 1155 | matrix.postTranslate(transX, transY); 1156 | fixTrans(); 1157 | setImageMatrix(matrix); 1158 | compatPostOnAnimation(this); 1159 | } 1160 | } 1161 | } 1162 | 1163 | @TargetApi(VERSION_CODES.GINGERBREAD) 1164 | private class CompatScroller { 1165 | Scroller scroller; 1166 | OverScroller overScroller; 1167 | boolean isPreGingerbread; 1168 | 1169 | public CompatScroller(Context context) { 1170 | if (VERSION.SDK_INT < VERSION_CODES.GINGERBREAD) { 1171 | isPreGingerbread = true; 1172 | scroller = new Scroller(context); 1173 | 1174 | } else { 1175 | isPreGingerbread = false; 1176 | overScroller = new OverScroller(context); 1177 | } 1178 | } 1179 | 1180 | public void fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) { 1181 | if (isPreGingerbread) { 1182 | scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); 1183 | } else { 1184 | overScroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY); 1185 | } 1186 | } 1187 | 1188 | public void forceFinished(boolean finished) { 1189 | if (isPreGingerbread) { 1190 | scroller.forceFinished(finished); 1191 | } else { 1192 | overScroller.forceFinished(finished); 1193 | } 1194 | } 1195 | 1196 | public boolean isFinished() { 1197 | if (isPreGingerbread) { 1198 | return scroller.isFinished(); 1199 | } else { 1200 | return overScroller.isFinished(); 1201 | } 1202 | } 1203 | 1204 | public boolean computeScrollOffset() { 1205 | if (isPreGingerbread) { 1206 | return scroller.computeScrollOffset(); 1207 | } else { 1208 | overScroller.computeScrollOffset(); 1209 | return overScroller.computeScrollOffset(); 1210 | } 1211 | } 1212 | 1213 | public int getCurrX() { 1214 | if (isPreGingerbread) { 1215 | return scroller.getCurrX(); 1216 | } else { 1217 | return overScroller.getCurrX(); 1218 | } 1219 | } 1220 | 1221 | public int getCurrY() { 1222 | if (isPreGingerbread) { 1223 | return scroller.getCurrY(); 1224 | } else { 1225 | return overScroller.getCurrY(); 1226 | } 1227 | } 1228 | } 1229 | 1230 | @TargetApi(VERSION_CODES.JELLY_BEAN) 1231 | private void compatPostOnAnimation(Runnable runnable) { 1232 | if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN) { 1233 | postOnAnimation(runnable); 1234 | 1235 | } else { 1236 | postDelayed(runnable, 1000/60); 1237 | } 1238 | } 1239 | 1240 | private class ZoomVariables { 1241 | public float scale; 1242 | public float focusX; 1243 | public float focusY; 1244 | public ScaleType scaleType; 1245 | 1246 | public ZoomVariables(float scale, float focusX, float focusY, ScaleType scaleType) { 1247 | this.scale = scale; 1248 | this.focusX = focusX; 1249 | this.focusY = focusY; 1250 | this.scaleType = scaleType; 1251 | } 1252 | } 1253 | 1254 | private void printMatrixInfo() { 1255 | float[] n = new float[9]; 1256 | matrix.getValues(n); 1257 | Log.d(DEBUG, "Scale: " + n[Matrix.MSCALE_X] + " TransX: " + n[Matrix.MTRANS_X] + " TransY: " + n[Matrix.MTRANS_Y]); 1258 | } 1259 | } -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/res/layout/activity_pdf.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/res/layout/each_page.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | android-pdf-viewer 3 | 4 | -------------------------------------------------------------------------------- /android-pdf-viewer/src/test/java/com/github/pdfviewer/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.github.pdfviewer; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 28 5 | defaultConfig { 6 | applicationId "com.pdfrenderer" 7 | minSdkVersion 21 8 | targetSdkVersion 28 9 | versionCode 1 10 | versionName "1.1" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(include: ['*.jar'], dir: 'libs') 23 | implementation 'com.android.support:appcompat-v7:28.0.0-rc01' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | implementation 'com.squareup.retrofit2:retrofit:2.3.0' 29 | implementation project(':android-pdf-viewer') 30 | } 31 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/pdfrenderer/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.pdfrenderer; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.pdfrenderer", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/assets/sample.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manishkummar21/AndroidPdfViewer/dad3052caa55e2fa48a625bd4800e200f3354bba/app/src/main/assets/sample.pdf -------------------------------------------------------------------------------- /app/src/main/assets/sample101.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/manishkummar21/AndroidPdfViewer/dad3052caa55e2fa48a625bd4800e200f3354bba/app/src/main/assets/sample101.pdf -------------------------------------------------------------------------------- /app/src/main/java/com/pdfrenderer/PdfLoad.java: -------------------------------------------------------------------------------- 1 | package com.pdfrenderer; 2 | 3 | import android.content.Intent; 4 | import android.net.Uri; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AppCompatActivity; 8 | import android.view.View; 9 | import android.widget.Toast; 10 | 11 | import com.github.pdfviewer.PDFView; 12 | 13 | import java.io.BufferedInputStream; 14 | import java.io.File; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.text.SimpleDateFormat; 19 | import java.util.Date; 20 | import java.util.Locale; 21 | 22 | import okhttp3.ResponseBody; 23 | import retrofit2.Call; 24 | import retrofit2.Callback; 25 | import retrofit2.Response; 26 | import retrofit2.Retrofit; 27 | 28 | public class PdfLoad extends AppCompatActivity implements View.OnClickListener { 29 | 30 | private static final String FILENAME = "sample.pdf"; 31 | 32 | 33 | @Override 34 | protected void onCreate(@Nullable Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_main); 37 | findViewById(R.id.asset).setOnClickListener(this); 38 | findViewById(R.id.storage).setOnClickListener(this); 39 | findViewById(R.id.download).setOnClickListener(this); 40 | 41 | 42 | } 43 | 44 | private void Pickpdfstorage() { 45 | 46 | Intent intent = new Intent(); 47 | intent.setAction(Intent.ACTION_GET_CONTENT); 48 | intent.setType("application/pdf"); 49 | startActivityForResult(intent, 10); 50 | } 51 | 52 | private void OpenfileFromAsset() { 53 | File file = new File(getCacheDir(), FILENAME); 54 | if (!file.exists()) { 55 | 56 | try { 57 | InputStream asset = getAssets().open(FILENAME); 58 | FileOutputStream output = null; 59 | output = new FileOutputStream(file); 60 | final byte[] buffer = new byte[1024]; 61 | int size; 62 | while ((size = asset.read(buffer)) != -1) { 63 | output.write(buffer, 0, size); 64 | } 65 | asset.close(); 66 | output.close(); 67 | } catch (IOException e) { 68 | e.printStackTrace(); 69 | } 70 | 71 | } 72 | 73 | OpenPdfActivity(file.getAbsolutePath()); 74 | } 75 | 76 | private void OpenPdfActivity(String absolutePath) { 77 | 78 | PDFView.with(PdfLoad.this) 79 | .fromfilepath(absolutePath) 80 | .swipeHorizontal(false) 81 | .start(); 82 | } 83 | 84 | @Override 85 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { 86 | super.onActivityResult(requestCode, resultCode, data); 87 | switch (requestCode) { 88 | 89 | case 10: 90 | // Get the Uri of the selected file 91 | if (resultCode == RESULT_OK) { 92 | 93 | if (null != data.getData()) { 94 | 95 | Uri uri = data.getData(); 96 | File file; 97 | 98 | if (uri.getScheme().equals("content")) { 99 | 100 | file = new File(getCacheDir(), data.getData().getLastPathSegment()); 101 | 102 | try { 103 | InputStream iStream = getContentResolver().openInputStream(uri); 104 | FileOutputStream output = null; 105 | output = new FileOutputStream(file); 106 | final byte[] buffer = new byte[1024]; 107 | int size; 108 | while ((size = iStream.read(buffer)) != -1) { 109 | output.write(buffer, 0, size); 110 | } 111 | iStream.close(); 112 | output.close(); 113 | } catch (IOException e) { 114 | e.printStackTrace(); 115 | } 116 | } else 117 | file = new File(uri.getPath()); 118 | 119 | 120 | OpenPdfActivity(file.getAbsolutePath()); 121 | } 122 | } 123 | break; 124 | } 125 | } 126 | 127 | @Override 128 | public void onClick(View view) { 129 | 130 | switch (view.getId()) { 131 | case R.id.asset: 132 | OpenfileFromAsset(); 133 | break; 134 | case R.id.storage: 135 | Pickpdfstorage(); 136 | break; 137 | case R.id.download: 138 | initDownload(); 139 | break; 140 | } 141 | } 142 | 143 | 144 | private void initDownload() { 145 | 146 | Retrofit retrofit = new Retrofit.Builder() 147 | .baseUrl("https://www.antennahouse.com/") 148 | .build(); 149 | 150 | RetrofitInterface retrofitInterface = retrofit.create(RetrofitInterface.class); 151 | 152 | Call request = retrofitInterface.downloadFile(); 153 | request.enqueue(new Callback() { 154 | @Override 155 | public void onResponse(Call call, Response response) { 156 | 157 | try { 158 | ResponseBody body = response.body(); 159 | 160 | InputStream iStream = new BufferedInputStream(body.byteStream(), 1024 * 8); 161 | 162 | //creating file name dynamically 163 | SimpleDateFormat formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.US); 164 | Date now = new Date(); 165 | String fileName = formatter.format(now) + ".pdf"; 166 | 167 | File file = new File(getCacheDir(), fileName); 168 | FileOutputStream output = null; 169 | output = new FileOutputStream(file); 170 | final byte[] buffer = new byte[1024]; 171 | int size; 172 | while ((size = iStream.read(buffer)) != -1) { 173 | output.write(buffer, 0, size); 174 | } 175 | iStream.close(); 176 | output.close(); 177 | 178 | OpenPdfActivity(file.getAbsolutePath()); 179 | 180 | 181 | } catch (IOException e) { 182 | 183 | e.printStackTrace(); 184 | Toast.makeText(getApplicationContext(), e.getMessage(), Toast.LENGTH_SHORT).show(); 185 | 186 | } 187 | } 188 | 189 | @Override 190 | public void onFailure(Call call, Throwable t) { 191 | System.out.println("testing"); 192 | 193 | } 194 | }); 195 | 196 | } 197 | 198 | } 199 | -------------------------------------------------------------------------------- /app/src/main/java/com/pdfrenderer/RetrofitInterface.java: -------------------------------------------------------------------------------- 1 | package com.pdfrenderer; 2 | 3 | import okhttp3.ResponseBody; 4 | import retrofit2.Call; 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Streaming; 7 | 8 | public interface RetrofitInterface { 9 | 10 | @GET("XSLsample/pdf/sample-link_1.pdf") 11 | @Streaming 12 | Call downloadFile(); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 |