├── .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 | 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 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.8 51 | 52 | 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 | --------------------------------------------------------------------------------