【问题标题】:How to implement PdfRenderer Zoom and Scroll support?如何实现 PdfRenderer 缩放和滚动支持?
【发布时间】:2016-09-25 18:25:58
【问题描述】:

我正在尝试使用 PdfRenderer,要求具有缩放和滚动功能,但在 Android 中 PdfRenderer 不提供任何缩放和滚动支持,只有页面导航支持可用。

但我想缩放和滚动支持可以实现,因为 PdfRenderer 使用位图使用 imageview 显示内容。

如何使用 Google PdfRenderer 实现缩放和滚动支持 样品?

PS:我正在使用 Google 提供的这个 PdfRenderer 示例,https://github.com/googlesamples/android-PdfRendererBasic

【问题讨论】:

    标签: java android pdfrenderer


    【解决方案1】:

    遇到这种情况时我使用的解决方案是:

    • 在 ImageView 中加载 pdfRenderer 页面

    • 将我的 ImageView 放在 ScrollView 中(tadam 滚动管理),并将此 ScrollView 放在 FrameLayout 中

    • 添加两个按钮(在滚动视图之外)来管理放大和缩小(每个按钮都会在我的 ImageView 上触发缩放动画)。您也可以使用手势检测器来管理它,但这样做时我很难处理滚动行为

    • 添加两个按钮来管理页面更改(仍在 ScrollView 之外)

    • 为了获得更好的效果,我在按钮上添加了 FadeIn/FadeOut 动画,在 OnTouchEvents 上触发 FadeIn(如果没有播放动画),并在 FadeIn 动画结束时触发 FadeOut

    希望我能帮上忙,如果您需要更详细的信息,请联系我,但您现在应该知道从哪里开始

    这是一个代码示例(不包括页面导航等,但仅包括缩放行为和滚动,其余部分在您链接的 google 代码示例中) 代码 : C#(但很容易转换成 Java)

    private Button _zoomInButton;
    private Button _zoomOutButton;
    private ImageView _pdfViewContainer;
    private float _currentZoomLevel;
    private float _zoomFactor;
    private float _maxZoomLevel;
    private float _minZoomLevel;
    
    private void Init(View view) // the content of this method must go in your OnViewCreated method, here the view being the frameLayout you will find in xml
    {
         _zoomInButton = view.FindViewById<Button>(Resource.Id.PdfZoomInButton);
         _zoomOutButton = view.FindViewById<Button>(Resource.Id.PdfZoomOutButton);
         _pdfViewContainer = view.FindViewById<ImageView>(Resource.Id.PdfViewContainer);
    
        _zoomInButton.Click += delegate { ZoomIn(); }; //for you (in Java) this must looks like setOnClickListener(this); and in the onClick metghod you just have to add a case for R.id.PdfZoomInButton containing a call to ZoomIn();
        _zoomOutButton.Click += delegate { ZoomOut(); };
    
        _minZoomLevel = 0.9f;
        _maxZoomLevel = 1.2f;
        _zoomFactor = 0.1f;
    }
    
    private void ZoomIn()
    {
        if (_currentZoomLevel + _zoomFactor < _maxZoomLevel)
        {
            ScaleAnimation scale = new ScaleAnimation(_currentZoomLevel, _currentZoomLevel + _zoomFactor, _currentZoomLevel, _currentZoomLevel + _zoomFactor, Dimension.RelativeToSelf, 0.5f, Dimension.RelativeToSelf, 0.5f);
            scale.Duration = 50;
            scale.FillAfter = true;
            _pdfViewContainer.StartAnimation(scale);
            _currentZoomLevel += _zoomFactor;
        }
    }
    
    private void ZoomOut()
    {
        if (_currentZoomLevel - _zoomFactor > _minZoomLevel)
        {
            ScaleAnimation scale = new ScaleAnimation(_currentZoomLevel, _currentZoomLevel - _zoomFactor, _currentZoomLevel, _currentZoomLevel - _zoomFactor, Dimension.RelativeToSelf, 0.5f, Dimension.RelativeToSelf, 0.5f);
            scale.Duration = 50;
            scale.FillAfter = true;
            _pdfViewContainer.StartAnimation(scale);
            _currentZoomLevel -= _zoomFactor;
        }
    }
    

    XMl

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/PdfContainer">
        <ScrollView xmlns:tools="http://schemas.android.com/tools"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:scrollbarAlwaysDrawVerticalTrack="true"
            android:id="@+id/PdfScrollView">
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="fitCenter"
                android:adjustViewBounds="true"
                android:scrollbars="vertical"
                android:src="@drawable/mediaIconPDF"
                android:id="@+id/PdfViewContainer" />
        </ScrollView>
        <LinearLayout
            android:id="@+id/PdfRightLayout"
            android:layout_gravity="right"
            android:orientation="vertical"
            android:gravity="center"
            android:layout_width="50dp"
            android:layout_height="match_parent"
            android:weightSum="1">
            <Button
                android:id="@+id/PdfZoomInButton"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:text="+" />
            <space
                android:layout_width="match_parent"
                android:layout_height="0dp"
                android:layout_weight="0.2" />
            <Button
                android:id="@+id/PdfZoomOutButton"
                android:layout_width="match_parent"
                android:layout_height="50dp"
                android:text="-" />
        </LinearLayout>
        <LinearLayout
            android:id="@+id/PdfBottomLayout"
            android:layout_gravity="bottom"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:background="@color/vogofTransparentGrey"
            android:weightSum="1">
            <Button
                android:id="@+id/PdfPreviousPage"
                android:layout_width="0dp"
                android:layout_weight="0.15"
                android:layout_height="match_parent"
                android:text="Prev" />
            <TextView
                android:id="@+id/PdfCurrentPageLabel"
                android:layout_width="0dp"
                android:layout_weight="0.7"
                android:gravity="center"
                android:layout_height="match_parent"
                 />
            <Button
                android:id="@+id/PdfNextPage"
                android:layout_width="0dp"
                android:layout_weight="0.15"
                android:layout_height="match_parent"
                android:text="Next" />
        </LinearLayout>
    </FrameLayout>
    

    有了这个,花点时间去理解它,一点点努力你应该就能得到想要的结果。祝你有美好的一天

    【讨论】:

    • 感谢您的回答。你能发布你的示例代码吗?
    • 完成了,但是如果你只是复制粘贴它就行不通了,因为它在 XamarinC# 中(但真的很容易翻译)
    【解决方案2】:

    我使用了@yan-yankelevich 的想法并用Java 编写了代码。很多问题在于找到适当的缩放和相应的位图大小值。不要忘记 PdfRenderer 仅适用于 API 21+。

    带有 PDF 位图的片段fragment_pdf_renderer.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:orientation="vertical"
        tools:context=".PdfRendererFragment">
    
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">
    
            <HorizontalScrollView
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
    
                <ImageView
                    android:id="@+id/image"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@android:color/white"
                    android:contentDescription="@null" />
            </HorizontalScrollView>
        </ScrollView>
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="@color/from_divider_gray"
            android:gravity="center_vertical"
            android:orientation="horizontal">
    
            <Button
                android:id="@+id/previous"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/previous_page"
                android:textSize="13sp" />
    
            <Button
                android:id="@+id/next"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:text="@string/next_page"
                android:textSize="13sp" />
    
            <ImageButton
                android:id="@+id/zoomout"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="0dp"
                android:padding="8dp"
                android:src="@drawable/ic_zoom_out_black_36dp" />
    
            <ImageButton
                android:id="@+id/zoomin"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="0dp"
                android:padding="8dp"
                android:src="@drawable/ic_zoom_in_black_36dp" />
        </LinearLayout>
    
    </LinearLayout>
    

    PdfRendererFragment:

    /**
     * This fragment has a big {@ImageView} that shows PDF pages, and 2
     * {@link android.widget.Button}s to move between pages. We use a
     * {@link android.graphics.pdf.PdfRenderer} to render PDF pages as
     * {@link android.graphics.Bitmap}s.
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    public class PdfRendererFragment extends Fragment implements View.OnClickListener {
    
        /**
         * Key string for saving the state of current page index.
         */
        private static final String STATE_CURRENT_PAGE_INDEX = "current_page_index";
    
        /**
         * The filename of the PDF.
         */
        public String FILENAME;
        public String PURCHASE_ID;
        public int TICKETS_NUMBER;
    
        /**
         * File descriptor of the PDF.
         */
        private ParcelFileDescriptor mFileDescriptor;
    
        /**
         * {@link android.graphics.pdf.PdfRenderer} to render the PDF.
         */
        private PdfRenderer mPdfRenderer;
    
        /**
         * Page that is currently shown on the screen.
         */
        private PdfRenderer.Page mCurrentPage;
    
        /**
         * {@link android.widget.ImageView} that shows a PDF page as a {@link android.graphics.Bitmap}
         */
        private ImageView mImageView;
    
        /**
         * {@link android.widget.Button} to move to the previous page.
         */
        private Button mButtonPrevious;
        private ImageView mButtonZoomin;
        private ImageView mButtonZoomout;
        private Button mButtonNext;
        private float currentZoomLevel = 12;
    
        /**
         * PDF page index
         */
        private int mPageIndex;
    
        public PdfRendererFragment() {
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            return inflater.inflate(R.layout.fragment_pdf_renderer, container, false);
        }
    
        @Override
        public void onViewCreated(View view, Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            // Retain view references.
            mImageView = (ImageView) view.findViewById(R.id.image);
            mButtonPrevious = (Button) view.findViewById(R.id.previous);
            mButtonNext = (Button) view.findViewById(R.id.next);
            mButtonZoomin = view.findViewById(R.id.zoomin);
            mButtonZoomout = view.findViewById(R.id.zoomout);
    
            // Bind events.
            mButtonPrevious.setOnClickListener(this);
            mButtonNext.setOnClickListener(this);
            mButtonZoomin.setOnClickListener(this);
            mButtonZoomout.setOnClickListener(this);
    
            mPageIndex = 0;
            // If there is a savedInstanceState (screen orientations, etc.), we restore the page index.
            if (null != savedInstanceState) {
                mPageIndex = savedInstanceState.getInt(STATE_CURRENT_PAGE_INDEX, 0);
            }
        }
    
        @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            FILENAME = getActivity().getIntent().getExtras().getString("pdfFilename");
            TICKETS_NUMBER = getActivity().getIntent().getExtras().getInt("tickets_number");
            PURCHASE_ID = getActivity().getIntent().getExtras().getString("purchaseGuid");
        }
    
        @Override
        public void onStart() {
            super.onStart();
            try {
                openRenderer(getActivity());
                showPage(mPageIndex);
            } catch (IOException e) {
                e.printStackTrace();
                Toast.makeText(getActivity(), getString(R.string.ticket_file_not_found, FILENAME), Toast.LENGTH_SHORT).show();
                App app = (App) getActivity().getApplicationContext();
                TicketUtil.downloadTicket(app, PURCHASE_ID);
                getActivity().finish();
            }
        }
    
        @Override
        public void onStop() {
            try {
                closeRenderer();
            } catch (IOException e) {
                e.printStackTrace();
            }
            super.onStop();
        }
    
        @Override
        public void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            if (null != mCurrentPage) {
                outState.putInt(STATE_CURRENT_PAGE_INDEX, mCurrentPage.getIndex());
            }
        }
    
        /**
         * Sets up a {@link android.graphics.pdf.PdfRenderer} and related resources.
         */
        private void openRenderer(Context context) throws IOException {
            // In this sample, we read a PDF from the assets directory.
            File file = TicketUtil.getTicketFile(context, PURCHASE_ID);
            if (!file.exists()) {
                // Since PdfRenderer cannot handle the compressed asset file directly, we copy it into
                // the cache directory.
                InputStream asset = context.getAssets().open(FILENAME);
                FileOutputStream output = new FileOutputStream(file);
                final byte[] buffer = new byte[1024];
                int size;
                while ((size = asset.read(buffer)) != -1) {
                    output.write(buffer, 0, size);
                }
                asset.close();
                output.close();
            }
            mFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
            // This is the PdfRenderer we use to render the PDF.
            if (mFileDescriptor != null) {
                mPdfRenderer = new PdfRenderer(mFileDescriptor);
            }
        }
    
        /**
         * Closes the {@link android.graphics.pdf.PdfRenderer} and related resources.
         *
         * @throws java.io.IOException When the PDF file cannot be closed.
         */
        private void closeRenderer() throws IOException {
            if (null != mCurrentPage) {
                mCurrentPage.close();
                mCurrentPage = null;
            }
            if (null != mPdfRenderer) {
                mPdfRenderer.close();
            }
            if (null != mFileDescriptor) {
                mFileDescriptor.close();
            }
        }
    
        /**
         * Zoom level for zoom matrix depends on screen density (dpiAdjustedZoomLevel), but width and height of bitmap depends only on pixel size and don't depend on DPI
         * Shows the specified page of PDF to the screen.
         *
         * @param index The page index.
         */
        private void showPage(int index) {
            if (mPdfRenderer.getPageCount() <= index) {
                return;
            }
            // Make sure to close the current page before opening another one.
            if (null != mCurrentPage) {
                mCurrentPage.close();
            }
            // Use `openPage` to open a specific page in PDF.
            mCurrentPage = mPdfRenderer.openPage(index);
            // Important: the destination bitmap must be ARGB (not RGB).
            int newWidth = (int) (getResources().getDisplayMetrics().widthPixels * mCurrentPage.getWidth() / 72 * currentZoomLevel / 40);
            int newHeight = (int) (getResources().getDisplayMetrics().heightPixels * mCurrentPage.getHeight() / 72 * currentZoomLevel / 64);
            Bitmap bitmap = Bitmap.createBitmap(
                    newWidth,
                    newHeight,
                    Bitmap.Config.ARGB_8888);
            Matrix matrix = new Matrix();
    
            float dpiAdjustedZoomLevel = currentZoomLevel * DisplayMetrics.DENSITY_MEDIUM / getResources().getDisplayMetrics().densityDpi;
            matrix.setScale(dpiAdjustedZoomLevel, dpiAdjustedZoomLevel);
    //        Toast.makeText(getActivity(), "width " + String.valueOf(newWidth) + " widthPixels " + getResources().getDisplayMetrics().widthPixels, Toast.LENGTH_LONG).show();
    //        matrix.postTranslate(-rect.left/mCurrentPage.getWidth(), -rect.top/mCurrentPage.getHeight());
    
            // Here, we render the page onto the Bitmap.
            // To render a portion of the page, use the second and third parameter. Pass nulls to get
            // the default result.
            // Pass either RENDER_MODE_FOR_DISPLAY or RENDER_MODE_FOR_PRINT for the last parameter.
            mCurrentPage.render(bitmap, null, matrix, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
            // We are ready to show the Bitmap to user.
            mImageView.setImageBitmap(bitmap);
            updateUi();
        }
    
        /**
         * Updates the state of 2 control buttons in response to the current page index.
         */
        private void updateUi() {
            int index = mCurrentPage.getIndex();
            int pageCount = mPdfRenderer.getPageCount();
            if (pageCount == 1) {
                mButtonPrevious.setVisibility(View.GONE);
                mButtonNext.setVisibility(View.GONE);
            } else {
                mButtonPrevious.setEnabled(0 != index);
                mButtonNext.setEnabled(index + 1 < pageCount);
            }
            if (currentZoomLevel == 2) {
                mButtonZoomout.setActivated(false);
            } else {
                mButtonZoomout.setActivated(true);
            }
        }
    
        /**
         * Gets the number of pages in the PDF. This method is marked as public for testing.
         *
         * @return The number of pages.
         */
        public int getPageCount() {
            return mPdfRenderer.getPageCount();
        }
    
        @Override
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.previous: {
                    // Move to the previous page
                    currentZoomLevel = 12;
                    showPage(mCurrentPage.getIndex() - 1);
                    break;
                }
                case R.id.next: {
                    // Move to the next page
                    currentZoomLevel = 12;
                    showPage(mCurrentPage.getIndex() + 1);
                    break;
                }
                case R.id.zoomout: {
                    // Move to the next page
                    --currentZoomLevel;
                    showPage(mCurrentPage.getIndex());
                    break;
                }
                case R.id.zoomin: {
                    // Move to the next page
                    ++currentZoomLevel;
                    showPage(mCurrentPage.getIndex());
                    break;
                }
            }
        }
    }
    

    请注意缩放级别取决于您的屏幕密度,但位图的宽度和高度(以像素为单位)仅取决于您的缩放级别。此外,您需要调整您的尺寸,以便在默认缩放时(对我来说,它是 pdf 全屏呈现,值为 12),您的 PDF 位图在您的视图中占用的空间不多也不少。

    int newWidth = (int) (getResources().getDisplayMetrics().widthPixels * mCurrentPage.getWidth() / 72 * currentZoomLevel / 40);
    int newHeight = (int) (getResources().getDisplayMetrics().heightPixels * mCurrentPage.getHeight() / 72 * currentZoomLevel / 64);
    Bitmap bitmap = Bitmap.createBitmap(
                    newWidth,
                    newHeight,
                    Bitmap.Config.ARGB_8888);
    
    1. 我发现缩放 12 适合我的屏幕,而 40 和 64 是使位图大小合适的系数。
    2. mCurrentPage.getWidth() 返回以 Postscript 点为单位的宽度,其中每个 pt 为 1/72 英寸。
    3. 72 (DPI) 是默认的 PDF 分辨率。

    PS。如果你需要同时垂直和水平滚动Scrollview vertical and horizontal in android

    【讨论】:

    • 我知道这是一个老问题,但根本不需要矩阵和 dpi。你可以这样做 int newWidth = (int) ( currentPage.getWidth() * currentZoomLevel / 6); int newHeight = (int) ( currentPage.getHeight() * currentZoomLevel / 6);位图位图 = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888); currentPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
    【解决方案3】:

    从这个解决方案开始:https://stackoverflow.com/a/46002017/5049286我找到了一个避免初始缩放系数和其他固定系数的好方法,只改变了这个方法:

    private void showPage(int index) {
    
        if (mPdfRenderer.getPageCount() <= index) {
            return;
        }
        if (null != mCurrentPage) {
            mCurrentPage.close();
        }
        mCurrentPage = mPdfRenderer.openPage(index);
    
        int newWidth = (int) (mVerticalScrollView.getWidth() * 
        currentZoomLevel);
        int newHeight = (int) (newWidth * 
        ((float)mCurrentPage.getHeight()/(float)mCurrentPage.getWidth()));
    
        Bitmap bitmap = Bitmap.createBitmap(
            newWidth,
            newHeight,
            Bitmap.Config.ARGB_8888);
    
       mCurrentPage.render(bitmap, null, null, 
       PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
       mImageView.setImageBitmap(bitmap);
    
       updateUi();
    

    }

    使用此解决方案 currentZoomLevel 从 1.0f 开始到 xxx(您决定一个限制),并且缩放 1.0f 的渲染图像适合滚动视图并保持比例......

    【讨论】:

      【解决方案4】:

      我在这里找到了更好的答案:PdfRendering zoom on page,由 CommonsWare 链接:https://github.com/commonsguy/cw-omnibus/tree/v8.8/PDF/PdfRenderer。所以根据 soshial 的回答,你有捏缩放,可以摆脱缩放按钮和常量:

      import com.davemorrissey.labs.subscaleview.ImageSource;
      import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView;
      

      ....

      private void showPage(int index) {
          if (mPdfRenderer.getPageCount() <= index) {
              return;
          }
          // Make sure to close the current page before opening another one.
          if (null != mCurrentPage) {
              mCurrentPage.close();
          }
          // Use `openPage` to open a specific page in PDF.
          mCurrentPage = mPdfRenderer.openPage(index);
          if(mBitmap==null) {
              // Important: the destination bitmap must be ARGB (not RGB).
              int newWidth = (int) (getResources().getDisplayMetrics().densityDpi * mCurrentPage.getWidth() / 72);
              int newHeight = (int) (getResources().getDisplayMetrics().densityDpi * mCurrentPage.getHeight() / 72);
              mBitmap = Bitmap.createBitmap(
                      newWidth,
                      newHeight,
                      Bitmap.Config.ARGB_8888);
          }
          mBitmap.eraseColor(0xFFFFFFFF);
          mCurrentPage.render(mBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
          // We are ready to show the Bitmap to user.
          mSubsamplingImageView.resetScaleAndCenter();
          mSubsamplingImageView.setImage(ImageSource.cachedBitmap(mBitmap));
          updateUi();
      }
      

      我还添加了位图回收:

      /**
       * Closes the {@link android.graphics.pdf.PdfRenderer} and related resources.
       *
       * @throws java.io.IOException When the PDF file cannot be closed.
       */
      private void closeRenderer() throws IOException {
          if (null != mCurrentPage) {
              mCurrentPage.close();
              mCurrentPage = null;
          }
          if (null != mPdfRenderer) {
              mPdfRenderer.close();
          }
          if (null != mFileDescriptor) {
              mFileDescriptor.close();
          }
          if(mBitmap!=null)
          {
              mBitmap.recycle();
              mBitmap = null;
          }
      }
      

      并且在xml中,而不是ImageView:

          <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
              android:id="@+id/report_viewer_image"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
          />
      

      【讨论】:

      • 这是目前最好的解决方案。 1) 只有一个外部库用于缩放目的。 2) 使用渲染器。 3)PDF的质量很好。感谢您的回答!
      猜你喜欢
      • 2019-04-28
      • 2018-04-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-15
      • 2012-07-25
      相关资源
      最近更新 更多