Android Open Source - android-pdfview P D F View






From Project

Back to project page android-pdfview.

License

The source code is released under:

GNU General Public License

If you think the Android project android-pdfview listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/**
 * Copyright 2014 Joan Zapata/*from   w ww  .j  a  va2s  .c o m*/
 *
 * This file is part of Android-pdfview.
 *
 * Android-pdfview is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Android-pdfview is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Android-pdfview.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.joanzapata.pdfview;

import android.content.Context;
import android.graphics.*;
import android.graphics.Paint.Style;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.view.SurfaceView;
import com.joanzapata.pdfview.exception.FileNotFoundException;
import com.joanzapata.pdfview.listener.OnDrawListener;
import com.joanzapata.pdfview.listener.OnLoadCompleteListener;
import com.joanzapata.pdfview.listener.OnPageChangeListener;
import com.joanzapata.pdfview.model.PagePart;
import com.joanzapata.pdfview.util.ArrayUtils;
import com.joanzapata.pdfview.util.Constants;
import com.joanzapata.pdfview.util.FileUtils;
import com.joanzapata.pdfview.util.NumberUtils;
import org.vudroid.core.DecodeService;

import java.io.File;
import java.io.IOException;

import static com.joanzapata.pdfview.util.Constants.Cache.CACHE_SIZE;

/**
 * @author Joan Zapata
 *         <p/>
 *         It supports animations, zoom, cache, and swipe.
 *         <p/>
 *         To fully understand this class you must know its principles :
 *         - The PDF document is seen as if we always want to draw all the pages.
 *         - The thing is that we only draw the visible parts.
 *         - All parts are the same size, this is because we can't interrupt a native page rendering,
 *         so we need these renderings to be as fast as possible, and be able to interrupt them
 *         as soon as we can.
 *         - The parts are loaded when the current offset or the current zoom level changes
 *         <p/>
 *         Important :
 *         - DocumentPage = A page of the PDF document.
 *         - UserPage = A page as defined by the user.
 *         By default, they're the same. But the user can change the pages order
 *         using {@link #load(Uri, OnLoadCompleteListener, int[])}. In this
 *         particular case, a userPage of 5 can refer to a documentPage of 17.
 */
public class PDFView extends SurfaceView {

    private static final String TAG = PDFView.class.getSimpleName();

    /** Rendered parts go to the cache manager */
    private CacheManager cacheManager;

    /** Animation manager manage all offset and zoom animation */
    private AnimationManager animationManager;

    /** Drag manager manage all touch events */
    private DragPinchManager dragPinchManager;

    /**
     * The pages the user want to display in order
     * (ex: 0, 2, 2, 8, 8, 1, 1, 1)
     */
    private int[] originalUserPages;

    /**
     * The same pages but with a filter to avoid repetition
     * (ex: 0, 2, 8, 1)
     */
    private int[] filteredUserPages;

    /**
     * The same pages but with a filter to avoid repetition
     * (ex: 0, 1, 1, 2, 2, 3, 3, 3)
     */
    private int[] filteredUserPageIndexes;

    /** Number of pages in the loaded PDF document */
    private int documentPageCount;

    /** The index of the current sequence */
    private int currentPage;

    /** The index of the current sequence */
    private int currentFilteredPage;

    /** The actual width and height of the pages in the PDF document */
    private int pageWidth, pageHeight;

    /** The optimal width and height of the pages to fit the component size */
    private float optimalPageWidth, optimalPageHeight;

    /**
     * If you picture all the pages side by side in their optimal width,
     * and taking into account the zoom level, the current offset is the
     * position of the left border of the screen in this big picture
     */
    private float currentXOffset = 0;

    /**
     * If you picture all the pages side by side in their optimal width,
     * and taking into account the zoom level, the current offset is the
     * position of the left border of the screen in this big picture
     */
    private float currentYOffset = 0;

    /** The zoom level, always >= 1 */
    private float zoom = 1f;

    /** Coordinates of the left mask on the screen */
    private RectF leftMask;

    /** Coordinates of the right mask on the screen */
    private RectF rightMask;

    /** True if the PDFView has been recycled */
    private boolean recycled = true;

    /** Current state of the view */
    private State state = State.DEFAULT;

    /** The VuDroid DecodeService used for decoding PDF and pages */
    private DecodeService decodeService;

    /** Async task used during the loading phase to decode a PDF document */
    private DecodingAsyncTask decodingAsyncTask;

    /** Async task always playing in the background and proceeding rendering tasks */
    private RenderingAsyncTask renderingAsyncTask;

    /** Call back object to call when the PDF is loaded */
    private OnLoadCompleteListener onLoadCompleteListener;

    /** Call back object to call when the page has changed */
    private OnPageChangeListener onPageChangeListener;

    /** Call back object to call when the above layer is to drawn */
    private OnDrawListener onDrawListener;

    /** Paint object for drawing */
    private Paint paint;

    /** Paint object for drawing mask */
    private Paint maskPaint;

    /** Paint object for drawing debug stuff */
    private Paint debugPaint;

    /** Paint object for minimap background */
    private Paint paintMinimapBack;

    private Paint paintMinimapFront;

    /** True if should draw map on the top right corner */
    private boolean miniMapRequired;

    /** Bounds of the minimap */
    private RectF minimapBounds;

    /** Bounds of the minimap */
    private RectF minimapScreenBounds;

    private int defaultPage = 0;

    private boolean userWantsMinimap = false;

    /** Construct the initial view */
    public PDFView(Context context, AttributeSet set) {
        super(context, set);
        miniMapRequired = false;
        cacheManager = new CacheManager();
        animationManager = new AnimationManager(this);
        dragPinchManager = new DragPinchManager(this);

        paint = new Paint();
        debugPaint = new Paint();
        debugPaint.setStyle(Style.STROKE);
        maskPaint = new Paint();
        maskPaint.setColor(Color.BLACK);
        maskPaint.setAlpha(Constants.MASK_ALPHA);
        paintMinimapBack = new Paint();
        paintMinimapBack.setStyle(Style.FILL);
        paintMinimapBack.setColor(Color.BLACK);
        paintMinimapBack.setAlpha(50);
        paintMinimapFront = new Paint();
        paintMinimapFront.setStyle(Style.FILL);
        paintMinimapFront.setColor(Color.BLACK);
        paintMinimapFront.setAlpha(50);

        // A surface view does not call
        // onDraw() as a default but we need it.
        setWillNotDraw(false);
    }

    private void load(Uri uri, OnLoadCompleteListener listener) {
        load(uri, listener, null);
    }

    private void load(Uri uri, OnLoadCompleteListener onLoadCompleteListener, int[] userPages) {

        if (!recycled) {
            throw new IllegalStateException("Don't call load on a PDF View without recycling it first.");
        }

        // Manage UserPages if not null
        if (userPages != null) {
            this.originalUserPages = userPages;
            this.filteredUserPages = ArrayUtils.deleteDuplicatedPages(originalUserPages);
            this.filteredUserPageIndexes = ArrayUtils.calculateIndexesInDuplicateArray(originalUserPages);
        }

        this.onLoadCompleteListener = onLoadCompleteListener;

        // Start decoding document
        decodingAsyncTask = new DecodingAsyncTask(uri, this);
        decodingAsyncTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

        renderingAsyncTask = new RenderingAsyncTask(this);
        renderingAsyncTask.execute();
    }

    /**
     * Go to the given page.
     * @param page Page number starting from 1.
     */
    public void jumpTo(int page) {
        showPage(page - 1);
    }

    void showPage(int pageNb) {
        state = State.SHOWN;

        // Check the page number and makes the
        // difference between UserPages and DocumentPages
        pageNb = determineValidPageNumberFrom(pageNb);
        currentPage = pageNb;
        currentFilteredPage = pageNb;
        if (filteredUserPageIndexes != null) {
            if (pageNb >= 0 && pageNb < filteredUserPageIndexes.length) {
                pageNb = filteredUserPageIndexes[pageNb];
                currentFilteredPage = pageNb;
            }
        }

        // Reset the zoom and center the page on the screen
        resetZoom();
        animationManager.startXAnimation(currentXOffset, calculateCenterOffsetForPage(pageNb));
        loadPages();

        if (onPageChangeListener != null) {
            onPageChangeListener.onPageChanged(currentPage + 1, getPageCount());
        }
    }

    public int getPageCount() {
        if (originalUserPages != null) {
            return originalUserPages.length;
        }
        return documentPageCount;
    }

    public void enableSwipe(boolean enableSwipe) {
        dragPinchManager.setSwipeEnabled(enableSwipe);
    }

    private void setOnPageChangeListener(OnPageChangeListener onPageChangeListener) {
        this.onPageChangeListener = onPageChangeListener;
    }

    private void setOnDrawListener(OnDrawListener onDrawListener) {
        this.onDrawListener = onDrawListener;
    }

    public void recycle() {

        // Stop tasks
        if (renderingAsyncTask != null) {
            renderingAsyncTask.cancel(true);
        }
        if (decodingAsyncTask != null) {
            decodingAsyncTask.cancel(true);
        }

        // Clear caches
        cacheManager.recycle();

        recycled = true;
        state = State.DEFAULT;
    }

    @Override
    protected void onDetachedFromWindow() {
        recycle();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        animationManager.stopAll();
        calculateOptimalWidthAndHeight();
        loadPages();
        moveTo(calculateCenterOffsetForPage(currentFilteredPage), currentYOffset);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // As I said in this class javadoc, we can think of this canvas as a huge
        // strip on which we draw all the images. We actually only draw the rendered
        // parts, of course, but we render them in the place they belong in this huge
        // strip.

        // That's where Canvas.translate(x, y) becomes very helpful.
        // This is the situation :
        //  _______________________________________________
        // |          |                    |
        // | the actual  |          The big strip  |
        // |  canvas   |                    |
        // |_____________|                   |
        // |_______________________________________________|
        //
        // If the rendered part is on the bottom right corner of the strip
        // we can draw it but we won't see it because the canvas is not big enough.

        // But if we call translate(-X, -Y) on the canvas just before drawing the object :
        //  _______________________________________________
        // |                       _____________|
        // |   The big strip            |         |
        // |                   |  the actual |
        // |                 |  canvas     |
        // |_________________________________|_____________|
        //
        // The object will be on the canvas.
        // This technique is massively used in this method, and allows
        // abstraction of the screen position when rendering the parts.

        // Draws background
        canvas.drawColor(Color.WHITE);

        if (state != State.SHOWN) {
            return;
        }

        // Moves the canvas before drawing any element
        float currentXOffset = this.currentXOffset;
        float currentYOffset = this.currentYOffset;
        canvas.translate(currentXOffset, currentYOffset);

        // Draws thumb nails
        for (PagePart part : cacheManager.getThumbnails()) {
            drawPart(canvas, part);
        }

        // Draws parts
        for (PagePart part : cacheManager.getPageParts()) {
            drawPart(canvas, part);
        }

        // Draws the user layer
        if (onDrawListener != null) {
            canvas.translate(toCurrentScale(currentFilteredPage * optimalPageWidth), 0);

            onDrawListener.onLayerDrawn(canvas, //
                    toCurrentScale(optimalPageWidth), //
                    toCurrentScale(optimalPageHeight),
                    currentPage);

            canvas.translate(-toCurrentScale(currentFilteredPage * optimalPageWidth), 0);
        }

        // Restores the canvas position
        canvas.translate(-currentXOffset, -currentYOffset);

        // Draws mask around current page
        canvas.drawRect(leftMask, maskPaint);
        canvas.drawRect(rightMask, maskPaint);

        // If minimap shown draws it
        if (userWantsMinimap && miniMapRequired) {
            drawMiniMap(canvas);
        }
    }

    public void onLayerUpdate() {
        invalidate();
    }

    /** Draw a given PagePart on the canvas */
    private void drawPart(Canvas canvas, PagePart part) {
        // Can seem strange, but avoid lot of calls
        RectF pageRelativeBounds = part.getPageRelativeBounds();
        Bitmap renderedBitmap = part.getRenderedBitmap();

        // Move to the target page
        float localTranslation = toCurrentScale(part.getUserPage() * optimalPageWidth);
        canvas.translate(localTranslation, 0);

        Rect srcRect = new Rect(0, 0, renderedBitmap.getWidth(), //
                renderedBitmap.getHeight());

        float offsetX = toCurrentScale(pageRelativeBounds.left * optimalPageWidth);
        float offsetY = toCurrentScale(pageRelativeBounds.top * optimalPageHeight);
        float width = toCurrentScale(pageRelativeBounds.width() * optimalPageWidth);
        float height = toCurrentScale(pageRelativeBounds.height() * optimalPageHeight);

        // If we use float values for this rectangle, there will be
        // a possible gap between page parts, especially when
        // the zoom level is high.
        RectF dstRect = new RectF((int) offsetX, (int) offsetY, //
                (int) (offsetX + width), //
                (int) (offsetY + height));

        // Check if bitmap is in the screen
        float translationX = currentXOffset + localTranslation;
        float translationY = currentYOffset;
        if (translationX + dstRect.left >= getWidth() || translationX + dstRect.right <= 0 ||
                translationY + dstRect.top >= getHeight() || translationY + dstRect.bottom <= 0) {
            canvas.translate(-localTranslation, 0);
            return;
        }

        canvas.drawBitmap(renderedBitmap, srcRect, dstRect, paint);

        if (Constants.DEBUG_MODE) {
            debugPaint.setColor(part.getUserPage() % 2 == 0 ? Color.RED : Color.BLUE);
            canvas.drawRect(dstRect, debugPaint);
        }

        // Restore the canvas position
        canvas.translate(-localTranslation, 0);

    }

    private void drawMiniMap(Canvas canvas) {
        canvas.drawRect(minimapBounds, paintMinimapBack);
        canvas.drawRect(minimapScreenBounds, paintMinimapFront);
    }

    /**
     * Load all the parts around the center of the screen,
     * taking into account X and Y offsets, zoom level, and
     * the current page displayed
     */
    public void loadPages() {
        if (optimalPageWidth == 0 || optimalPageHeight == 0) {
            return;
        }

        // Cancel all current tasks
        renderingAsyncTask.removeAllTasks();
        cacheManager.makeANewSet();

        // Find current index in filtered user pages
        int index = currentPage;
        if (filteredUserPageIndexes != null) {
            index = filteredUserPageIndexes[currentPage];
        }

        // Loop through the pages like [...][4][2][0][1][3][...]
        // loading as many parts as it can.
        int parts = 0;
        for (int i = 0; i <= Constants.LOADED_SIZE / 2 && parts < CACHE_SIZE; i++) {
            parts += loadPage(index + i, CACHE_SIZE - parts);
            if (i != 0 && parts < CACHE_SIZE) {
                parts += loadPage(index - i, CACHE_SIZE - parts);
            }
        }

        invalidate();
    }

    /**
     * Render a page, creating 1 to <i>nbOfPartsLoadable</i> page parts. <br><br>
     * <p/>
     * This is one of the trickiest method of this library. It finds
     * the DocumentPage associated with the given UserPage, loads its
     * thumbnail, cut this page into 256x256 blocs considering the
     * current zoom level, calculate the bloc containing the center of
     * the screen, and start loading these parts in a spiral {@link SpiralLoopManager},
     * only if the given part is not already in the Cache, in which case it
     * moves the part up in the cache.
     * @param userPage          The user page to load.
     * @param nbOfPartsLoadable Maximum number of parts it can load.
     * @return The number of parts loaded.
     */
    private int loadPage(final int userPage, final int nbOfPartsLoadable) {

        // Finds the document page associated with the given userPage
        int documentPage = userPage;
        if (filteredUserPages != null) {
            if (userPage < 0 || userPage >= filteredUserPages.length) {
                return 0;
            } else {
                documentPage = filteredUserPages[userPage];
            }
        }
        final int documentPageFinal = documentPage;
        if (documentPage < 0 || userPage >= documentPageCount) {
            return 0;
        }

        // Render thumbnail of the page
        if (!cacheManager.containsThumbnail(userPage, documentPage, //
                (int) (optimalPageWidth * Constants.THUMBNAIL_RATIO), //
                (int) (optimalPageHeight * Constants.THUMBNAIL_RATIO), //
                new RectF(0, 0, 1, 1))) {
            renderingAsyncTask.addRenderingTask(userPage, documentPage, //
                    (int) (optimalPageWidth * Constants.THUMBNAIL_RATIO), //
                    (int) (optimalPageHeight * Constants.THUMBNAIL_RATIO), //
                    new RectF(0, 0, 1, 1), true, 0);
        }

        // When we want to render a 256x256 bloc, we also need to provide
        // the bounds (left, top, right, bottom) of the rendered part in
        // the PDF page. These four coordinates are ratios (0 -> 1), where
        // (0,0) is the top left corner of the PDF page, and (1,1) is the
        // bottom right corner.
        float ratioX = 1f / (float) optimalPageWidth;
        float ratioY = 1f / (float) optimalPageHeight;
        final float partHeight = (Constants.PART_SIZE * ratioY) / zoom;
        final float partWidth = (Constants.PART_SIZE * ratioX) / zoom;
        final int nbRows = (int) Math.ceil(1f / partHeight);
        final int nbCols = (int) Math.ceil(1f / partWidth);
        final float pageRelativePartWidth = 1f / (float) nbCols;
        final float pageRelativePartHeight = 1f / (float) nbRows;

        // To improve user experience, we need to start displaying the
        // 256x256 blocs with the middle of the screen. Imagine the cut
        // page as a grid. This part calculates which cell of this grid
        // is currently in the middle of the screen, given the current
        // zoom level and the offsets.
        float middleOfScreenX = (-currentXOffset + getWidth() / 2);
        float middleOfScreenY = (-currentYOffset + getHeight() / 2);
        float middleOfScreenPageX = middleOfScreenX - userPage * toCurrentScale(optimalPageWidth);
        float middleOfScreenPageY = middleOfScreenY;
        float middleOfScreenPageXRatio = middleOfScreenPageX / toCurrentScale(optimalPageWidth);
        float middleOfScreenPageYRatio = middleOfScreenPageY / toCurrentScale(optimalPageHeight);
        int startingRow = (int) (middleOfScreenPageYRatio * nbRows);
        int startingCol = (int) (middleOfScreenPageXRatio * nbCols);

        // Avoid outside values
        startingRow = NumberUtils.limit(startingRow, 0, nbRows);
        startingCol = NumberUtils.limit(startingCol, 0, nbCols);

        // Prepare the loop listener
        class SpiralLoopListenerImpl implements SpiralLoopManager.SpiralLoopListener {
            int nbItemTreated = 0;

            @Override
            public boolean onLoop(int row, int col) {

                // Create relative page bounds
                float relX = pageRelativePartWidth * col;
                float relY = pageRelativePartHeight * row;
                float relWidth = pageRelativePartWidth;
                float relHeight = pageRelativePartHeight;

                // Adjust width and height to
                // avoid being outside the page
                float renderWidth = Constants.PART_SIZE / relWidth;
                float renderHeight = Constants.PART_SIZE / relHeight;
                if (relX + relWidth > 1) {
                    relWidth = 1 - relX;
                }
                if (relY + relHeight > 1) {
                    relHeight = 1 - relY;
                }
                renderWidth *= relWidth;
                renderHeight *= relHeight;
                RectF pageRelativeBounds = new RectF(relX, relY, relX + relWidth, relY + relHeight);

                if (renderWidth != 0 && renderHeight != 0) {

                    // Check it the calculated part is already contained in the Cache
                    // If it is, this call will insure the part will go to the right
                    // place in the cache and won't be deleted if the cache need space.
                    if (!cacheManager.upPartIfContained(userPage, documentPageFinal, //
                            renderWidth, renderHeight, pageRelativeBounds, nbItemTreated)) {

                        // If not already in cache, register the rendering
                        // task for further execution.
                        renderingAsyncTask.addRenderingTask(userPage, documentPageFinal, //
                                renderWidth, renderHeight, pageRelativeBounds, false, nbItemTreated);
                    }

                }

                nbItemTreated++;
                if (nbItemTreated >= nbOfPartsLoadable) {
                    // Return false to stop the loop
                    return false;
                }
                return true;
            }
        }

        // Starts the loop
        SpiralLoopListenerImpl spiralLoopListener;
        new SpiralLoopManager(spiralLoopListener = new SpiralLoopListenerImpl())//
                .startLoop(nbRows, nbCols, startingRow, startingCol);

        return spiralLoopListener.nbItemTreated;
    }

    /** Called when the PDF is loaded */
    public void loadComplete(DecodeService decodeService) {
        this.decodeService = decodeService;
        this.documentPageCount = decodeService.getPageCount();

        // We assume all the pages are the same size
        this.pageWidth = decodeService.getPageWidth(0);
        this.pageHeight = decodeService.getPageHeight(0);
        state = State.LOADED;
        calculateOptimalWidthAndHeight();

        // Notify the listener
        jumpTo(defaultPage);
        if (onLoadCompleteListener != null) {
            onLoadCompleteListener.loadComplete(documentPageCount);
        }
    }

    /**
     * Called when a rendering task is over and
     * a PagePart has been freshly created.
     * @param part The created PagePart.
     */
    public void onBitmapRendered(PagePart part) {
        if (part.isThumbnail()) {
            cacheManager.cacheThumbnail(part);
        } else {
            cacheManager.cachePart(part);
        }
        invalidate();
    }

    /**
     * Given the UserPage number, this method restrict it
     * to be sure it's an existing page. It takes care of
     * using the user defined pages if any.
     * @param userPage A page number.
     * @return A restricted valid page number (example : -2 => 0)
     */
    private int determineValidPageNumberFrom(int userPage) {
        if (userPage <= 0) {
            return 0;
        }
        if (originalUserPages != null) {
            if (userPage >= originalUserPages.length) {
                return originalUserPages.length - 1;
            }
        } else {
            if (userPage >= documentPageCount) {
                return documentPageCount - 1;
            }
        }
        return userPage;
    }

    /**
     * Calculate the x-offset needed to have the given
     * page centered on the screen. It doesn't take into
     * account the zoom level.
     * @param pageNb The page number.
     * @return The x-offset to use to have the pageNb centered.
     */
    private float calculateCenterOffsetForPage(int pageNb) {
        float imageX = -(pageNb * optimalPageWidth);
        imageX += getWidth() / 2 - optimalPageWidth / 2;
        return imageX;
    }

    /**
     * Calculate the optimal width and height of a page
     * considering the area width and height
     */
    private void calculateOptimalWidthAndHeight() {
        if (state == State.DEFAULT || getWidth() == 0) {
            return;
        }

        float maxWidth = getWidth(), maxHeight = getHeight();
        float w = pageWidth, h = pageHeight;
        float ratio = w / h;
        w = maxWidth;
        h = (float) Math.floor(maxWidth / ratio);
        if (h > maxHeight) {
            h = maxHeight;
            w = (float) Math.floor(maxHeight * ratio);
        }

        optimalPageWidth = w;
        optimalPageHeight = h;

        calculateMasksBounds();
        calculateMinimapBounds();
    }

    /**
     * Place the minimap background considering the optimal width and height
     * and the MINIMAP_MAX_SIZE.
     */
    private void calculateMinimapBounds() {
        float ratioX = Constants.MINIMAP_MAX_SIZE / optimalPageWidth;
        float ratioY = Constants.MINIMAP_MAX_SIZE / optimalPageHeight;
        float ratio = Math.min(ratioX, ratioY);
        float minimapWidth = optimalPageWidth * ratio;
        float minimapHeight = optimalPageHeight * ratio;
        minimapBounds = new RectF(getWidth() - 5 - minimapWidth, 5, getWidth() - 5, 5 + minimapHeight);
        calculateMinimapAreaBounds();
    }

    /**
     * Place the minimap current rectangle considering the minimap bounds
     * the zoom level, and the current X/Y offsets
     */
    private void calculateMinimapAreaBounds() {
        if (minimapBounds == null) {
            return;
        }

        if (zoom == 1f) {
            miniMapRequired = false;
        } else {
            // Calculates the bounds of the current displayed area
            float x = (-currentXOffset - toCurrentScale(currentFilteredPage * optimalPageWidth)) //
                    / toCurrentScale(optimalPageWidth) * minimapBounds.width();
            float width = getWidth() / toCurrentScale(optimalPageWidth) * minimapBounds.width();
            float y = -currentYOffset / toCurrentScale(optimalPageHeight) * minimapBounds.height();
            float height = getHeight() / toCurrentScale(optimalPageHeight) * minimapBounds.height();
            minimapScreenBounds = new RectF(minimapBounds.left + x, minimapBounds.top + y, //
                    minimapBounds.left + x + width, minimapBounds.top + y + height);
            minimapScreenBounds.intersect(minimapBounds);
            miniMapRequired = true;
        }
    }

    /** Place the left and right masks around the current page. */
    private void calculateMasksBounds() {
        leftMask = new RectF(0, 0, getWidth() / 2 - toCurrentScale(optimalPageWidth) / 2, getHeight());
        rightMask = new RectF(getWidth() / 2 + toCurrentScale(optimalPageWidth) / 2, 0, getWidth(), getHeight());
    }

    /**
     * Move to the given X and Y offsets, but check them ahead of time
     * to be sure not to go outside the the big strip.
     * @param offsetX The big strip X offset to use as the left border of the screen.
     * @param offsetY The big strip Y offset to use as the right border of the screen.
     */
    public void moveTo(float offsetX, float offsetY) {

        // Check Y offset
        if (toCurrentScale(optimalPageHeight) < getHeight()) {
            offsetY = getHeight() / 2 - toCurrentScale(optimalPageHeight) / 2;
        } else {
            if (offsetY > 0) {
                offsetY = 0;
            } else if (offsetY + toCurrentScale(optimalPageHeight) < getHeight()) {
                offsetY = getHeight() - toCurrentScale(optimalPageHeight);
            }
        }

        // Check X offset
        if (isZooming()) {
            if (toCurrentScale(optimalPageWidth) < getWidth()) {
                miniMapRequired = false;
                offsetX = getWidth() / 2 - toCurrentScale((currentFilteredPage + 0.5f) * optimalPageWidth);
            } else {
                miniMapRequired = true;
                if (offsetX + toCurrentScale(currentFilteredPage * optimalPageWidth) > 0) {
                    offsetX = -toCurrentScale(currentFilteredPage * optimalPageWidth);
                } else if (offsetX + toCurrentScale((currentFilteredPage + 1) * optimalPageWidth) < getWidth()) {
                    offsetX = getWidth() - toCurrentScale((currentFilteredPage + 1) * optimalPageWidth);
                }
            }

        } else {

            float maxX = calculateCenterOffsetForPage(currentFilteredPage + 1);
            float minX = calculateCenterOffsetForPage(currentFilteredPage - 1);
            if (offsetX < maxX) {
                offsetX = maxX;
            } else if (offsetX > minX) {
                offsetX = minX;
            }
        }

        currentXOffset = offsetX;
        currentYOffset = offsetY;
        calculateMinimapAreaBounds();
        invalidate();
    }

    /**
     * Move relatively to the current position.
     * @param dx The X difference you want to apply.
     * @param dy The Y difference you want to apply.
     * @see #moveTo(float, float)
     */
    public void moveRelativeTo(float dx, float dy) {
        moveTo(currentXOffset + dx, currentYOffset + dy);
    }

    /** Change the zoom level */
    public void zoomTo(float zoom) {
        this.zoom = zoom;
        calculateMasksBounds();
    }

    /**
     * Change the zoom level, relatively to a pivot point.
     * It will call moveTo() to make sure the given point stays
     * in the middle of the screen.
     * @param zoom  The zoom level.
     * @param pivot The point on the screen that should stays.
     */
    public void zoomCenteredTo(float zoom, PointF pivot) {
        float dzoom = zoom / this.zoom;
        zoomTo(zoom);
        float baseX = currentXOffset * dzoom;
        float baseY = currentYOffset * dzoom;
        baseX += (pivot.x - pivot.x * dzoom);
        baseY += (pivot.y - pivot.y * dzoom);
        moveTo(baseX, baseY);
    }

    /** @see #zoomCenteredTo(float, PointF) */
    public void zoomCenteredRelativeTo(float dzoom, PointF pivot) {
        zoomCenteredTo(zoom * dzoom, pivot);
    }

    public int getCurrentPage() {
        return currentPage;
    }

    public float getCurrentXOffset() {
        return currentXOffset;
    }

    public float getCurrentYOffset() {
        return currentYOffset;
    }

    public float toRealScale(float size) {
        return size / zoom;
    }

    public float toCurrentScale(float size) {
        return size * zoom;
    }

    public float getZoom() {
        return zoom;
    }

    DecodeService getDecodeService() {
        return decodeService;
    }

    public boolean isZooming() {
        return zoom != 1;
    }

    public float getOptimalPageWidth() {
        return optimalPageWidth;
    }

    private void setUserWantsMinimap(boolean userWantsMinimap) {
        this.userWantsMinimap = userWantsMinimap;
    }

    private void setDefaultPage(int defaultPage) {
        this.defaultPage = defaultPage;
    }

    public void resetZoom() {
        zoomTo(1);
    }

    public void resetZoomWithAnimation() {
        animationManager.startZoomAnimation(zoom, 1f);
    }

    /** Use an asset file as the pdf source */
    public Configurator fromAsset(String assetName) {
        try {
            File pdfFile = FileUtils.fileFromAsset(getContext(), assetName);
            return fromFile(pdfFile);
        } catch (IOException e) {
            throw new FileNotFoundException(assetName + " does not exist.", e);
        }
    }

    /** Use a file as the pdf source */
    public Configurator fromFile(File file) {
        if (!file.exists()) throw new FileNotFoundException(file.getAbsolutePath() + "does not exist.");
        return new Configurator(Uri.fromFile(file));
    }

    private enum State {DEFAULT, LOADED, SHOWN}

    public class Configurator {

        private final Uri uri;

        private int[] pageNumbers = null;

        private boolean enableSwipe = true;

        private OnDrawListener onDrawListener;

        private OnLoadCompleteListener onLoadCompleteListener;

        private OnPageChangeListener onPageChangeListener;

        private int defaultPage = 1;

        private boolean showMinimap = false;

        private Configurator(Uri uri) {
            this.uri = uri;
        }

        public Configurator pages(int... pageNumbers) {
            this.pageNumbers = pageNumbers;
            return this;
        }

        public Configurator enableSwipe(boolean enableSwipe) {
            this.enableSwipe = enableSwipe;
            return this;
        }

        public Configurator onDraw(OnDrawListener onDrawListener) {
            this.onDrawListener = onDrawListener;
            return this;
        }

        public Configurator onLoad(OnLoadCompleteListener onLoadCompleteListener) {
            this.onLoadCompleteListener = onLoadCompleteListener;
            return this;
        }

        public Configurator onPageChange(OnPageChangeListener onPageChangeListener) {
            this.onPageChangeListener = onPageChangeListener;
            return this;
        }

        public Configurator defaultPage(int defaultPage) {
            this.defaultPage = defaultPage;
            return this;
        }

        public void load() {
            PDFView.this.recycle();
            PDFView.this.setOnDrawListener(onDrawListener);
            PDFView.this.setOnPageChangeListener(onPageChangeListener);
            PDFView.this.enableSwipe(enableSwipe);
            PDFView.this.setDefaultPage(defaultPage);
            PDFView.this.setUserWantsMinimap(showMinimap);
            if (pageNumbers != null) {
                PDFView.this.load(uri, onLoadCompleteListener, pageNumbers);
            } else {
                PDFView.this.load(uri, onLoadCompleteListener);
            }
        }

        public Configurator showMinimap(boolean showMinimap) {
            this.showMinimap = showMinimap;
            return this;
        }
    }
}




Java Source Code List

com.joanzapata.PDFViewActivity.java
com.joanzapata.pdfview.AnimationManager.java
com.joanzapata.pdfview.CacheManager.java
com.joanzapata.pdfview.DecodingAsyncTask.java
com.joanzapata.pdfview.DragPinchManager.java
com.joanzapata.pdfview.PDFView.java
com.joanzapata.pdfview.RenderingAsyncTask.java
com.joanzapata.pdfview.SpiralLoopManager.java
com.joanzapata.pdfview.exception.FileNotFoundException.java
com.joanzapata.pdfview.listener.OnDrawListener.java
com.joanzapata.pdfview.listener.OnLoadCompleteListener.java
com.joanzapata.pdfview.listener.OnPageChangeListener.java
com.joanzapata.pdfview.model.PagePart.java
com.joanzapata.pdfview.util.ArrayUtils.java
com.joanzapata.pdfview.util.Constants.java
com.joanzapata.pdfview.util.DragPinchListener.java
com.joanzapata.pdfview.util.FileUtils.java
com.joanzapata.pdfview.util.NumberUtils.java
org.vudroid.core.DecodeServiceBase.java
org.vudroid.core.DecodeService.java
org.vudroid.core.DocumentView.java
org.vudroid.core.PageTreeNode.java
org.vudroid.core.Page.java
org.vudroid.core.VuDroidLibraryLoader.java
org.vudroid.core.codec.CodecContext.java
org.vudroid.core.codec.CodecDocument.java
org.vudroid.core.codec.CodecPage.java
org.vudroid.core.events.BringUpZoomControlsEvent.java
org.vudroid.core.events.BringUpZoomControlsListener.java
org.vudroid.core.events.CurrentPageListener.java
org.vudroid.core.events.DecodingProgressListener.java
org.vudroid.core.events.EventDispatcher.java
org.vudroid.core.events.Event.java
org.vudroid.core.events.SafeEvent.java
org.vudroid.core.events.ZoomChangedEvent.java
org.vudroid.core.events.ZoomListener.java
org.vudroid.core.models.CurrentPageModel.java
org.vudroid.core.models.DecodingProgressModel.java
org.vudroid.core.models.ZoomModel.java
org.vudroid.core.multitouch.MultiTouchZoomImpl.java
org.vudroid.core.multitouch.MultiTouchZoom.java
org.vudroid.core.utils.MD5StringUtil.java
org.vudroid.core.utils.PathFromUri.java
org.vudroid.pdfdroid.codec.PdfContext.java
org.vudroid.pdfdroid.codec.PdfDocument.java
org.vudroid.pdfdroid.codec.PdfPage.java