From 15adb86de9c42d9b4d9737a6b60d9508b64c8ca6 Mon Sep 17 00:00:00 2001 From: Robin Date: Sat, 21 Jul 2018 20:55:41 +0200 Subject: [PATCH 1/4] Add initial state saving. Does not work properly for configuration change --- .../github/barteksc/pdfviewer/PDFView.java | 174 +++++++++++++++++- 1 file changed, 173 insertions(+), 1 deletion(-) diff --git a/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java b/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java index 5e9636e7..bd828f52 100644 --- a/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java +++ b/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java @@ -31,6 +31,9 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.HandlerThread; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; import android.util.AttributeSet; import android.util.Log; import android.widget.RelativeLayout; @@ -68,6 +71,7 @@ import java.io.File; import java.io.InputStream; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -124,6 +128,9 @@ enum ScrollDir { PdfFile pdfFile; + private PdfViewState restoredState; + private boolean hasRestoredFromState; + /** The index of the current sequence */ private int currentPage; @@ -261,6 +268,21 @@ public PDFView(Context context, AttributeSet set) { setWillNotDraw(false); } + @Nullable + @Override + protected Parcelable onSaveInstanceState() { + SavedState state = new SavedState(super.onSaveInstanceState()); + state.viewState = getCurrentViewState(); + return state; + } + + @Override + protected void onRestoreInstanceState(Parcelable parcelable) { + SavedState state = (SavedState) parcelable; + restoredState = state.viewState; + super.onRestoreInstanceState(state.getSuperState()); + } + private void load(DocumentSource docSource, String password) { load(docSource, password, null); } @@ -277,6 +299,52 @@ private void load(DocumentSource docSource, String password, int[] userPages) { decodingAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + /** + * Gets the current position and zoom state of the view. + * This state can be saved or persisted and later restored with {@link #restoreViewState(PdfViewState)} + */ + public PdfViewState getCurrentViewState() { + return new PdfViewState(this); + } + + /** + *

Restores a view state previously gotten with {@link #getCurrentViewState()} or + * {@link #onSaveInstanceState()}.

+ *

The view handles restoring state with onSaveInstanceState for configuration changes or + * activity destruction, but the host application has to make sure the same pdf is loaded. + * This view has no way of knowing if the pdf loaded after restoring is the same as before. + * If the pdf content changed or a different pdf is loaded, it may restore to a incorrect position. + * To disable the automatic restoration call {@link #setSaveEnabled(boolean)}.

+ *

Note: This must be called after a pdf file has been loaded. A good place to call this is + * in the loadComplete callback.

+ * @throws IllegalStateException If pdf file is not loaded + */ + public void restoreViewState(PdfViewState state) { + if (pdfFile == null) { + throw new IllegalStateException("Pdf file has not been loaded. Call restoreViewState from the loadCompleted callback."); + } + zoom = state.zoom; + int maxPageCount = pdfFile.getPagesCount() - 1; + currentPage = Math.min(maxPageCount, state.currentPage); + // since view and page sizes might have changed in a different configuration we cant just restore x/y offsets + if (swipeVertical) { + float maxXOffset = toCurrentScale(pdfFile.getMaxPageWidth()) - getWidth(); + currentXOffset = Math.min(maxXOffset, state.offsetX); + float newYOffset = pdfFile.getPageOffset(currentPage, zoom) - state.offsetY; + float maxYOffset = pdfFile.getDocLen(zoom) - getHeight(); + currentYOffset = -Math.min(maxYOffset, newYOffset); + } else { + float newXOffset = pdfFile.getPageOffset(currentPage, zoom) - state.offsetX; + float maxXOffset = pdfFile.getDocLen(zoom) - getWidth(); + currentXOffset = -Math.min(maxXOffset, newXOffset); + float maxYOffset = toCurrentScale(pdfFile.getMaxPageHeight()) - getHeight(); + currentYOffset = Math.min(maxYOffset, state.offsetY); + } + + showPage(currentPage); + hasRestoredFromState = true; + } + /** * Go to the given page. * @@ -735,9 +803,17 @@ void loadComplete(PdfFile pdfFile) { dragPinchManager.enable(); + hasRestoredFromState = false; callbacks.callOnLoadComplete(pdfFile.getPagesCount()); - jumpTo(defaultPage, false); + if (!hasRestoredFromState && restoredState != null) { + restoreViewState(restoredState); + restoredState = null; + } + + if (!hasRestoredFromState) { + jumpTo(defaultPage, false); + } } void loadError(Throwable t) { @@ -1280,6 +1356,7 @@ public Configurator fromSource(DocumentSource docSource) { private enum State {DEFAULT, LOADED, SHOWN, ERROR} + @SuppressWarnings("unused") public class Configurator { private final DocumentSource documentSource; @@ -1508,4 +1585,99 @@ public void load() { } } } + + /** + * Holds the zoom and position state for PdfView + */ + public static class PdfViewState implements Serializable, Parcelable { + + int currentPage; + float zoom = 1f; + float offsetX; + float offsetY; + + public PdfViewState() { + } + + public PdfViewState(PDFView view) { + currentPage = view.getCurrentPage(); + zoom = view.getZoom(); + offsetX = view.getCurrentXOffset(); + offsetY = view.getCurrentYOffset(); + if (view.pdfFile != null) { + float pageOffset = view.pdfFile.getPageOffset(currentPage, zoom); + if (view.swipeVertical) { + offsetY += pageOffset; + } else { + offsetX += pageOffset; + } + } + } + + private PdfViewState(Parcel in) { + currentPage = in.readInt(); + zoom = in.readFloat(); + offsetX = in.readFloat(); + offsetY = in.readFloat(); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(currentPage); + out.writeFloat(zoom); + out.writeFloat(offsetX); + out.writeFloat(offsetY); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public PdfViewState createFromParcel(Parcel in) { + return new PdfViewState(in); + } + + public PdfViewState[] newArray(int size) { + return new PdfViewState[size]; + } + }; + } + + /** + * Wrapper around {@link PdfViewState} to not leak super state in public API. + */ + static class SavedState extends BaseSavedState { + + PdfViewState viewState; + + public SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + viewState = in.readParcelable(getClass().getClassLoader()); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeParcelable(viewState, 0); + } + + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + + } } From 9b02875ff0aced06f8a9011ed37ce91ec62fe225 Mon Sep 17 00:00:00 2001 From: Robin Date: Sun, 29 Jul 2018 16:38:19 +0200 Subject: [PATCH 2/4] Save position as fractions. Finish state-saving api. --- .../github/barteksc/pdfviewer/PDFView.java | 94 +++++++++++-------- 1 file changed, 57 insertions(+), 37 deletions(-) diff --git a/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java b/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java index bd828f52..a2cdbd73 100644 --- a/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java +++ b/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java @@ -288,7 +288,6 @@ private void load(DocumentSource docSource, String password) { } private void load(DocumentSource docSource, String password, int[] userPages) { - if (!recycled) { throw new IllegalStateException("Don't call load on a PDF View without recycling it first."); } @@ -301,10 +300,23 @@ private void load(DocumentSource docSource, String password, int[] userPages) { /** * Gets the current position and zoom state of the view. - * This state can be saved or persisted and later restored with {@link #restoreViewState(PdfViewState)} + * This state can be saved or persisted and later restored with {@link #restoreViewState(PdfViewState)}. */ public PdfViewState getCurrentViewState() { - return new PdfViewState(this); + PdfViewState state = new PdfViewState(); + state.zoom = getZoom(); + state.currentPage = getCurrentPage(); + float centerX = -currentXOffset + (getWidth() / 2f); + float centerY = -currentYOffset + (getHeight() / 2f); + SizeF pageSize = pdfFile.getScaledPageSize(state.currentPage, state.zoom); + if (swipeVertical) { + state.pageFocusX = (centerX - pdfFile.getSecondaryPageOffset(state.currentPage, zoom)) / pageSize.getWidth(); + state.pageFocusY = (centerY - pdfFile.getPageOffset(state.currentPage, zoom)) / pageSize.getHeight(); + } else { + state.pageFocusX = (centerX - pdfFile.getPageOffset(state.currentPage, zoom)) / pageSize.getWidth(); + state.pageFocusY = (centerY - pdfFile.getSecondaryPageOffset(state.currentPage, zoom)) / pageSize.getHeight(); + } + return state; } /** @@ -314,9 +326,9 @@ public PdfViewState getCurrentViewState() { * activity destruction, but the host application has to make sure the same pdf is loaded. * This view has no way of knowing if the pdf loaded after restoring is the same as before. * If the pdf content changed or a different pdf is loaded, it may restore to a incorrect position. - * To disable the automatic restoration call {@link #setSaveEnabled(boolean)}.

+ * To disable the automatic restoration use {@link #setSaveEnabled(boolean)}.

*

Note: This must be called after a pdf file has been loaded. A good place to call this is - * in the loadComplete callback.

+ * in the {@link OnLoadCompleteListener} callback.

* @throws IllegalStateException If pdf file is not loaded */ public void restoreViewState(PdfViewState state) { @@ -326,22 +338,29 @@ public void restoreViewState(PdfViewState state) { zoom = state.zoom; int maxPageCount = pdfFile.getPagesCount() - 1; currentPage = Math.min(maxPageCount, state.currentPage); - // since view and page sizes might have changed in a different configuration we cant just restore x/y offsets + + SizeF pageSize = pdfFile.getScaledPageSize(currentPage, zoom); + float pageX = pageSize.getWidth() * state.pageFocusX; + float pageY = pageSize.getHeight() * state.pageFocusY; + float mainOffset = pdfFile.getPageOffset(currentPage, zoom); + float secondaryOffset = pdfFile.getSecondaryPageOffset(currentPage, zoom); if (swipeVertical) { + float newXOffset = secondaryOffset + pageX - (getWidth() / 2f); float maxXOffset = toCurrentScale(pdfFile.getMaxPageWidth()) - getWidth(); - currentXOffset = Math.min(maxXOffset, state.offsetX); - float newYOffset = pdfFile.getPageOffset(currentPage, zoom) - state.offsetY; + currentXOffset = -MathUtils.limit(newXOffset, 0, maxXOffset); + float newYOffset = mainOffset + pageY - (getHeight() / 2f); float maxYOffset = pdfFile.getDocLen(zoom) - getHeight(); - currentYOffset = -Math.min(maxYOffset, newYOffset); + currentYOffset = -MathUtils.limit(newYOffset, 0, maxYOffset); } else { - float newXOffset = pdfFile.getPageOffset(currentPage, zoom) - state.offsetX; + float newXOffset = mainOffset + pageX - (getWidth() / 2f); float maxXOffset = pdfFile.getDocLen(zoom) - getWidth(); - currentXOffset = -Math.min(maxXOffset, newXOffset); + currentXOffset = -MathUtils.limit(newXOffset, 0, maxXOffset); + float newYOffset = secondaryOffset + pageY - (getHeight() / 2f); float maxYOffset = toCurrentScale(pdfFile.getMaxPageHeight()) - getHeight(); - currentYOffset = Math.min(maxYOffset, state.offsetY); + currentYOffset = -MathUtils.limit(newYOffset, 0, maxYOffset); } - showPage(currentPage); + performPageSnap(false); hasRestoredFromState = true; } @@ -973,6 +992,13 @@ void loadPageByOffset() { * Animate to the nearest snapping position for the current SnapPolicy */ public void performPageSnap() { + performPageSnap(true); + } + + /** + * Snap to the nearest snapping position for the current SnapPolicy + */ + public void performPageSnap(boolean animated) { if (!pageSnap || pdfFile == null || pdfFile.getPagesCount() == 0) { return; } @@ -983,10 +1009,18 @@ public void performPageSnap() { } float offset = snapOffsetForPage(centerPage, edge); - if (swipeVertical) { - animationManager.startYAnimation(currentYOffset, -offset); + if (animated) { + if (swipeVertical) { + animationManager.startYAnimation(currentYOffset, -offset); + } else { + animationManager.startXAnimation(currentXOffset, -offset); + } } else { - animationManager.startXAnimation(currentXOffset, -offset); + if (swipeVertical) { + moveTo(currentXOffset, -offset); + } else { + moveTo(-offset, currentYOffset); + } } } @@ -1593,40 +1627,26 @@ public static class PdfViewState implements Serializable, Parcelable { int currentPage; float zoom = 1f; - float offsetX; - float offsetY; + // relative point of the page (0 to 1) that is in the center of the view + float pageFocusX; + float pageFocusY; public PdfViewState() { } - public PdfViewState(PDFView view) { - currentPage = view.getCurrentPage(); - zoom = view.getZoom(); - offsetX = view.getCurrentXOffset(); - offsetY = view.getCurrentYOffset(); - if (view.pdfFile != null) { - float pageOffset = view.pdfFile.getPageOffset(currentPage, zoom); - if (view.swipeVertical) { - offsetY += pageOffset; - } else { - offsetX += pageOffset; - } - } - } - private PdfViewState(Parcel in) { currentPage = in.readInt(); zoom = in.readFloat(); - offsetX = in.readFloat(); - offsetY = in.readFloat(); + pageFocusX = in.readFloat(); + pageFocusY = in.readFloat(); } @Override public void writeToParcel(Parcel out, int flags) { out.writeInt(currentPage); out.writeFloat(zoom); - out.writeFloat(offsetX); - out.writeFloat(offsetY); + out.writeFloat(pageFocusX); + out.writeFloat(pageFocusY); } @Override From d0702f301996696f8a70e2d4c0ba6d9612f787cc Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 13 Aug 2018 21:08:02 +0200 Subject: [PATCH 3/4] Fix position offset and handle null pdf file --- .../github/barteksc/pdfviewer/PDFView.java | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java b/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java index a2cdbd73..be4db996 100644 --- a/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java +++ b/android-pdf-viewer/src/main/java/com/github/barteksc/pdfviewer/PDFView.java @@ -272,7 +272,9 @@ public PDFView(Context context, AttributeSet set) { @Override protected Parcelable onSaveInstanceState() { SavedState state = new SavedState(super.onSaveInstanceState()); - state.viewState = getCurrentViewState(); + if (pdfFile != null) { + state.viewState = getCurrentViewState(); + } return state; } @@ -303,6 +305,9 @@ private void load(DocumentSource docSource, String password, int[] userPages) { * This state can be saved or persisted and later restored with {@link #restoreViewState(PdfViewState)}. */ public PdfViewState getCurrentViewState() { + if (pdfFile == null) { + throw new IllegalStateException("Failed to save current view state (no file loaded)"); + } PdfViewState state = new PdfViewState(); state.zoom = getZoom(); state.currentPage = getCurrentPage(); @@ -344,21 +349,15 @@ public void restoreViewState(PdfViewState state) { float pageY = pageSize.getHeight() * state.pageFocusY; float mainOffset = pdfFile.getPageOffset(currentPage, zoom); float secondaryOffset = pdfFile.getSecondaryPageOffset(currentPage, zoom); + float x, y; if (swipeVertical) { - float newXOffset = secondaryOffset + pageX - (getWidth() / 2f); - float maxXOffset = toCurrentScale(pdfFile.getMaxPageWidth()) - getWidth(); - currentXOffset = -MathUtils.limit(newXOffset, 0, maxXOffset); - float newYOffset = mainOffset + pageY - (getHeight() / 2f); - float maxYOffset = pdfFile.getDocLen(zoom) - getHeight(); - currentYOffset = -MathUtils.limit(newYOffset, 0, maxYOffset); + x = secondaryOffset + pageX - (getWidth() / 2f); + y = mainOffset + pageY - (getHeight() / 2f); } else { - float newXOffset = mainOffset + pageX - (getWidth() / 2f); - float maxXOffset = pdfFile.getDocLen(zoom) - getWidth(); - currentXOffset = -MathUtils.limit(newXOffset, 0, maxXOffset); - float newYOffset = secondaryOffset + pageY - (getHeight() / 2f); - float maxYOffset = toCurrentScale(pdfFile.getMaxPageHeight()) - getHeight(); - currentYOffset = -MathUtils.limit(newYOffset, 0, maxYOffset); + x = mainOffset + pageX - (getWidth() / 2f); + y = secondaryOffset + pageY - (getHeight() / 2f); } + moveTo(-x, -y); showPage(currentPage); performPageSnap(false); hasRestoredFromState = true; @@ -1671,7 +1670,7 @@ public PdfViewState[] newArray(int size) { */ static class SavedState extends BaseSavedState { - PdfViewState viewState; + @Nullable PdfViewState viewState; public SavedState(Parcelable superState) { super(superState); @@ -1679,13 +1678,18 @@ public SavedState(Parcelable superState) { private SavedState(Parcel in) { super(in); - viewState = in.readParcelable(getClass().getClassLoader()); + if (in.readByte() == 1) { + viewState = in.readParcelable(getClass().getClassLoader()); + } } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); - out.writeParcelable(viewState, 0); + out.writeByte((byte) (viewState == null ? 0 : 1)); + if (viewState != null) { + out.writeParcelable(viewState, 0); + } } public static final Parcelable.Creator CREATOR = From e3d3be1ad25254fefcc36bd17032e579021b6dbb Mon Sep 17 00:00:00 2001 From: Robin Date: Mon, 13 Aug 2018 21:08:58 +0200 Subject: [PATCH 4/4] Remove unnecessary page restoration from sample activity --- .../java/com/github/barteksc/sample/PDFViewActivity.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sample/src/main/java/com/github/barteksc/sample/PDFViewActivity.java b/sample/src/main/java/com/github/barteksc/sample/PDFViewActivity.java index d23b0008..75c65cee 100755 --- a/sample/src/main/java/com/github/barteksc/sample/PDFViewActivity.java +++ b/sample/src/main/java/com/github/barteksc/sample/PDFViewActivity.java @@ -66,9 +66,6 @@ public class PDFViewActivity extends AppCompatActivity implements OnPageChangeLi @NonConfigurationInstance Uri uri; - @NonConfigurationInstance - Integer pageNumber = 0; - String pdfFileName; @OptionsItem(R.id.pickFile) @@ -115,7 +112,6 @@ private void displayFromAsset(String assetFileName) { pdfFileName = assetFileName; pdfView.fromAsset(SAMPLE_FILE) - .defaultPage(pageNumber) .onPageChange(this) .enableAnnotationRendering(true) .onLoad(this) @@ -130,7 +126,6 @@ private void displayFromUri(Uri uri) { pdfFileName = getFileName(uri); pdfView.fromUri(uri) - .defaultPage(pageNumber) .onPageChange(this) .enableAnnotationRendering(true) .onLoad(this) @@ -150,7 +145,6 @@ public void onResult(int resultCode, Intent intent) { @Override public void onPageChanged(int page, int pageCount) { - pageNumber = page; setTitle(String.format("%s %s / %s", pdfFileName, page + 1, pageCount)); }