创建自定义工具
事件处理机制
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 事件响应流程:

自定义工具示例
本文介绍如何在 UI Extensions 的基础上创建一个自定义工具。UI Extensions 已内置多个工具实现,您可以参考这些实现或基于其扩展。
示例:实现一个“区域截图工具”,用于在页面上框选区域并保存为图片。
说明
以下示例基于 samples/viewer_ctrl_demo,并以实现 ToolHandler.java 接口为核心。
实现步骤概览
- 创建
ScreenCaptureToolHandler,实现ToolHandler.java接口 - 处理
onTouchEvent与onDraw(框选区域、绘制选框、渲染并保存位图) - 实例化并注册到
UIExtensionsManager - 设为当前工具(current tool handler)
步骤 1:创建 ScreenCaptureToolHandler
在 Android Studio 中加载 viewer_ctrl_demo,在 com.foxit.pdf.viewctrl 包下创建 ScreenCaptureToolHandler,并让其实现 ToolHandler 接口。
步骤 2:处理 onTouchEvent 与 onDraw
下面给出 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.java 的 onActionItemClicked() 中切换工具。
1) 在 Main.xml(app/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);
}
运行与验证
完成以上步骤后,即可运行并验证工具是否生效
- 确保已在代码中提前声明并持有
ScreenCaptureToolHandler实例引用,例如:private ScreenCaptureToolHandler screenCapture = null;
- 构建并运行
viewer_ctrl_demo。 - 注意:请确保已将
Sample.pdf推送到设备/模拟器 SD 卡中与示例工程路径一致的目录(示例工程通常使用/FoxitSDK/Sample.pdf)。 - 安装完成后,按系统弹窗提示点击 Allow,允许应用访问文件。
- 文档打开后,点击页面任意位置唤出"页面菜单",找到并点击 ScreenCapture。
- 在页面上长按并拖动选择矩形区域。完成后会弹出提示,告知截图保存位置。
- 在 IDE 的 Device Explorer 中进入设备存储的
FoxitSDK目录,确认已生成ScreenCapture.bmp。可将其导出到本地后查看内容是否符合预期。