├── .idea
├── .name
├── copyright
│ └── profiles_settings.xml
├── encodings.xml
├── vcs.xml
├── modules.xml
├── runConfigurations.xml
├── compiler.xml
├── gradle.xml
└── misc.xml
├── settings.gradle
├── app
├── libs
│ └── droidText.0.5.jar
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── styles.xml
│ │ │ ├── drawable
│ │ │ │ ├── qianming.png
│ │ │ │ ├── darkdenim3.png
│ │ │ │ ├── qianming1.png
│ │ │ │ ├── icon_pdf_save.png
│ │ │ │ ├── icon_pdf_erasure.png
│ │ │ │ ├── icon_pdf_lastone.png
│ │ │ │ ├── icon_pdf_submit.png
│ │ │ │ └── icon_pdf_signature.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ └── layout
│ │ │ │ ├── pdfbgm.xml
│ │ │ │ ├── signature_layout.xml
│ │ │ │ ├── read_pdf_main.xml
│ │ │ │ └── activity_pdf.xml
│ │ ├── jniLibs
│ │ │ └── armeabi
│ │ │ │ └── libmupdf.so
│ │ ├── java
│ │ │ └── com
│ │ │ │ ├── artifex
│ │ │ │ └── mupdf
│ │ │ │ │ ├── LinkInfo.java
│ │ │ │ │ ├── DimenUtils.java
│ │ │ │ │ ├── MuPDFPageView.java
│ │ │ │ │ ├── MuPDFPageAdapter.java
│ │ │ │ │ ├── MuPDFCore.java
│ │ │ │ │ ├── SavePdf.java
│ │ │ │ │ ├── PageView.java
│ │ │ │ │ └── ReaderView.java
│ │ │ │ └── example
│ │ │ │ └── jammy
│ │ │ │ └── pdf_demo
│ │ │ │ ├── SignatureView.java
│ │ │ │ └── PDFActivity.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── jammy
│ │ │ └── pdf_demo
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── jammy
│ │ └── pdf_demo
│ │ └── ApplicationTest.java
├── .gitignore
├── proguard-rules.pro
└── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── README.md
├── gradle.properties
├── gradlew.bat
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | PDF_Demo
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/app/libs/droidText.0.5.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/libs/droidText.0.5.jar
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | PDF_Demo
3 |
4 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/qianming.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/drawable/qianming.png
--------------------------------------------------------------------------------
/app/src/main/jniLibs/armeabi/libmupdf.so:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/jniLibs/armeabi/libmupdf.so
--------------------------------------------------------------------------------
/app/src/main/res/drawable/darkdenim3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/drawable/darkdenim3.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/qianming1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/drawable/qianming1.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_pdf_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/drawable/icon_pdf_save.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_pdf_erasure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/drawable/icon_pdf_erasure.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_pdf_lastone.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/drawable/icon_pdf_lastone.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_pdf_submit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/drawable/icon_pdf_submit.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_pdf_signature.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/drawable/icon_pdf_signature.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JammyR/PDF_Demo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PDF_Demo
2 |
3 | 一个实现PDF文件涂鸦/签名的Demo,可以做到浏览PDF和在PDF上面进行涂鸦操作,并且没有偏移。
4 |
5 | 具体描述见我的CSDN博客:http://blog.csdn.net/r_rito/article/details/52040427
6 |
7 | 由于我没有做文件选择的功能,只能手动在代码中更改要打开的PDF以及签名后的PDF保存位置
8 | 具体是in_path是要打开的PDF的路径,
9 | out_path是保存的PDF的路径
10 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/artifex/mupdf/LinkInfo.java:
--------------------------------------------------------------------------------
1 | package com.artifex.mupdf;
2 |
3 | import android.graphics.RectF;
4 |
5 | public class LinkInfo extends RectF {
6 | public static int pageNumber;
7 |
8 | public LinkInfo(float l, float t, float r, float b, int p) {
9 | super(l, t, r, b);
10 | pageNumber = p;
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/jammy/pdf_demo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.jammy.pdf_demo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/pdfbgm.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | # built application files
2 | *.apk
3 | *.ap_
4 |
5 | # files for the dex VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # generated files
12 | bin/
13 | gen/
14 |
15 | # Local configuration file (sdk path, etc)
16 | local.properties
17 |
18 | # Eclipse project files
19 | .classpath
20 | .project
21 |
22 | # Proguard folder generated by Eclipse
23 | proguard/
24 |
25 | # Intellij project files
26 | *.iml
27 | *.ipr
28 | *.iws
29 | .idea/
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/jammy/pdf_demo/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.example.jammy.pdf_demo;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/signature_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in E:\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | applicationId "com.example.jammy.pdf_demo"
9 | minSdkVersion 15
10 | targetSdkVersion 22
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(include: ['*.jar'], dir: 'libs')
24 | testCompile 'junit:junit:4.12'
25 | compile 'com.android.support:appcompat-v7:23.3.0'
26 | compile files('libs/droidText.0.5.jar')
27 | compile 'com.jakewharton:butterknife:7.0.0'
28 | }
29 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/artifex/mupdf/DimenUtils.java:
--------------------------------------------------------------------------------
1 | package com.artifex.mupdf;
2 |
3 | /**
4 | * Created by Jammy on 2016/7/15.
5 | */
6 | import android.content.Context;
7 |
8 | public class DimenUtils {
9 |
10 | public static int sp2px(Context context, float spValue) {
11 | float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
12 | return (int) (spValue * fontScale + 0.5f);
13 | }
14 |
15 | public static int px2sp(Context context, float pxValue) {
16 | float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
17 | return (int) (pxValue / fontScale + 0.5f);
18 | }
19 |
20 | public static int dip2px(Context context, int dipValue) {
21 | final float scale = context.getResources().getDisplayMetrics().density;
22 | return (int) (dipValue * scale + 0.5f);
23 | }
24 |
25 | public static int px2dip(Context context, float pxValue) {
26 | final float scale = context.getResources().getDisplayMetrics().density;
27 | return (int) (pxValue / scale + 0.5f);
28 | }
29 |
30 |
31 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/artifex/mupdf/MuPDFPageView.java:
--------------------------------------------------------------------------------
1 | package com.artifex.mupdf;
2 |
3 |
4 | import android.content.Context;
5 | import android.graphics.Bitmap;
6 | import android.graphics.Point;
7 |
8 | public class MuPDFPageView extends PageView {
9 | private final MuPDFCore mCore;
10 | public static int pdfSizeX;
11 | public static int pdfSizeY;
12 | public static int pdfPatchX;
13 | public static int pdfPatchY;
14 | public static int pdfPatchWidth;
15 | public static int pdfPatchHeight;
16 | public MuPDFPageView(Context c, MuPDFCore core, Point parentSize) {
17 | super(c, parentSize);
18 | mCore = core;
19 | }
20 |
21 | public int hitLinkPage(float x, float y) {
22 | // Since link highlighting was implemented, the super class
23 | // PageView has had sufficient information to be able to
24 | // perform this method directly. Making that change would
25 | // make MuPDFCore.hitLinkPage superfluous.
26 | float scale = mSourceScale*(float)getWidth()/(float)mSize.x;
27 | float docRelX = (x - getLeft())/scale;
28 | float docRelY = (y - getTop())/scale;
29 | // Log.v("info", "Page--->",docRelX);
30 | return mCore.hitLinkPage(mPageNumber, docRelX, docRelY);
31 | }
32 |
33 | @Override
34 | protected void drawPage(Bitmap bm, int sizeX, int sizeY,
35 | int patchX, int patchY, int patchWidth, int patchHeight) {
36 | mCore.drawPage(mPageNumber, bm, sizeX, sizeY, patchX, patchY, patchWidth, patchHeight);
37 | pdfSizeX = sizeX;
38 | pdfSizeY = sizeY;
39 | pdfPatchX = patchX;
40 | pdfPatchY = patchY;
41 | pdfPatchWidth = patchWidth;
42 | pdfPatchHeight = patchHeight;
43 | }
44 |
45 | @Override
46 | protected LinkInfo[] getLinkInfo() {
47 | return mCore.getPageLinks(mPageNumber);
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/read_pdf_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
21 |
29 |
30 |
37 |
38 |
47 |
48 |
49 |
50 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/jammy/pdf_demo/SignatureView.java:
--------------------------------------------------------------------------------
1 | package com.example.jammy.pdf_demo;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.Paint;
7 | import android.graphics.Path;
8 | import android.util.AttributeSet;
9 | import android.view.MotionEvent;
10 | import android.view.View;
11 |
12 | /**
13 | * Created by Jammy on 2016/6/23.
14 | */
15 | public class SignatureView extends View {
16 | Path path;
17 | Paint paint;
18 |
19 | private float clickX = 0, clickY = 0;
20 | private float startX = 0, startY = 0;
21 |
22 | public SignatureView(Context context) {
23 | super(context);
24 | init();
25 | }
26 |
27 | public SignatureView(Context context, AttributeSet attrs) {
28 | super(context, attrs);
29 | init();
30 | }
31 |
32 | public SignatureView(Context context, AttributeSet attrs, int defStyleAttr) {
33 | super(context, attrs, defStyleAttr);
34 | init();
35 | }
36 |
37 |
38 | @Override
39 | protected void onDraw(Canvas canvas) {
40 | canvas.drawPath(path, paint);
41 | }
42 |
43 |
44 | @Override
45 | public boolean onTouchEvent(MotionEvent event) {
46 | startX = event.getX();
47 | startY = event.getY();
48 |
49 | switch (event.getAction()) {
50 | case MotionEvent.ACTION_DOWN:
51 | clickX = startX;
52 | clickY = startY;
53 | path.moveTo(startX, startY);
54 | invalidate();
55 | return true;
56 | case MotionEvent.ACTION_MOVE:
57 | path.quadTo(clickX, clickY, (clickX + startX) / 2, (clickY + startY) / 2);
58 | clickX = startX;
59 | clickY = startY;
60 | invalidate();
61 | return true;
62 | case MotionEvent.ACTION_UP:
63 | return true;
64 | default:
65 | break;
66 | }
67 | return super.onTouchEvent(event);
68 | }
69 |
70 | public void init() {
71 | path = new Path();
72 | paint = new Paint();
73 | paint.setColor(Color.BLACK);
74 | paint.setStyle(Paint.Style.STROKE);////////一定要设置这个才可以画直线
75 | paint.setStrokeWidth(8);
76 | paint.setAntiAlias(true);
77 | // this.setBackgroundColor(Color.BLUE);
78 | }
79 |
80 | /**
81 | * 清空画板
82 | */
83 | public void clear() {
84 | path.reset();
85 | invalidate();
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | 1.8
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/app/src/main/java/com/artifex/mupdf/MuPDFPageAdapter.java:
--------------------------------------------------------------------------------
1 | package com.artifex.mupdf;
2 |
3 | import android.content.Context;
4 | import android.graphics.Point;
5 | import android.graphics.PointF;
6 | import android.os.AsyncTask;
7 | import android.util.SparseArray;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.BaseAdapter;
11 |
12 | public class MuPDFPageAdapter extends BaseAdapter {
13 | private final Context mContext;
14 | private final MuPDFCore mCore;
15 | private final SparseArray mPageSizes = new SparseArray();
16 |
17 | public MuPDFPageAdapter(Context c, MuPDFCore core) {
18 | mContext = c;
19 | mCore = core;
20 | }
21 |
22 | public int getCount() {
23 | return mCore.countPages();
24 | }
25 |
26 | public Object getItem(int position) {
27 | return null;
28 | }
29 |
30 | public long getItemId(int position) {
31 | return 0;
32 | }
33 |
34 | public View getView(final int position, View convertView, ViewGroup parent) {
35 | final MuPDFPageView pageView;
36 | if (convertView == null) {
37 | pageView = new MuPDFPageView(mContext, mCore, new Point(parent.getWidth(), parent.getHeight()));
38 | } else {
39 | pageView = (MuPDFPageView) convertView;
40 | }
41 |
42 | PointF pageSize = mPageSizes.get(position);
43 | if (pageSize != null) {
44 | // We already know the page size. Set it up
45 | // immediately
46 | pageView.setPage(position, pageSize);
47 | } else {
48 | // Page size as yet unknown. Blank it for now, and
49 | // start a background task to find the size
50 | //这是把他设置成繁忙的白板
51 | pageView.blank(position);
52 |
53 | AsyncTask sizingTask = new AsyncTask() {
54 |
55 |
56 | @Override
57 | protected void onPreExecute() {
58 | //TODO 弹出对话框
59 | super.onPreExecute();
60 | }
61 |
62 | @Override
63 | protected PointF doInBackground(Void... arg0) {
64 | return mCore.getPageSize(position);
65 | }
66 |
67 | @Override
68 | protected void onPostExecute(PointF result) {
69 | super.onPostExecute(result);
70 | //TODO 关闭对话框
71 | // We now know the page size
72 | mPageSizes.put(position, result);
73 | // Check that this view hasn't been reused for
74 | // another page since we started
75 | if (pageView.getPage() == position)
76 | pageView.setPage(position, result);
77 | }
78 | };
79 | try
80 | {
81 | sizingTask.execute((Void)null);
82 | }
83 | catch (java.util.concurrent.RejectedExecutionException e)
84 | {
85 | // If we can't do it in the background, just
86 | // do it in the foreground.
87 | PointF result = mCore.getPageSize(position);
88 | mPageSizes.put(position, result);
89 | // Check that this view hasn't been reused for
90 | // another page since we started
91 | if (pageView.getPage() == position)
92 | pageView.setPage(position, result);
93 | }
94 | }
95 | return pageView;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/com/artifex/mupdf/MuPDFCore.java:
--------------------------------------------------------------------------------
1 | package com.artifex.mupdf;
2 | import android.graphics.Bitmap;
3 | import android.graphics.PointF;
4 | import android.graphics.RectF;
5 |
6 | public class MuPDFCore
7 | {
8 | /* load our native library */
9 | static {
10 | System.loadLibrary("mupdf");
11 | }
12 |
13 | /* Readable members */
14 | private int pageNum = -1;;
15 | private int numPages = -1;
16 | public float pageWidth;
17 | public float pageHeight;
18 |
19 | /* The native functions */
20 | private static native int openFile(String filename);
21 | private static native int countPagesInternal();
22 | private static native void gotoPageInternal(int localActionPageNum);
23 | private static native float getPageWidth();
24 | private static native float getPageHeight();
25 | public static native void drawPage(Bitmap bitmap,
26 | int pageW, int pageH,
27 | int patchX, int patchY,
28 | int patchW, int patchH);
29 | public static native RectF[] searchPage(String text);
30 | public static native int getPageLink(int page, float x, float y);
31 | public static native LinkInfo[] getPageLinksInternal(int page);
32 | public static native boolean hasOutlineInternal();
33 | public static native boolean needsPasswordInternal();
34 | public static native boolean authenticatePasswordInternal(String password);
35 | public static native void destroying();
36 |
37 | public MuPDFCore(String filename) throws Exception
38 | {
39 | if (openFile(filename) <= 0)
40 | {
41 | throw new Exception("Failed to open "+filename);
42 | }
43 | }
44 |
45 | public int countPages()
46 | {
47 | if (numPages < 0)
48 | numPages = countPagesSynchronized();
49 |
50 | return numPages;
51 | }
52 |
53 | private synchronized int countPagesSynchronized() {
54 | return countPagesInternal();
55 | }
56 |
57 | /* Shim function */
58 | public void gotoPage(int page)
59 | {
60 | if (page > numPages-1)
61 | page = numPages-1;
62 | else if (page < 0)
63 | page = 0;
64 | if (this.pageNum == page)
65 | return;
66 | gotoPageInternal(page);
67 | this.pageNum = page;
68 | this.pageWidth = getPageWidth();
69 | this.pageHeight = getPageHeight();
70 | }
71 |
72 | public synchronized PointF getPageSize(int page) {
73 | gotoPage(page);
74 | return new PointF(pageWidth, pageHeight);
75 | }
76 |
77 | public synchronized void onDestroy() {
78 | destroying();
79 | }
80 |
81 | public synchronized void drawPage(int page, Bitmap bitmap,
82 | int pageW, int pageH,
83 | int patchX, int patchY,
84 | int patchW, int patchH) {
85 | gotoPage(page);
86 | drawPage(bitmap, pageW, pageH, patchX, patchY, patchW, patchH);
87 | }
88 |
89 | public synchronized int hitLinkPage(int page, float x, float y) {
90 | return getPageLink(page, x, y);
91 | }
92 |
93 | public synchronized LinkInfo[] getPageLinks(int page) {
94 | return getPageLinksInternal(page);
95 | }
96 |
97 | public synchronized RectF[] searchPage(int page, String text) {
98 | gotoPage(page);
99 | return searchPage(text);
100 | }
101 |
102 | public synchronized boolean hasOutline() {
103 | return hasOutlineInternal();
104 | }
105 |
106 | public synchronized boolean needsPassword() {
107 | return needsPasswordInternal();
108 | }
109 |
110 | public synchronized boolean authenticatePassword(String password) {
111 | return authenticatePasswordInternal(password);
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/app/src/main/java/com/artifex/mupdf/SavePdf.java:
--------------------------------------------------------------------------------
1 | package com.artifex.mupdf;
2 |
3 | import android.content.Context;
4 | import android.graphics.Bitmap;
5 | import android.util.Log;
6 |
7 | import com.lowagie.text.BadElementException;
8 | import com.lowagie.text.DocumentException;
9 | import com.lowagie.text.Image;
10 | import com.lowagie.text.pdf.PdfArray;
11 | import com.lowagie.text.pdf.PdfContentByte;
12 | import com.lowagie.text.pdf.PdfDictionary;
13 | import com.lowagie.text.pdf.PdfName;
14 | import com.lowagie.text.pdf.PdfObject;
15 | import com.lowagie.text.pdf.PdfReader;
16 | import com.lowagie.text.pdf.PdfStamper;
17 |
18 | import java.io.ByteArrayOutputStream;
19 | import java.io.FileNotFoundException;
20 | import java.io.FileOutputStream;
21 | import java.io.IOException;
22 | import java.net.MalformedURLException;
23 |
24 |
25 | /**
26 | * Created by Jammy on 2016/6/23.
27 | */
28 | public class SavePdf {
29 |
30 | public void setWidthScale(float widthScale) {
31 | this.widthScale = widthScale;
32 | }
33 |
34 | public void setHeightScale(float heightScale) {
35 | this.heightScale = heightScale;
36 | }
37 |
38 | float widthScale;
39 | float heightScale;
40 | String inPath;/////当前的PDF地址
41 | String outPath;////要输出的PDF地址
42 | private int pageNum;/////签名所在的页码
43 | private Bitmap bitmap;//////签名图像
44 | private float scale;
45 | private float density; ///手机屏幕的分辨率密度
46 |
47 | /**
48 | * 设置放大比例
49 | * @param scale
50 | */
51 | public void setScale(float scale) {
52 | this.scale = scale;
53 | }
54 |
55 |
56 | /**
57 | * 设置分辨率密度
58 | *
59 | * @param density
60 | */
61 | public void setDensity(float density) {
62 | this.density = density;
63 | }
64 |
65 | /**
66 | * 设置嵌入的图片
67 | *
68 | * @param bitmap
69 | */
70 | public void setBitmap(Bitmap bitmap) {
71 | this.bitmap = bitmap;
72 | }
73 |
74 | /**
75 | * 设置需要嵌入的页面
76 | *
77 | * @param pageNum
78 | */
79 | public void setPageNum(int pageNum) {
80 | this.pageNum = pageNum;
81 | }
82 |
83 | public SavePdf(String inPath, String outPath) {
84 | this.inPath = inPath;
85 | this.outPath = outPath;
86 | }
87 |
88 | /**
89 | * 将图片加入PDF并保存
90 | */
91 | public void addText() {
92 | try {
93 | PdfReader reader = new PdfReader(inPath, "PDF".getBytes());///打开要写入的PDF
94 | FileOutputStream outputStream = new FileOutputStream(outPath);//设置涂鸦后的PDF
95 | PdfStamper stamp;
96 | stamp = new PdfStamper(reader, outputStream);
97 | PdfContentByte over = stamp.getOverContent(pageNum);//////用于设置在第几页打印签名
98 | byte[] bytes = Bitmap2Bytes(bitmap);
99 | Image img = Image.getInstance(bytes);//将要放到PDF的图片传过来,要设置为byte[]类型
100 | com.lowagie.text.Rectangle rectangle = reader.getPageSize(pageNum);
101 | img.setAlignment(1);
102 | //这里是重点!!!!!设置Image图片大小,需要根据屏幕的分辨率,签名时PDF的放大比例来计算;还有就是当PDF开始显示的时候,他已经做了一次缩放,可以用 rectangle.getWidth() / (bitmap.getWidth() / 2)求得那个放大比
103 | img.scaleAbsolute(363 * 1.0f * density / 2 / scale * rectangle.getWidth() / (bitmap.getWidth() / 2), 557 * 1.0f * density / 2 / scale * rectangle.getWidth() / (bitmap.getWidth() / 2));
104 | //这里设置image相对PDF左下角的偏移量,我的做法是得到放大后位置相对于整个PDF的百分比再乘PDF的大小得到他的相对偏移位置
105 | img.setAbsolutePosition(rectangle.getWidth() * widthScale, rectangle.getHeight() * heightScale);
106 | over.addImage(img);
107 | stamp.close();
108 | } catch (FileNotFoundException e) {
109 | e.printStackTrace();
110 | } catch (MalformedURLException e) {
111 | e.printStackTrace();
112 | } catch (IOException e) {
113 | e.printStackTrace();
114 | } catch (BadElementException e) {
115 | e.printStackTrace();
116 | } catch (DocumentException e) {
117 | e.printStackTrace();
118 | }
119 | }
120 |
121 | /**
122 | * 将BitMap转换为Bytes
123 | *
124 | * @param bm
125 | * @return
126 | */
127 | public byte[] Bitmap2Bytes(Bitmap bm) {
128 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
129 | bm.compress(Bitmap.CompressFormat.PNG, 100, baos);
130 | return baos.toByteArray();
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_pdf.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
13 |
14 |
15 |
16 |
22 |
23 |
24 |
30 |
31 |
39 |
40 |
49 |
50 |
51 |
56 |
57 |
65 |
66 |
75 |
76 |
77 |
84 |
85 |
93 |
94 |
103 |
104 |
105 |
113 |
114 |
122 |
123 |
132 |
133 |
134 |
140 |
141 |
149 |
150 |
159 |
160 |
161 |
162 |
168 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/jammy/pdf_demo/PDFActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.jammy.pdf_demo;
2 |
3 | import android.annotation.TargetApi;
4 | import android.app.Activity;
5 | import android.app.AlertDialog;
6 | import android.app.ProgressDialog;
7 | import android.content.DialogInterface;
8 | import android.graphics.Bitmap;
9 | import android.graphics.Canvas;
10 | import android.os.AsyncTask;
11 | import android.os.Build;
12 | import android.os.Bundle;
13 | import android.os.Environment;
14 | import android.util.DisplayMetrics;
15 | import android.view.LayoutInflater;
16 | import android.view.View;
17 | import android.view.ViewGroup;
18 | import android.widget.PopupWindow;
19 | import android.widget.RelativeLayout;
20 | import android.widget.Toast;
21 |
22 | import com.artifex.mupdf.MuPDFCore;
23 | import com.artifex.mupdf.MuPDFPageAdapter;
24 | import com.artifex.mupdf.ReaderView;
25 | import com.artifex.mupdf.SavePdf;
26 |
27 | import java.io.File;
28 |
29 | import butterknife.Bind;
30 | import butterknife.ButterKnife;
31 |
32 |
33 | /**
34 | * Created by Jammy on 2016/6/23.
35 | */
36 | public class PDFActivity extends Activity {
37 |
38 | @Bind(R.id.readerView)
39 | ReaderView readerView;
40 |
41 | @Bind(R.id.rl_back)
42 | RelativeLayout rlBack;
43 | @Bind(R.id.rl_sign)
44 | RelativeLayout rlSign;
45 | @Bind(R.id.rl_update)
46 | RelativeLayout rlUpdate;
47 | @Bind(R.id.rl_clear)
48 | RelativeLayout rlClear;
49 | @Bind(R.id.rl_save)
50 | RelativeLayout rlSave;
51 |
52 | boolean isUpdate = false;
53 | String in_path;
54 | String out_path;
55 | String update_path;
56 | PopupWindow popupWindow;
57 | SignatureView signatureView;
58 | boolean iBack = false;
59 | float density; //屏幕分辨率密度
60 | int first = 1;
61 | int back_first = 0;
62 | String file_id;
63 | ProgressDialog progressDialog;
64 | MuPDFCore muPDFCore;
65 | Save_Pdf save_pdf;
66 |
67 |
68 | @Override
69 | public void onCreate(Bundle savedInstanceState) {
70 | super.onCreate(savedInstanceState);
71 | setContentView(R.layout.activity_pdf);
72 | ButterKnife.bind(this);
73 |
74 | View screenView = this.getWindow().getDecorView();
75 | screenView.setDrawingCacheEnabled(true);
76 | screenView.buildDrawingCache();
77 |
78 | //计算分辨率密度
79 | DisplayMetrics metric = new DisplayMetrics();
80 | getWindowManager().getDefaultDisplay().getMetrics(metric);
81 | density = metric.density;
82 |
83 |
84 | in_path = Environment.getExternalStorageDirectory().getPath() + "/123.pdf";
85 | out_path = in_path.substring(0, in_path.length() - 4) + "1.pdf";
86 |
87 |
88 | try {
89 | muPDFCore = new MuPDFCore(in_path);//PDF的文件路径
90 | readerView.setAdapter(new MuPDFPageAdapter(this, muPDFCore));
91 | View view = LayoutInflater.from(this).inflate(R.layout.signature_layout, null);
92 | signatureView = (SignatureView) view.findViewById(R.id.qianming);
93 | readerView.setDisplayedViewIndex(0);
94 | popupWindow = new PopupWindow(view, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, false);
95 | rlSign.setOnClickListener(new View.OnClickListener() {
96 | @TargetApi(Build.VERSION_CODES.KITKAT)
97 | @Override
98 | public void onClick(View v) {
99 | if (rlSave.getVisibility() == View.GONE) {
100 | popupWindow.showAsDropDown(rlSign, 0, 0);
101 | rlSave.setVisibility(View.VISIBLE);
102 | rlClear.setVisibility(View.VISIBLE);
103 | rlUpdate.setVisibility(View.VISIBLE);
104 | } else {
105 | popupWindow.dismiss();
106 | signatureView.clear();
107 | rlSave.setVisibility(View.GONE);
108 | rlClear.setVisibility(View.GONE);
109 | rlUpdate.setVisibility(View.GONE);
110 | }
111 | }
112 | });
113 | rlSave.setOnClickListener(new View.OnClickListener() {
114 | @Override
115 | public void onClick(View v) {
116 | float scale = readerView.getmScale();///得到放大因子
117 | SavePdf savePdf = new SavePdf(in_path, out_path);
118 | savePdf.setScale(scale);
119 | savePdf.setPageNum(readerView.getDisplayedViewIndex() + 1);
120 |
121 | savePdf.setWidthScale(1.0f * readerView.scrollX / readerView.getDisplayedView().getWidth());//计算宽偏移的百分比
122 | savePdf.setHeightScale(1.0f * readerView.scrollY / readerView.getDisplayedView().getHeight());//计算长偏移的百分比
123 |
124 |
125 | savePdf.setDensity(density);
126 | Bitmap bitmap = Bitmap.createBitmap(signatureView.getWidth(), signatureView.getHeight(),
127 | Bitmap.Config.ARGB_8888);
128 | Canvas canvas = new Canvas(bitmap);
129 | signatureView.draw(canvas);
130 | savePdf.setBitmap(bitmap);
131 | save_pdf = new Save_Pdf(savePdf);
132 | save_pdf.execute();
133 | popupWindow.dismiss();
134 | iBack = true;
135 | rlSave.setVisibility(View.GONE);
136 | rlClear.setVisibility(View.GONE);
137 | rlUpdate.setVisibility(View.GONE);
138 | rlBack.setVisibility(View.VISIBLE);
139 | ///显示隐藏probar
140 | progressDialog = ProgressDialog.show(PDFActivity.this, null, "正在存储...");
141 | signatureView.clear();
142 | }
143 | });
144 |
145 |
146 | rlBack.setOnClickListener(new View.OnClickListener() {
147 | @Override
148 | public void onClick(View v) {
149 | try {
150 | if (iBack) {
151 | if (back_first == 1) {
152 | muPDFCore = new MuPDFCore(getIntent().getExtras().getString("inPath"));
153 | first = 1;
154 | in_path = getIntent().getExtras().getString("inPath");
155 | out_path = in_path.substring(0, in_path.length() - 4) + "1.pdf";
156 | } else {
157 | muPDFCore = new MuPDFCore(out_path);
158 | String temp = in_path;
159 | in_path = out_path;
160 | out_path = temp;
161 | }
162 | readerView.setmScale(1.0f);
163 | readerView.setDisplayedViewIndex(readerView.getDisplayedViewIndex());
164 | iBack = false;
165 | if (rlSave.getVisibility() == View.VISIBLE) {
166 | rlSave.setVisibility(View.GONE);
167 | rlClear.setVisibility(View.GONE);
168 | rlUpdate.setVisibility(View.GONE);
169 | signatureView.clear();
170 | }
171 | rlBack.setVisibility(View.GONE);
172 | back_first--;
173 | } else {
174 | AlertDialog.Builder builder = new AlertDialog.Builder(PDFActivity.this);
175 | builder.setTitle("无法返回");
176 | builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
177 | @Override
178 | public void onClick(DialogInterface dialog, int which) {
179 | //删除缓冲的存储
180 | dialog.cancel();
181 | }
182 | }).show();
183 | }
184 | } catch (Exception e) {
185 | e.printStackTrace();
186 | }
187 | }
188 | });
189 |
190 | rlClear.setOnClickListener(new View.OnClickListener() {
191 | @Override
192 | public void onClick(View v) {
193 | signatureView.clear();
194 | }
195 | });
196 | rlUpdate.setOnClickListener(new View.OnClickListener() {
197 | @Override
198 | public void onClick(View v) {
199 | Toast.makeText(PDFActivity.this,"这里没有用到",Toast.LENGTH_SHORT).show();
200 | }
201 | });
202 |
203 | } catch (Exception e) {
204 | e.printStackTrace();
205 | }
206 | }
207 |
208 |
209 | /*
210 | * 用于存储的异步,并上传更新
211 | * */
212 | class Save_Pdf extends AsyncTask {
213 |
214 | SavePdf savePdf;
215 |
216 | public Save_Pdf(SavePdf savePdf) {
217 | this.savePdf = savePdf;
218 | }
219 |
220 | @Override
221 | protected Object doInBackground(Object[] params) {
222 | savePdf.addText();
223 | if (first == 1) {
224 | update_path = in_path.substring(0, in_path.length() - 4) + ".pdf";
225 | in_path = in_path.substring(0, in_path.length() - 4) + "2.pdf";
226 | first++;
227 | }
228 | return null;
229 | }
230 |
231 | @Override
232 | protected void onPostExecute(Object o) {
233 | Toast.makeText(PDFActivity.this, "存储完成", Toast.LENGTH_SHORT).show();
234 | try {
235 | muPDFCore = new MuPDFCore(out_path);
236 | readerView.setAdapter(new MuPDFPageAdapter(PDFActivity.this, muPDFCore));
237 |
238 | String temp = in_path;
239 | in_path = out_path;
240 | out_path = temp;
241 | readerView.setmScale(1.0f);
242 | readerView.setDisplayedViewIndex(readerView.getDisplayedViewIndex());
243 | progressDialog.dismiss();
244 | back_first++;
245 | } catch (Exception e) {
246 | e.printStackTrace();
247 | }
248 | }
249 | }
250 |
251 | /**
252 | * 返回按钮,退出时删除那两个文件
253 | */
254 | @Override
255 | public void onBackPressed() {
256 | AlertDialog.Builder builder = new AlertDialog.Builder(this);
257 | builder.setTitle("是否退出?");
258 | builder.setPositiveButton("是的", new DialogInterface.OnClickListener() {
259 | @Override
260 | public void onClick(DialogInterface dialog, int which) {
261 | //删除缓冲的存储
262 | PDFActivity.this.finish();
263 | }
264 | }).setNegativeButton("取消", new DialogInterface.OnClickListener() {
265 | @Override
266 | public void onClick(DialogInterface dialog, int which) {
267 | dialog.cancel();
268 | }
269 | }).show();
270 | }
271 |
272 | @Override
273 | protected void onDestroy() {
274 | super.onDestroy();
275 | if (first != 1) {
276 | File file = new File(in_path);
277 | File file1 = new File(out_path);
278 | File file2 = new File(update_path);
279 | if (file.exists()) file.delete();
280 | if (file1.exists()) file1.delete();
281 | if (file2.exists() && isUpdate) file2.delete();
282 | }
283 | save_pdf.cancel(true);
284 | }
285 | }
286 |
--------------------------------------------------------------------------------
/app/src/main/java/com/artifex/mupdf/PageView.java:
--------------------------------------------------------------------------------
1 | package com.artifex.mupdf;
2 |
3 |
4 | import android.app.ProgressDialog;
5 | import android.content.Context;
6 | import android.graphics.Bitmap;
7 | import android.graphics.Canvas;
8 | import android.graphics.Paint;
9 | import android.graphics.Point;
10 | import android.graphics.PointF;
11 | import android.graphics.Rect;
12 | import android.graphics.RectF;
13 | import android.os.AsyncTask;
14 | import android.os.Build;
15 | import android.os.Handler;
16 | import android.view.View;
17 | import android.view.ViewGroup;
18 | import android.widget.ImageView;
19 | import android.widget.ProgressBar;
20 |
21 |
22 | class PatchInfo {
23 | public Bitmap bm;
24 | public Point patchViewSize;
25 | public Rect patchArea;
26 |
27 | public PatchInfo(Bitmap aBm, Point aPatchViewSize, Rect aPatchArea) {
28 | bm = aBm;
29 | patchViewSize = aPatchViewSize;
30 | patchArea = aPatchArea;
31 | }
32 | }
33 |
34 | // Make our ImageViews opaque to optimize redraw
35 | class OpaqueImageView extends ImageView {
36 |
37 | public OpaqueImageView(Context context) {
38 | super(context);
39 | }
40 |
41 | @Override
42 | public boolean isOpaque() {
43 | return true;
44 | }
45 | }
46 |
47 | /***
48 | * ��Ŀ��ElectronicSignature
49 | * ������PageView
50 | * ���ܣ�����pdf
51 | * ����ʱ�䣺2013-12-11
52 | * �����ˣ�LXH
53 | */
54 |
55 | public abstract class PageView extends ViewGroup {
56 | private static final int HIGHLIGHT_COLOR = 0x805555FF;
57 | private static final int LINK_COLOR = 0x80FFCC88;
58 | private static final int BACKGROUND_COLOR = 0xFFFFFFFF;
59 | private static final int PROGRESS_DIALOG_DELAY = 200;
60 | private final Context mContext;
61 | protected int mPageNumber;
62 | private Point mParentSize;
63 |
64 | protected Point mSize; // Size of page at minimum zoom
65 | protected float mSourceScale;
66 |
67 | private ImageView mEntire; // Image rendered at minimum zoom
68 | private Bitmap mEntireBm;
69 | private AsyncTask mDrawEntire;
70 |
71 | private Point mPatchViewSize; // View size on the basis of which the patch was created
72 | private Rect mPatchArea;
73 | private ImageView mPatch;
74 | private AsyncTask mDrawPatch;
75 | private RectF mSearchBoxes[];
76 | private LinkInfo mLinks[];
77 | private View mSearchView;
78 | private boolean mIsBlank;
79 | private boolean mUsingHardwareAcceleration;
80 | private boolean mHighlightLinks;
81 |
82 | private ProgressBar mBusyIndicator;
83 | private final Handler mHandler = new Handler();
84 | ProgressDialog progressDialog;
85 | public PageView(Context c, Point parentSize) {
86 | super(c);
87 | mContext = c;
88 | mParentSize = parentSize;
89 | setBackgroundColor(BACKGROUND_COLOR);
90 | mUsingHardwareAcceleration = Build.VERSION.SDK != null
91 | && Integer.valueOf(Build.VERSION.SDK) >= 14;
92 | }
93 |
94 | protected abstract void drawPage(Bitmap bm, int sizeX, int sizeY, int patchX, int patchY, int patchWidth, int patchHeight);
95 | protected abstract LinkInfo[] getLinkInfo();
96 |
97 | public void blank(int page) {
98 | // Cancel pending render task
99 | if (mDrawEntire != null) {
100 | mDrawEntire.cancel(true);
101 | mDrawEntire = null;
102 | }
103 |
104 | mIsBlank = true;
105 | mPageNumber = page;
106 |
107 | if (mSize == null)
108 | mSize = mParentSize;
109 |
110 | if (mEntire != null)
111 | mEntire.setImageBitmap(null);
112 |
113 | if (mPatch != null)
114 | mPatch.setImageBitmap(null);
115 |
116 | if (mBusyIndicator == null) {
117 | mBusyIndicator = new ProgressBar(mContext);
118 | mBusyIndicator.setIndeterminate(true);
119 | // mBusyIndicator.setBackgroundResource(R.drawable.busy);
120 | addView(mBusyIndicator);
121 | }
122 | }
123 |
124 | public void setPage(int page, PointF size) {
125 | // Cancel pending render task
126 | if (mDrawEntire != null) {
127 | mDrawEntire.cancel(true);
128 | mDrawEntire = null;
129 | }
130 |
131 | mIsBlank = false;
132 |
133 | mPageNumber = page;
134 | if (mEntire == null) {
135 | mEntire = new OpaqueImageView(mContext);
136 | mEntire.setScaleType(ImageView.ScaleType.FIT_CENTER);
137 | addView(mEntire);
138 | }
139 |
140 | // Calculate scaled size that fits within the screen limits
141 | // This is the size at minimum zoom
142 | mSourceScale = Math.min(mParentSize.x/size.x, mParentSize.y/size.y);
143 | Point newSize = new Point((int)(size.x*mSourceScale), (int)(size.y*mSourceScale));
144 | mSize = newSize;
145 |
146 | if (mUsingHardwareAcceleration) {
147 | // When hardware accelerated, updates to the bitmap seem to be
148 | // ignored, so we recreate it. There may be another way around this
149 | // that we are yet to find.
150 | mEntire.setImageBitmap(null);
151 | mEntireBm = null;
152 | }
153 |
154 | if (mEntireBm == null || mEntireBm.getWidth() != newSize.x
155 | || mEntireBm.getHeight() != newSize.y) {
156 | mEntireBm = Bitmap.createBitmap(mSize.x, mSize.y, Bitmap.Config.ARGB_8888);
157 | }
158 |
159 | // Render the page in the background
160 | mDrawEntire = new AsyncTask() {
161 |
162 |
163 |
164 | protected LinkInfo[] doInBackground(Void... v) {
165 | drawPage(mEntireBm, mSize.x, mSize.y, 0, 0, mSize.x, mSize.y);
166 | return getLinkInfo();
167 | }
168 |
169 | protected void onPreExecute() {
170 | progressDialog = ProgressDialog.show(mContext,null,"正在载入");
171 | PageView.this.setFocusable(false);
172 | mEntire.setImageBitmap(null);
173 |
174 | if (mBusyIndicator == null) {
175 | //TODO
176 | mBusyIndicator = new ProgressBar(mContext);
177 |
178 | mBusyIndicator.setIndeterminate(true);
179 | // mBusyIndicator.setBackgroundResource(R.drawable.busy);
180 | addView(mBusyIndicator);
181 | mBusyIndicator.setVisibility(INVISIBLE);
182 | mHandler.postDelayed(new Runnable() {
183 | public void run() {
184 | if (mBusyIndicator != null)
185 | mBusyIndicator.setVisibility(VISIBLE);
186 | }
187 | }, PROGRESS_DIALOG_DELAY);
188 | }
189 | }
190 |
191 | protected void onPostExecute(LinkInfo[] v) {
192 | progressDialog.dismiss();
193 | PageView.this.setFocusable(true);
194 | removeView(mBusyIndicator);
195 | mBusyIndicator = null;
196 | mEntire.setImageBitmap(mEntireBm);
197 | mLinks = v;
198 | invalidate();
199 | }
200 | };
201 |
202 | mDrawEntire.execute();
203 |
204 | if (mSearchView == null) {
205 | mSearchView = new View(mContext) {
206 | @Override
207 | protected void onDraw(Canvas canvas) {
208 | super.onDraw(canvas);
209 | float scale = mSourceScale*(float)getWidth()/(float)mSize.x;
210 | Paint paint = new Paint();
211 |
212 | if (!mIsBlank && mSearchBoxes != null) {
213 | // Work out current total scale factor
214 | // from source to view
215 | paint.setColor(HIGHLIGHT_COLOR);
216 | for (RectF rect : mSearchBoxes)
217 | canvas.drawRect(rect.left*scale, rect.top*scale,
218 | rect.right*scale, rect.bottom*scale,
219 | paint);
220 | }
221 |
222 | if (!mIsBlank && mLinks != null && mHighlightLinks) {
223 | // Work out current total scale factor
224 | // from source to view
225 | paint.setColor(LINK_COLOR);
226 | for (RectF rect : mLinks)
227 | canvas.drawRect(rect.left*scale, rect.top*scale,
228 | rect.right*scale, rect.bottom*scale,
229 | paint);
230 | }
231 | }
232 | };
233 |
234 | addView(mSearchView);
235 | }
236 | requestLayout();
237 | }
238 |
239 | public void setSearchBoxes(RectF searchBoxes[]) {
240 | mSearchBoxes = searchBoxes;
241 | if (mSearchView != null)
242 | mSearchView.invalidate();
243 | }
244 |
245 | public void setLinkHighlighting(boolean f) {
246 | mHighlightLinks = f;
247 | if (mSearchView != null)
248 | mSearchView.invalidate();
249 | }
250 |
251 | @Override
252 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
253 | int x, y;
254 | switch(MeasureSpec.getMode(widthMeasureSpec)) {
255 | case MeasureSpec.UNSPECIFIED:
256 | x = mSize.x;
257 | break;
258 | default:
259 | x = MeasureSpec.getSize(widthMeasureSpec);
260 | }
261 | switch(MeasureSpec.getMode(heightMeasureSpec)) {
262 | case MeasureSpec.UNSPECIFIED:
263 | y = mSize.y;
264 | break;
265 | default:
266 | y = MeasureSpec.getSize(heightMeasureSpec);
267 | }
268 |
269 | setMeasuredDimension(x, y);
270 |
271 | if (mBusyIndicator != null) {
272 | int limit = Math.min(mParentSize.x, mParentSize.y)/2;
273 | mBusyIndicator.measure(MeasureSpec.AT_MOST | limit, MeasureSpec.AT_MOST | limit);
274 | }
275 | }
276 |
277 | @Override
278 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
279 | int w = right-left;
280 | int h = bottom-top;
281 |
282 | if (mEntire != null) {
283 | mEntire.layout(0, 0, w, h);
284 | }
285 |
286 | if (mSearchView != null) {
287 | mSearchView.layout(0, 0, w, h);
288 | }
289 |
290 | if (mPatchViewSize != null) {
291 | if (mPatchViewSize.x != w || mPatchViewSize.y != h) {
292 | // Zoomed since patch was created
293 | mPatchViewSize = null;
294 | mPatchArea = null;
295 | if (mPatch != null)
296 | mPatch.setImageBitmap(null);
297 | } else {
298 | mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom);
299 | }
300 | }
301 |
302 | if (mBusyIndicator != null) {
303 | int bw = mBusyIndicator.getMeasuredWidth();
304 | int bh = mBusyIndicator.getMeasuredHeight();
305 |
306 | mBusyIndicator.layout((w-bw)/2, (h-bh)/2, (w+bw)/2, (h+bh)/2);
307 | }
308 | }
309 |
310 | public void addHq() {
311 | Rect viewArea = new Rect(getLeft(),getTop(),getRight(),getBottom());
312 | // If the viewArea's size matches the unzoomed size, there is no need for an hq patch
313 | if (viewArea.width() != mSize.x || viewArea.height() != mSize.y) {
314 | Point patchViewSize = new Point(viewArea.width(), viewArea.height());
315 | Rect patchArea = new Rect(0, 0, mParentSize.x, mParentSize.y);
316 |
317 | // Intersect and test that there is an intersection
318 | if (!patchArea.intersect(viewArea))
319 | return;
320 |
321 | // Offset patch area to be relative to the view top left
322 | patchArea.offset(-viewArea.left, -viewArea.top);
323 |
324 | // If being asked for the same area as last time, nothing to do
325 | if (patchArea.equals(mPatchArea) && patchViewSize.equals(mPatchViewSize))
326 | return;
327 |
328 | // Stop the drawing of previous patch if still going
329 | if (mDrawPatch != null) {
330 | mDrawPatch.cancel(true);
331 | mDrawPatch = null;
332 | }
333 |
334 | // Create and add the image view if not already done
335 | if (mPatch == null) {
336 | mPatch = new OpaqueImageView(mContext);
337 | mPatch.setScaleType(ImageView.ScaleType.FIT_CENTER);
338 | addView(mPatch);
339 | mSearchView.bringToFront();
340 | }
341 |
342 | Bitmap bm = Bitmap.createBitmap(patchArea.width(), patchArea.height(), Bitmap.Config.ARGB_8888);
343 |
344 | mDrawPatch = new AsyncTask() {
345 | protected PatchInfo doInBackground(PatchInfo... v) {
346 | drawPage(v[0].bm, v[0].patchViewSize.x, v[0].patchViewSize.y,
347 | v[0].patchArea.left, v[0].patchArea.top,
348 | v[0].patchArea.width(), v[0].patchArea.height());
349 | return v[0];
350 | }
351 |
352 | protected void onPostExecute(PatchInfo v) {
353 | mPatchViewSize = v.patchViewSize;
354 | mPatchArea = v.patchArea;
355 | mPatch.setImageBitmap(v.bm);
356 | //requestLayout();
357 | // Calling requestLayout here doesn't lead to a later call to layout. No idea
358 | // why, but apparently others have run into the problem.
359 | mPatch.layout(mPatchArea.left, mPatchArea.top, mPatchArea.right, mPatchArea.bottom);
360 | invalidate();
361 | }
362 | };
363 |
364 | mDrawPatch.execute(new PatchInfo(bm, patchViewSize, patchArea));
365 | }
366 | }
367 |
368 | public void removeHq() {
369 | // Stop the drawing of the patch if still going
370 | if (mDrawPatch != null) {
371 | mDrawPatch.cancel(true);
372 | mDrawPatch = null;
373 | }
374 |
375 | // And get rid of it
376 | mPatchViewSize = null;
377 | mPatchArea = null;
378 | if (mPatch != null)
379 | mPatch.setImageBitmap(null);
380 | }
381 |
382 | public int getPage() {
383 | return mPageNumber;
384 | }
385 |
386 | @Override
387 | public boolean isOpaque() {
388 | return true;
389 | }
390 | }
391 |
--------------------------------------------------------------------------------
/app/src/main/java/com/artifex/mupdf/ReaderView.java:
--------------------------------------------------------------------------------
1 | package com.artifex.mupdf;
2 |
3 | import android.content.Context;
4 | import android.graphics.Point;
5 | import android.graphics.Rect;
6 | import android.util.AttributeSet;
7 | import android.util.Log;
8 | import android.util.SparseArray;
9 | import android.view.GestureDetector;
10 | import android.view.MotionEvent;
11 | import android.view.ScaleGestureDetector;
12 | import android.view.View;
13 | import android.view.WindowManager;
14 | import android.widget.Adapter;
15 | import android.widget.AdapterView;
16 | import android.widget.Scroller;
17 |
18 | import java.util.LinkedList;
19 | import java.util.NoSuchElementException;
20 |
21 |
22 | public class ReaderView extends AdapterView
23 | implements GestureDetector.OnGestureListener, View.OnTouchListener,
24 | ScaleGestureDetector.OnScaleGestureListener,
25 | Runnable {
26 | public static boolean NoTouch = true;
27 | private static Rect sRect;
28 | private static final int MOVING_DIAGONALLY = 0;
29 | private static final int MOVING_LEFT = 1;
30 | private static final int MOVING_RIGHT = 2;
31 | private static final int MOVING_UP = 3;
32 | private static final int MOVING_DOWN = 4;
33 |
34 | private static final int FLING_MARGIN = 100;
35 | private static final int GAP = 20;
36 |
37 | private static final float MIN_SCALE = 1.0f;
38 | private static final float MAX_SCALE = 20.0f;
39 |
40 | private Adapter mAdapter;
41 | private int mCurrent; // Adapter's index for the current view
42 | private boolean mResetLayout;
43 | private final SparseArray
44 | mChildViews = new SparseArray(3);
45 | // Shadows the children of the adapter view
46 | // but with more sensible indexing
47 | private final LinkedList
48 | mViewCache = new LinkedList();
49 | private boolean mUserInteracting; // Whether the user is interacting
50 | private boolean mScaling; // Whether the user is currently pinch zooming
51 | private float mScale = 1.0f;
52 |
53 |
54 | private final GestureDetector
55 | mGestureDetector;
56 | private final ScaleGestureDetector
57 | mScaleGestureDetector;
58 |
59 | private final Scroller mScroller;
60 |
61 |
62 | private int mScrollerLastX;
63 | private int mScrollerLastY;
64 | private boolean mScrollDisabled;
65 |
66 |
67 | public int scrollX;
68 | public int scrollY;
69 | private int mXScroll; // Scroll amounts recorded from events.
70 | private int mYScroll; // and then accounted for in onLayout
71 |
72 | public static float scalingFactor = 1;
73 |
74 | public ReaderView(Context context) {
75 | super(context);
76 | mGestureDetector = new GestureDetector(this);
77 | mScaleGestureDetector = new ScaleGestureDetector(context, this);
78 | mScroller = new Scroller(context);
79 | }
80 |
81 | public ReaderView(Context context, AttributeSet attrs) {
82 | super(context, attrs);
83 | mGestureDetector = new GestureDetector(this);
84 | mScaleGestureDetector = new ScaleGestureDetector(context, this);
85 | mScroller = new Scroller(context);
86 | }
87 |
88 | public ReaderView(Context context, AttributeSet attrs, int defStyle) {
89 | super(context, attrs, defStyle);
90 | mGestureDetector = new GestureDetector(this);
91 | mScaleGestureDetector = new ScaleGestureDetector(context, this);
92 | mScroller = new Scroller(context);
93 | }
94 |
95 | public int getDisplayedViewIndex() {
96 | return mCurrent;
97 | }
98 |
99 | public void setDisplayedViewIndex(int i) {
100 | if (0 <= i && i < mAdapter.getCount()) {
101 | mCurrent = i;
102 | onMoveToChild(i);
103 | mResetLayout = true;
104 | requestLayout();
105 | }
106 | }
107 |
108 | public void moveToNext() {
109 | View v = mChildViews.get(mCurrent + 1);
110 | if (v != null)
111 | slideViewOntoScreen(v);
112 | }
113 |
114 | public void moveToPrevious() {
115 | View v = mChildViews.get(mCurrent - 1);
116 | if (v != null)
117 | slideViewOntoScreen(v);
118 | }
119 |
120 | public void resetupChildren() {
121 | for (int i = 0; i < mChildViews.size(); i++)
122 | onChildSetup(mChildViews.keyAt(i), mChildViews.valueAt(i));
123 | }
124 |
125 | protected void onChildSetup(int i, View v) {
126 |
127 | }
128 |
129 | protected void onMoveToChild(int i) {
130 | }
131 |
132 | protected void onSettle(View v) {
133 | }
134 |
135 | ;
136 |
137 | protected void onUnsettle(View v) {
138 | }
139 |
140 | ;
141 |
142 | public View getDisplayedView() {
143 | return mChildViews.get(mCurrent);
144 | }
145 |
146 | public void run() {
147 | if (!mScroller.isFinished()) {
148 | mScroller.computeScrollOffset();
149 | int x = mScroller.getCurrX();
150 | int y = mScroller.getCurrY();
151 | mXScroll += x - mScrollerLastX;
152 | mYScroll += y - mScrollerLastY;
153 | mScrollerLastX = x;
154 | mScrollerLastY = y;
155 | requestLayout();
156 | post(this);
157 | } else if (!mUserInteracting) {
158 | // End of an inertial scroll and the user is not interacting.
159 | // The layout is stable
160 | View v = mChildViews.get(mCurrent);
161 | postSettle(v);
162 | }
163 | }
164 |
165 |
166 | public boolean onDown(MotionEvent arg0) {
167 | mScroller.forceFinished(true);
168 | //Log.e("info", "-->onDown");
169 | return true;
170 | }
171 |
172 |
173 | public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
174 | float velocityY) {
175 | if (mScrollDisabled)
176 | return true;
177 |
178 |
179 | View v = mChildViews.get(mCurrent);
180 | if (v != null) {
181 | Rect bounds = getScrollBounds(v);
182 | sRect = bounds;
183 | invalidate();
184 | switch (directionOfTravel(velocityX, velocityY)) {
185 | case MOVING_LEFT:
186 | if (bounds.left >= 0) {
187 | // Fling off to the left bring next view onto screen
188 | View vl = mChildViews.get(mCurrent + 1);
189 |
190 | if (vl != null) {
191 | slideViewOntoScreen(vl);
192 | return true;
193 | }
194 | }
195 | break;
196 | case MOVING_RIGHT:
197 | if (bounds.right <= 0) {
198 | // Fling off to the right bring previous view onto screen
199 | View vr = mChildViews.get(mCurrent - 1);
200 |
201 | if (vr != null) {
202 | slideViewOntoScreen(vr);
203 | return true;
204 | }
205 | }
206 | break;
207 | }
208 | mScrollerLastX = mScrollerLastY = 0;
209 |
210 | Rect expandedBounds = new Rect(bounds);
211 | expandedBounds.inset(-FLING_MARGIN, -FLING_MARGIN);
212 |
213 | if (withinBoundsInDirectionOfTravel(bounds, velocityX, velocityY)
214 | && expandedBounds.contains(0, 0)) {
215 | mScroller.fling(0, 0, (int) velocityX, (int) velocityY, bounds.left, bounds.right, bounds.top, bounds.bottom);
216 | post(this);
217 | }
218 | }
219 |
220 | return true;
221 | }
222 |
223 | public void onLongPress(MotionEvent e) {
224 | }
225 |
226 | public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
227 | float distanceY) {
228 | if (!mScrollDisabled) {
229 | mXScroll -= distanceX;
230 | mYScroll -= distanceY;
231 | requestLayout();
232 | }
233 | return true;
234 | }
235 |
236 |
237 | public void onShowPress(MotionEvent e) {
238 | //Log.e("info", "-->onShowPress");
239 | }
240 |
241 | public boolean onSingleTapUp(MotionEvent e) {
242 | return false;
243 | }
244 |
245 | public boolean onScale(ScaleGestureDetector detector) {
246 | float previousScale = getmScale();
247 | mScale = Math.min(Math.max(getmScale() * detector.getScaleFactor(), MIN_SCALE), MAX_SCALE);
248 | scalingFactor = getmScale() / previousScale;
249 | View v = mChildViews.get(mCurrent);
250 | if (v != null) {
251 | // Work out the focus point relative to the view top left
252 | int viewFocusX = (int) detector.getFocusX() - (v.getLeft() + mXScroll);
253 | int viewFocusY = (int) detector.getFocusY() - (v.getTop() + mYScroll);
254 | // Scroll to maintain the focus point
255 | mXScroll += viewFocusX - viewFocusX * scalingFactor;
256 | mYScroll += viewFocusY - viewFocusY * scalingFactor;
257 | requestLayout();
258 | }
259 |
260 | return true;
261 | }
262 |
263 | public boolean onScaleBegin(ScaleGestureDetector detector) {
264 | mScaling = true;
265 | // Ignore any scroll amounts yet to be accounted for: the
266 | // screen is not showing the effect of them, so they can
267 | // only confuse the user
268 | mXScroll = mYScroll = 0;
269 |
270 | // Avoid jump at end of scaling by disabling scrolling
271 | // until the next start of gesture
272 | mScrollDisabled = true;
273 | return true;
274 | }
275 |
276 | public void onScaleEnd(ScaleGestureDetector detector) {
277 | mScaling = false;
278 | }
279 |
280 | @Override
281 | public boolean onTouch(View v, MotionEvent event) {
282 | // TODO Auto-generated method stub
283 | return mGestureDetector.onTouchEvent(event);
284 | }
285 |
286 | @Override
287 | public boolean onTouchEvent(MotionEvent event) {
288 |
289 | mScaleGestureDetector.onTouchEvent(event);
290 | if (!mScaling)
291 | mGestureDetector.onTouchEvent(event);
292 |
293 | if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
294 | mUserInteracting = true;
295 | }
296 | if (event.getActionMasked() == MotionEvent.ACTION_UP) {
297 | mScrollDisabled = false;
298 | mUserInteracting = false;
299 |
300 |
301 | if (event.getAction() == MotionEvent.ACTION_UP) {
302 | View v = mChildViews.get(mCurrent);
303 | if (v != null) {
304 | if (mScroller.isFinished()) {
305 | slideViewOntoScreen(v);
306 | }
307 |
308 | if (mScroller.isFinished()) {
309 | postSettle(v);
310 | }
311 | }
312 | }
313 | }
314 | requestLayout();
315 | return true;
316 | }
317 |
318 | @Override
319 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
320 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
321 |
322 | int n = getChildCount();
323 | for (int i = 0; i < n; i++)
324 | measureView(getChildAt(i));
325 | }
326 |
327 | @Override
328 | protected void onLayout(boolean changed, int left, int top, int right,
329 | int bottom) {
330 | super.onLayout(changed, left, top, right, bottom);
331 |
332 | View cv = mChildViews.get(mCurrent);
333 | Point cvOffset;
334 | if (!mResetLayout) {
335 | // Move to next or previous if current is sufficiently off center
336 | if (cv != null) {
337 | cvOffset = subScreenSizeOffset(cv);
338 | // cv.getRight() may be out of date with the current scale
339 | // so add left to the measured width for the correct position
340 | if (cv.getLeft() + cv.getMeasuredWidth() + cvOffset.x + GAP / 2 + mXScroll < getWidth() / 2 && mCurrent + 1 < mAdapter.getCount()) {
341 | // mScale = 1.0f;
342 | postUnsettle(cv);
343 | // post to invoke test for end of animation
344 | // where we must set hq area for the new current view
345 | post(this);
346 | mCurrent++;
347 | onMoveToChild(mCurrent);
348 | }
349 |
350 | if (cv.getLeft() - cvOffset.x - GAP / 2 + mXScroll >= getWidth() / 2 && mCurrent > 0) {
351 | // mScale = 1.0f;
352 | postUnsettle(cv);
353 | // post to invoke test for end of animation
354 | // where we must set hq area for the new current view
355 | post(this);
356 |
357 | mCurrent--;
358 | onMoveToChild(mCurrent);
359 | }
360 | }
361 |
362 |
363 | // Remove not needed children and hold them for reuse
364 | int numChildren = mChildViews.size();
365 | int childIndices[] = new int[numChildren];
366 | for (int i = 0; i < numChildren; i++)
367 | childIndices[i] = mChildViews.keyAt(i);
368 |
369 | for (int i = 0; i < numChildren; i++) {
370 | int ai = childIndices[i];
371 | if (ai < mCurrent - 1 || ai > mCurrent + 1) {
372 | View v = mChildViews.get(ai);
373 | mViewCache.add(v);
374 | removeViewInLayout(v);
375 | mChildViews.remove(ai);
376 | }
377 | }
378 | } else {
379 | mResetLayout = false;
380 | mXScroll = mYScroll = 0;
381 | // Remove all children and hold them for reuse
382 | int numChildren = mChildViews.size();
383 | for (int i = 0; i < numChildren; i++) {
384 | View v = mChildViews.valueAt(i);
385 | postUnsettle(v);
386 | mViewCache.add(v);
387 | removeViewInLayout(v);
388 | }
389 | mChildViews.clear();
390 | // post to ensure generation of hq area
391 | post(this);
392 | }
393 |
394 | // Ensure current view is present
395 | int cvLeft, cvRight, cvTop, cvBottom;
396 | boolean notPresent = (mChildViews.get(mCurrent) == null);
397 | cv = getOrCreateChild(mCurrent);
398 | cvOffset = subScreenSizeOffset(cv);
399 | if (notPresent) {
400 | cvLeft = cvOffset.x;
401 | cvTop = cvOffset.y;
402 | } else {
403 | cvLeft = cv.getLeft() + mXScroll;
404 | cvTop = cv.getTop() + mYScroll;
405 | }
406 | mXScroll = mYScroll = 0;
407 | cvRight = cvLeft + cv.getMeasuredWidth();
408 | cvBottom = cvTop + cv.getMeasuredHeight();
409 |
410 | if (!mUserInteracting && mScroller.isFinished()) {
411 | Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom));
412 | cvRight += corr.x;
413 | cvLeft += corr.x;
414 | cvTop += corr.y;
415 | cvBottom += corr.y;
416 | } else if (cv.getMeasuredHeight() <= getHeight()) {
417 | Point corr = getCorrection(getScrollBounds(cvLeft, cvTop, cvRight, cvBottom));
418 | cvTop += corr.y;
419 | cvBottom += corr.y;
420 | }
421 | // Log.d("ReaderView","cvLeft::"+String.valueOf(cvLeft));
422 | cv.layout(cvLeft, cvTop, cvRight, cvBottom);
423 |
424 | if (mCurrent > 0) {
425 | View lv = getOrCreateChild(mCurrent - 1);
426 | Point leftOffset = subScreenSizeOffset(lv);
427 | int gap = leftOffset.x + GAP + cvOffset.x;
428 | lv.layout(cvLeft - lv.getMeasuredWidth() - gap,
429 | (cvBottom + cvTop - lv.getMeasuredHeight()) / 2,
430 | cvLeft - gap,
431 | (cvBottom + cvTop + lv.getMeasuredHeight()) / 2);
432 | }
433 |
434 | if (mCurrent + 1 < mAdapter.getCount()) {
435 | View rv = getOrCreateChild(mCurrent + 1);
436 | Point rightOffset = subScreenSizeOffset(rv);
437 | int gap = cvOffset.x + GAP + rightOffset.x;
438 | rv.layout(cvRight + gap,
439 | (cvBottom + cvTop - rv.getMeasuredHeight()) / 2,
440 | cvRight + rv.getMeasuredWidth() + gap,
441 | (cvBottom + cvTop + rv.getMeasuredHeight()) / 2);
442 |
443 | }
444 |
445 |
446 | WindowManager wm = (WindowManager) getContext()
447 | .getSystemService(Context.WINDOW_SERVICE);
448 | int height = wm.getDefaultDisplay().getHeight();
449 | int pianyizhi = height - this.getHeight();
450 |
451 |
452 | if (cv.getX() > 0) {
453 | scrollX = (int) cv.getX();
454 | } else {
455 | scrollX = - cvLeft;
456 | }
457 | if (cv.getY() > 0) {
458 | scrollY = -(int) (height - cv.getY() - cv.getHeight() - pianyizhi);
459 | } else {
460 | scrollY = cvBottom - height + pianyizhi;
461 | }
462 | invalidate();
463 | }
464 |
465 | @Override
466 | public Adapter getAdapter() {
467 | return mAdapter;
468 | }
469 |
470 | @Override
471 | public View getSelectedView() {
472 | throw new UnsupportedOperationException("Not supported");
473 | }
474 |
475 | @Override
476 | public void setAdapter(Adapter adapter) {
477 | mAdapter = adapter;
478 | mChildViews.clear();
479 | removeAllViewsInLayout();
480 | requestLayout();
481 | }
482 |
483 | @Override
484 | public void setSelection(int arg0) {
485 | throw new UnsupportedOperationException("Not supported");
486 | }
487 |
488 | private View getCached() {
489 | if (mViewCache.size() == 0)
490 | return null;
491 | else
492 | return mViewCache.removeFirst();
493 | }
494 |
495 | private View getOrCreateChild(int i) {
496 | View v = mChildViews.get(i);
497 | if (v == null) {
498 | v = mAdapter.getView(i, getCached(), this);
499 | addAndMeasureChild(i, v);
500 | }
501 | onChildSetup(i, v);
502 |
503 | return v;
504 | }
505 |
506 | private void addAndMeasureChild(int i, View v) {
507 | LayoutParams params = v.getLayoutParams();
508 | if (params == null) {
509 | params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
510 | }
511 | addViewInLayout(v, 0, params, true);
512 | mChildViews.append(i, v); // Record the view against it's adapter index
513 | measureView(v);
514 | }
515 |
516 | private void measureView(View v) {
517 | // See what size the view wants to be
518 | v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
519 | // Work out a scale that will fit it to this view
520 | float scale = Math.min((float) getWidth() / (float) v.getMeasuredWidth(),
521 | (float) getHeight() / (float) v.getMeasuredHeight());
522 | // Use the fitting values scaled by our current scale factor
523 | v.measure(MeasureSpec.EXACTLY | (int) (v.getMeasuredWidth() * scale * getmScale()),
524 | MeasureSpec.EXACTLY | (int) (v.getMeasuredHeight() * scale * getmScale()));
525 | }
526 |
527 | private Rect getScrollBounds(int left, int top, int right, int bottom) {
528 | int xmin = getWidth() - right;
529 | int xmax = -left;
530 | int ymin = getHeight() - bottom;
531 | int ymax = -top;
532 |
533 | // In either dimension, if view smaller than screen then
534 | // constrain it to be central
535 | if (xmin > xmax) xmin = xmax = (xmin + xmax) / 2;
536 | if (ymin > ymax) ymin = ymax = (ymin + ymax) / 2;
537 |
538 | return new Rect(xmin, ymin, xmax, ymax);
539 | }
540 |
541 | private Rect getScrollBounds(View v) {
542 | // There can be scroll amounts not yet accounted for in
543 | // onLayout, so add mXScroll and mYScroll to the current
544 | // positions when calculating the bounds.
545 | return getScrollBounds(v.getLeft() + mXScroll,
546 | v.getTop() + mYScroll,
547 | v.getLeft() + v.getMeasuredWidth() + mXScroll,
548 | v.getTop() + v.getMeasuredHeight() + mYScroll);
549 | }
550 |
551 | private Point getCorrection(Rect bounds) {
552 | return new Point(Math.min(Math.max(0, bounds.left), bounds.right),
553 | Math.min(Math.max(0, bounds.top), bounds.bottom));
554 | }
555 |
556 | private void postSettle(final View v) {
557 | // onSettle and onUnsettle are posted so that the calls
558 | // wont be executed until after the system has performed
559 | // layout.
560 | post(new Runnable() {
561 | public void run() {
562 | onSettle(v);
563 | }
564 | });
565 | }
566 |
567 | private void postUnsettle(final View v) {
568 | post(new Runnable() {
569 | public void run() {
570 | onUnsettle(v);
571 | }
572 | });
573 | }
574 |
575 | private void slideViewOntoScreen(View v) {
576 | Point corr = getCorrection(getScrollBounds(v));
577 | if (corr.x != 0 || corr.y != 0) {
578 | mScrollerLastX = mScrollerLastY = 0;
579 | mScroller.startScroll(0, 0, corr.x, corr.y, 400);
580 | post(this);
581 | }
582 | }
583 |
584 | private Point subScreenSizeOffset(View v) {
585 | return new Point(Math.max((getWidth() - v.getMeasuredWidth()) / 2, 0),
586 | Math.max((getHeight() - v.getMeasuredHeight()) / 2, 0));
587 | }
588 |
589 | private static int directionOfTravel(float vx, float vy) {
590 | if (Math.abs(vx) > 2 * Math.abs(vy))
591 | return (vx > 0) ? MOVING_RIGHT : MOVING_LEFT;
592 | else if (Math.abs(vy) > 2 * Math.abs(vx))
593 | return (vy > 0) ? MOVING_DOWN : MOVING_UP;
594 | else
595 | return MOVING_DIAGONALLY;
596 | }
597 |
598 | private static boolean withinBoundsInDirectionOfTravel(Rect bounds, float vx, float vy) {
599 | switch (directionOfTravel(vx, vy)) {
600 | case MOVING_DIAGONALLY:
601 | return bounds.contains(0, 0);
602 | case MOVING_LEFT:
603 | return bounds.left <= 0;
604 | case MOVING_RIGHT:
605 | return bounds.right >= 0;
606 | case MOVING_UP:
607 | return bounds.top <= 0;
608 | case MOVING_DOWN:
609 | return bounds.bottom >= 0;
610 | default:
611 | throw new NoSuchElementException();
612 | }
613 | }
614 |
615 | public float getmScale() {
616 | return mScale;
617 | }
618 |
619 | public void setmScale(float scale) {
620 | this.mScale = scale;
621 | }
622 |
623 |
624 | }
625 |
--------------------------------------------------------------------------------