Skip to content

创建自定义工具

事件处理机制

Tool Handler 和 Annotation Handler 处理来自 PDFViewCtrl 的触屏、手势等事件。事件触发时,PDFViewCtrl 将事件传递给 UIExtensionsManager:

  • Tool Handler 响应: 若存在当前 Tool Handler,UIExtensionsManager 将事件传递给它,事件处理结束。
  • Annotation Handler 响应: 若存在选中的 Annotation,UIExtensionsManager 将事件传递给其对应的 Annotation Handler,事件处理结束。
  • Selection Tool Handler 响应: 若不存在 Tool Handler 和选中的 Annotation,UIExtensionsManager 将事件传递给 Selection Tool Handler。
    • Text Selection Tool: 处理文本选择事件(如添加高亮注释)。
    • Blank Selection Tool: 处理空白区域事件(如添加笔记注释)。

说明

  • Tool Handler 和 Annotation Handler 不会同时响应事件。
  • Tool Handler 主要用于创建注释(暂不支持 Link Annotation)、创建签名和文本选择。
  • Annotation Handler 主要用于编辑注释和填写表单。

Tool Handler 和 Annotation Handler 事件响应流程:

Tool Handler 和 Annotation Handler 事件响应流程

自定义工具示例

本文介绍如何在 UI Extensions 的基础上创建一个自定义工具。UI Extensions 已内置多个工具实现,您可以参考这些实现或基于其扩展。

示例:实现一个“区域截图工具”,用于在页面上框选区域并保存为图片。

说明

以下示例基于 samples/viewer_ctrl_demo,并以实现 ToolHandler.java 接口为核心。

实现步骤概览

  • 创建 ScreenCaptureToolHandler,实现 ToolHandler.java 接口
  • 处理 onTouchEventonDraw(框选区域、绘制选框、渲染并保存位图)
  • 实例化并注册到 UIExtensionsManager
  • 设为当前工具(current tool handler)

步骤 1:创建 ScreenCaptureToolHandler

在 Android Studio 中加载 viewer_ctrl_demo,在 com.foxit.pdf.viewctrl 包下创建 ScreenCaptureToolHandler,并让其实现 ToolHandler 接口。

步骤 2:处理 onTouchEventonDraw

下面给出 ScreenCaptureToolHandler.java 的参考实现。

Details
java
package com.foxit.pdf.pdfviewer;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Environment;
import android.view.MotionEvent;
import android.widget.Toast;

import com.foxit.sdk.PDFViewCtrl;
import com.foxit.sdk.PDFException;
import com.foxit.sdk.common.Progressive;
import com.foxit.sdk.common.fxcrt.Matrix2D;
import com.foxit.sdk.pdf.PDFPage;
import com.foxit.sdk.common.Renderer;
import com.foxit.uiextensions.ToolHandler;
import com.foxit.uiextensions.UIExtensionsManager;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class ScreenCaptureToolHandler implements ToolHandler {

    private Context mContext;
    private PDFViewCtrl mPdfViewCtrl;

    public ScreenCaptureToolHandler(Context context, PDFViewCtrl pdfViewCtrl) {
        mPdfViewCtrl = pdfViewCtrl;
        mContext = context;
    }

    @Override
    public String getType() {
        return "";
    }

    @Override
    public void onActivate() {

    }

    @Override
    public void onDeactivate() {

    }

    private PointF mStartPoint = new PointF(0, 0);
    private PointF mEndPoint = new PointF(0, 0);
    private PointF mDownPoint = new PointF(0, 0);
    private Rect mRect = new Rect(0, 0, 0, 0);
    private RectF mNowRect = new RectF(0, 0, 0, 0);
    private int mLastPageIndex = -1;

// 处理触摸事件
    @Override
    public boolean onTouchEvent(int pageIndex, MotionEvent motionEvent) {
        // 获取设备坐标系中的显示视图点
        PointF devPt = new PointF(motionEvent.getX(), motionEvent.getY());
        PointF point = new PointF();
        // 将显示视图坐标转换为页面视图坐标
        mPdfViewCtrl.convertDisplayViewPtToPageViewPt(devPt, point, pageIndex);
        float x = point.x;
        float y = point.y;

        switch (motionEvent.getAction()) {
            // 处理 ACTION_DOWN:记录起始点坐标
            case MotionEvent.ACTION_DOWN:
                if (mLastPageIndex == -1 || mLastPageIndex == pageIndex) {
                    mStartPoint.x = x;
                    mStartPoint.y = y;
                    mEndPoint.x = x;
                    mEndPoint.y = y;
                    mDownPoint.set(x, y);
                    if (mLastPageIndex == -1) {
                        mLastPageIndex = pageIndex;
                    }
                }
                return true;

            // 处理 ACTION_MOVE:更新选区
            case MotionEvent.ACTION_MOVE:
                if (mLastPageIndex != pageIndex)
                    break;
                if (!mDownPoint.equals(x, y)) {
                    mEndPoint.x = x;
                    mEndPoint.y = y;

                    // 计算选区矩形坐标
                    getDrawRect(mStartPoint.x, mStartPoint.y, mEndPoint.x, mEndPoint.y);

                    // 将浮点坐标转为整数
                    mRect.set((int) mNowRect.left, (int) mNowRect.top, (int) mNowRect.right, (int) mNowRect.bottom);

                    // 刷新 PDFViewCtrl,触发 onDraw 重绘
                    mPdfViewCtrl.refresh(pageIndex, mRect);
                    mDownPoint.set(x, y);
                }
                return true;

            // 将选区保存为位图
            case MotionEvent.ACTION_UP:
                if (mLastPageIndex != pageIndex)
                    break;
                if (!mStartPoint.equals(mEndPoint.x, mEndPoint.y)) {
                    renderToBmp(pageIndex, Environment.getExternalStorageDirectory().getPath() + "/FoxitSDK/ScreenCapture.bmp");
                    Toast.makeText(mContext, "The selected area was saved as a bitmap stored in the /FoxitSDK/ScreenCapture.bmp", Toast.LENGTH_LONG).show();
                }
                mDownPoint.set(0, 0);
                mLastPageIndex = -1;
                return true;
            default:
                return true;
        }
        return true;
    }

    // 保存 Bitmap 到指定路径
    public static void saveBitmap(Bitmap bm, String outPath) throws IOException {
        File file = new File(outPath);
        file.createNewFile();

        FileOutputStream fileout = null;
        try {
            fileout = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        bm.compress(Bitmap.CompressFormat.JPEG, 100, fileout);
        try {
            fileout.flush();
            fileout.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 将选区渲染为位图
    private void renderToBmp(int pageIndex, String filePath) {
        try {
            PDFPage page = mPdfViewCtrl.getDoc().getPage(pageIndex);

            mPdfViewCtrl.convertPageViewRectToPdfRect(mNowRect, mNowRect, pageIndex);
            int width = (int) page.getWidth();
            int height = (int) page.getHeight();

            Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            bmp.eraseColor(Color.WHITE);

            // 创建 Renderer 对象
            Renderer renderer = new Renderer(bmp, true);

            // 获取显示矩阵
            Matrix2D matrix = page.getDisplayMatrix(0, 0, width, height, 0);
            Progressive progress = renderer.startRender(page, matrix, null);
            int state = Progressive.e_ToBeContinued;
            while (state == Progressive.e_ToBeContinued) {
                state = progress.resume();
            }

            // 裁剪出选区大小的位图
            bmp = Bitmap.createBitmap(bmp, (int) mNowRect.left, (int) (height - mNowRect.top), (int) mNowRect.width(), (int) Math.abs(mNowRect.height()));
            try {
                saveBitmap(bmp, filePath);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } catch (PDFException e) {
            e.printStackTrace();
        }
    }

    // 计算选区矩形的归一化坐标
    private void getDrawRect(float x1, float y1, float x2, float y2) {
        float minx = Math.min(x1, x2);
        float miny = Math.min(y1, y2);
        float maxx = Math.max(x1, x2);
        float maxy = Math.max(y1, y2);

        mNowRect.left = minx;
        mNowRect.top = miny;
        mNowRect.right = maxx;
        mNowRect.bottom = maxy;
    }

    @Override
    public boolean onLongPress(int pageIndex, MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean onSingleTapConfirmed(int pageIndex, MotionEvent motionEvent) {
        return false;
    }

    @Override
    public boolean isContinueAddAnnot() {
        return false;
    }

    @Override
    public void setContinueAddAnnot(boolean continueAddAnnot) {

    }

    // 处理绘制事件:绘制选区框
    @Override
    public void onDraw(int i, Canvas canvas) {
        if (((UIExtensionsManager) mPdfViewCtrl.getUIExtensionsManager()).getCurrentToolHandler() != this)
            return;
        if (mLastPageIndex != i) {
            return;
        }
        canvas.save();
        Paint mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setAntiAlias(true);
        mPaint.setDither(true);
        mPaint.setColor(Color.BLUE);
        mPaint.setAlpha(200);
        mPaint.setStrokeWidth(3);
        canvas.drawRect(mNowRect, mPaint);
        canvas.restore();
    }
}

提示

以上示例中会将截图保存到 /FoxitSDK/ScreenCapture.bmp,并通过 Toast 提示保存路径。请确保设备/模拟器可访问对应目录。

步骤 3:注册工具到 UIExtensionsManager

JAVA
private ScreenCaptureToolHandler screenCapture = null;
...
screenCapture = new ScreenCaptureToolHandler(mContext, pdfViewCtrl);
uiExtensionsManager.registerToolHandler(screenCapture);

步骤 4:设置为当前工具

java
uiExtensionsManager.setCurrentToolHandler(screenCapture);

将工具接入到界面

为便于触发该工具,可在菜单中添加 Action Item,并在 MainActivity.javaonActionItemClicked() 中切换工具。

1) 在 Main.xmlapp/src/main/res/menu)添加条目

xml
<item
   android:id="@+id/ScreenCapture"
   android:title="@string/screencapture"/>

2) 在 strings.xml 添加字符串

xml
<string name="screencapture">ScreenCapture</string>

3) 在 MainActivity.java 的 onActionItemClicked() 中处理点击

java
if (itemId == R.id.ScreenCapture) {
    if (screenCapture == null) {
        screenCapture = new ScreenCaptureToolHandler(mContext, pdfViewCtrl);
        uiExtensionsManager.registerToolHandler(screenCapture);
    }
    uiExtensionsManager.setCurrentToolHandler(screenCapture);
}

运行与验证

完成以上步骤后,即可运行并验证工具是否生效

  1. 确保已在代码中提前声明并持有 ScreenCaptureToolHandler 实例引用,例如:
    • private ScreenCaptureToolHandler screenCapture = null;
  2. 构建并运行 viewer_ctrl_demo
  3. 注意:请确保已将 Sample.pdf 推送到设备/模拟器 SD 卡中与示例工程路径一致的目录(示例工程通常使用 /FoxitSDK/Sample.pdf)。
  4. 安装完成后,按系统弹窗提示点击 Allow,允许应用访问文件。
  5. 文档打开后,点击页面任意位置唤出"页面菜单",找到并点击 ScreenCapture
  6. 在页面上长按并拖动选择矩形区域。完成后会弹出提示,告知截图保存位置。
  7. 在 IDE 的 Device Explorer 中进入设备存储的 FoxitSDK 目录,确认已生成 ScreenCapture.bmp。可将其导出到本地后查看内容是否符合预期。