liuyuqi-dellpc 4 years ago
commit
50fdc46e2b
71 changed files with 3115 additions and 0 deletions
  1. 9 0
      .classpath
  2. 4 0
      .gitignore
  3. 33 0
      .project
  4. 32 0
      AndroidManifest.xml
  5. 6 0
      README.md
  6. BIN
      libs/armeabi/libvudroid.so
  7. 3 0
      lint.xml
  8. 34 0
      proguard.cfg
  9. 14 0
      project.properties
  10. BIN
      res/drawable-ldpi/icon.png
  11. BIN
      res/drawable-mdpi/arrowup.png
  12. BIN
      res/drawable-mdpi/book.png
  13. BIN
      res/drawable-mdpi/center.png
  14. BIN
      res/drawable-mdpi/folderopen.png
  15. BIN
      res/drawable-mdpi/gallery_zoom_in.png
  16. BIN
      res/drawable-mdpi/gallery_zoom_in_touch.png
  17. BIN
      res/drawable-mdpi/gallery_zoom_out.png
  18. BIN
      res/drawable-mdpi/gallery_zoom_out_touch.png
  19. BIN
      res/drawable-mdpi/icon.png
  20. BIN
      res/drawable-mdpi/left.png
  21. BIN
      res/drawable-mdpi/pdf.png
  22. BIN
      res/drawable-mdpi/right.png
  23. BIN
      res/drawable-mdpi/serifs.png
  24. BIN
      res/drawable-mdpi/zoomin.png
  25. 28 0
      res/layout-800x485/browser.xml
  26. 17 0
      res/layout-800x485/browseritem.xml
  27. 17 0
      res/layout-800x485/gotopage.xml
  28. 12 0
      res/layout-800x485/main.xml
  29. 28 0
      res/layout/browser.xml
  30. 17 0
      res/layout/browseritem.xml
  31. 17 0
      res/layout/gotopage.xml
  32. 12 0
      res/layout/main.xml
  33. 5 0
      res/values/strings.xml
  34. 164 0
      src/com/poqop/document/BaseBrowserActivity.java
  35. 314 0
      src/com/poqop/document/BaseViewerActivity.java
  36. 37 0
      src/com/poqop/document/DecodeService.java
  37. 275 0
      src/com/poqop/document/DecodeServiceBase.java
  38. 496 0
      src/com/poqop/document/DocumentView.java
  39. 69 0
      src/com/poqop/document/GoToPageDialog.java
  40. 55 0
      src/com/poqop/document/MainBrowserActivity.java
  41. 107 0
      src/com/poqop/document/Page.java
  42. 278 0
      src/com/poqop/document/PageTreeNode.java
  43. 63 0
      src/com/poqop/document/ViewerPreferences.java
  44. 16 0
      src/com/poqop/document/VuDroidLibraryLoader.java
  45. 12 0
      src/com/poqop/document/codec/CodecContext.java
  46. 9 0
      src/com/poqop/document/codec/CodecDocument.java
  47. 19 0
      src/com/poqop/document/codec/CodecPage.java
  48. 10 0
      src/com/poqop/document/events/BringUpZoomControlsEvent.java
  49. 6 0
      src/com/poqop/document/events/BringUpZoomControlsListener.java
  50. 22 0
      src/com/poqop/document/events/CurrentPageListener.java
  51. 22 0
      src/com/poqop/document/events/DecodingProgressListener.java
  52. 6 0
      src/com/poqop/document/events/Event.java
  53. 26 0
      src/com/poqop/document/events/EventDispatcher.java
  54. 36 0
      src/com/poqop/document/events/SafeEvent.java
  55. 19 0
      src/com/poqop/document/events/ZoomChangedEvent.java
  56. 17 0
      src/com/poqop/document/events/ZoomListener.java
  57. 24 0
      src/com/poqop/document/models/CurrentPageModel.java
  58. 26 0
      src/com/poqop/document/models/DecodingProgressModel.java
  59. 71 0
      src/com/poqop/document/models/ZoomModel.java
  60. 11 0
      src/com/poqop/document/multitouch/MultiTouchZoom.java
  61. 47 0
      src/com/poqop/document/multitouch/MultiTouchZoomImpl.java
  62. 98 0
      src/com/poqop/document/presentation/BrowserAdapter.java
  63. 50 0
      src/com/poqop/document/presentation/UriBrowserAdapter.java
  64. 32 0
      src/com/poqop/document/utils/MD5StringUtil.java
  65. 22 0
      src/com/poqop/document/utils/PathFromUri.java
  66. 60 0
      src/com/poqop/document/views/PageViewZoomControls.java
  67. 95 0
      src/com/poqop/document/views/ZoomRoll.java
  68. 15 0
      src/org/vudroid/pdfdroid/PdfViewerActivity.java
  69. 27 0
      src/org/vudroid/pdfdroid/codec/PdfContext.java
  70. 50 0
      src/org/vudroid/pdfdroid/codec/PdfDocument.java
  71. 121 0
      src/org/vudroid/pdfdroid/codec/PdfPage.java

+ 9 - 0
.classpath

@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="src" path="gen"/>
+	<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
+	<classpathentry kind="output" path="bin/classes"/>
+</classpath>

+ 4 - 0
.gitignore

@@ -0,0 +1,4 @@
+/bin
+/gen
+/.settings
+.svn

+ 33 - 0
.project

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>DocumentViewer</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>com.android.ide.eclipse.adt.ApkBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>

+ 32 - 0
AndroidManifest.xml

@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.poqop"
+      android:versionCode="1"
+      android:versionName="1.0.1">
+    
+    <uses-permission android:name="com.android.email.permission.ACCESS_PROVIDER"/>
+    <uses-permission android:name="com.android.email.permission.READ_ATTACHMENT"/>
+    <application android:icon="@drawable/pdf" android:label="PDF阅读" android:debuggable="true">
+        <activity android:name="org.vudroid.pdfdroid.PdfViewerActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:mimeType="application/pdf"/>
+            </intent-filter>
+        </activity>
+        <activity android:name=".document.MainBrowserActivity" android:label="PoQoP-Document">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        
+    </application>
+    <supports-screens android:resizeable="true"
+        android:smallScreens="true"
+        android:normalScreens="true"
+        android:largeScreens="true"
+        android:xlargeScreens="true"
+        android:anyDensity="true"/>
+</manifest> 

+ 6 - 0
README.md

@@ -0,0 +1,6 @@
+## DocumentViewer
+
+libvudroid.so 解析pdf,速度慢,清晰度不高。
+
+
+

BIN
libs/armeabi/libvudroid.so


+ 3 - 0
lint.xml

@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<lint>
+</lint>

+ 34 - 0
proguard.cfg

@@ -0,0 +1,34 @@
+-optimizationpasses 5
+-dontusemixedcaseclassnames
+-dontskipnonpubliclibraryclasses
+-dontpreverify
+-verbose
+-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
+
+-keep public class * extends android.app.Activity
+-keep public class * extends android.app.Application
+-keep public class * extends android.app.Service
+-keep public class * extends android.content.BroadcastReceiver
+-keep public class * extends android.content.ContentProvider
+-keep public class com.android.vending.licensing.ILicensingService
+
+-keepclasseswithmembernames class * {
+    native <methods>;
+}
+
+-keepclasseswithmembernames class * {
+    public <init>(android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembernames class * {
+    public <init>(android.content.Context, android.util.AttributeSet, int);
+}
+
+-keepclassmembers enum * {
+    public static **[] values();
+    public static ** valueOf(java.lang.String);
+}
+
+-keep class * implements android.os.Parcelable {
+  public static final android.os.Parcelable$Creator *;
+}

+ 14 - 0
project.properties

@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}\tools\proguard\proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-21

BIN
res/drawable-ldpi/icon.png


BIN
res/drawable-mdpi/arrowup.png


BIN
res/drawable-mdpi/book.png


BIN
res/drawable-mdpi/center.png


BIN
res/drawable-mdpi/folderopen.png


BIN
res/drawable-mdpi/gallery_zoom_in.png


BIN
res/drawable-mdpi/gallery_zoom_in_touch.png


BIN
res/drawable-mdpi/gallery_zoom_out.png


BIN
res/drawable-mdpi/gallery_zoom_out_touch.png


BIN
res/drawable-mdpi/icon.png


BIN
res/drawable-mdpi/left.png


BIN
res/drawable-mdpi/pdf.png


BIN
res/drawable-mdpi/right.png


BIN
res/drawable-mdpi/serifs.png


BIN
res/drawable-mdpi/zoomin.png


+ 28 - 0
res/layout-800x485/browser.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:orientation="vertical">
+    <TabHost
+        	android:layout_width="fill_parent"
+             android:layout_height="fill_parent"
+             android:id="@+id/browserTabHost"
+            >
+        <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:padding="5dp">
+                <TabWidget
+                    android:id="@android:id/tabs"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+                <FrameLayout
+                    android:id="@android:id/tabcontent"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    android:padding="5dp" />
+            </LinearLayout>
+    </TabHost>
+</LinearLayout>

+ 17 - 0
res/layout-800x485/browseritem.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:orientation="horizontal">
+              
+    
+    <ImageView 
+        android:layout_width="wrap_content"
+               android:layout_height="wrap_content"
+               android:id="@+id/browserItemIcon"/>
+    <TextView 
+        android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:id="@+id/browserItemText"
+              android:gravity="center_vertical"/>
+</LinearLayout>

+ 17 - 0
res/layout-800x485/gotopage.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:orientation="horizontal">
+    <EditText
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:minEms="4"
+            android:numeric="integer"
+            android:id="@+id/pageNumberTextEdit"/>
+    <Button android:text="Go to Page!"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/goToButton"/>
+</LinearLayout>

+ 12 - 0
res/layout-800x485/main.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+<TextView  
+    android:layout_width="fill_parent" 
+    android:layout_height="wrap_content" 
+    android:text="@string/hello"
+    />
+</LinearLayout>

+ 28 - 0
res/layout/browser.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:orientation="vertical">
+    <TabHost
+        	android:layout_width="fill_parent"
+             android:layout_height="fill_parent"
+             android:id="@+id/browserTabHost"
+            >
+        <LinearLayout
+                android:orientation="vertical"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:padding="5dp">
+                <TabWidget
+                    android:id="@android:id/tabs"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+                <FrameLayout
+                    android:id="@android:id/tabcontent"
+                    android:layout_width="fill_parent"
+                    android:layout_height="fill_parent"
+                    android:padding="5dp" />
+            </LinearLayout>
+    </TabHost>
+</LinearLayout>

+ 17 - 0
res/layout/browseritem.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:orientation="horizontal">
+              
+    
+    <ImageView 
+        android:layout_width="wrap_content"
+               android:layout_height="wrap_content"
+               android:id="@+id/browserItemIcon"/>
+    <TextView 
+        android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:id="@+id/browserItemText"
+              android:gravity="center_vertical"/>
+</LinearLayout>

+ 17 - 0
res/layout/gotopage.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="fill_parent"
+              android:layout_height="fill_parent"
+              android:orientation="horizontal">
+    <EditText
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:minEms="4"
+            android:numeric="integer"
+            android:id="@+id/pageNumberTextEdit"/>
+    <Button android:text="Go to Page!"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:id="@+id/goToButton"/>
+</LinearLayout>

+ 12 - 0
res/layout/main.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="vertical"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    >
+<TextView  
+    android:layout_width="fill_parent" 
+    android:layout_height="wrap_content" 
+    android:text="@string/hello"
+    />
+</LinearLayout>

+ 5 - 0
res/values/strings.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="hello">Hello World, PdfViewActivity!</string>
+    <string name="app_name">PdfViewer</string>
+</resources>

+ 164 - 0
src/com/poqop/document/BaseBrowserActivity.java

@@ -0,0 +1,164 @@
+package com.poqop.document;
+
+import android.app.Activity;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.FrameLayout;
+import android.widget.ListView;
+import android.widget.TabHost;
+import com.poqop.R;
+
+import com.poqop.document.presentation.BrowserAdapter;
+import com.poqop.document.presentation.UriBrowserAdapter;
+
+import java.io.File;
+import java.io.FileFilter;
+
+
+/**
+ * 生成两个listview去填充tabHost
+ * @author Administrator
+ *
+ */
+public abstract class BaseBrowserActivity extends Activity
+{
+    private BrowserAdapter adapter;
+    private static final String CURRENT_DIRECTORY = "currentDirectory";
+    private final AdapterView.OnItemClickListener onItemClickListener = new AdapterView.OnItemClickListener()
+    {
+        @SuppressWarnings({"unchecked"})
+        public void onItemClick(AdapterView<?> adapterView, View view, int i, long l)
+        {
+            final File file = ((AdapterView<BrowserAdapter>)adapterView).getAdapter().getItem(i);
+            if (file.isDirectory())
+            {
+                setCurrentDir(file);
+            }
+            else
+            {
+                showDocument(file);
+            }
+        }
+    };
+    private UriBrowserAdapter recentAdapter;
+    private ViewerPreferences viewerPreferences;
+    protected final FileFilter filter;
+
+    public BaseBrowserActivity()
+    {
+        this.filter = createFileFilter();
+    }
+
+    protected abstract FileFilter createFileFilter();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.browser);
+        viewerPreferences = new ViewerPreferences(this);
+        final ListView browseList = initBrowserListView();  //这个listview:是显示文件夹和文件
+        final ListView recentListView = initRecentListView();  //这个listview:是显示最近浏览的文件
+        TabHost tabHost = (TabHost) findViewById(R.id.browserTabHost);
+        tabHost.setup();
+        //选项卡Browse
+        tabHost.addTab(tabHost.newTabSpec("Browse").setIndicator("Browse").setContent(new TabHost.TabContentFactory()
+        {
+            public View createTabContent(String s)
+            {
+                return browseList;
+            }
+        }));
+        //选项卡 Recent
+        tabHost.addTab(tabHost.newTabSpec("Recent").setIndicator("Recent").setContent(new TabHost.TabContentFactory()
+        {
+            public View createTabContent(String s)
+            {
+                return recentListView;
+            }
+        }));
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState)
+    {
+        super.onPostCreate(savedInstanceState);
+        final File sdcardPath = new File("/sdcard");
+        if (sdcardPath.exists())
+        {
+            setCurrentDir(sdcardPath);
+        }
+        else
+        {
+            setCurrentDir(new File("/"));
+        }
+        if (savedInstanceState != null)
+        {
+            final String absolutePath = savedInstanceState.getString(CURRENT_DIRECTORY);
+            if (absolutePath != null)
+            {
+                setCurrentDir(new File(absolutePath));
+            }
+        }
+    }
+
+    private ListView initBrowserListView()
+    {
+        final ListView listView = new ListView(this);
+        adapter = new BrowserAdapter(this, filter);
+        listView.setAdapter(adapter);
+        listView.setOnItemClickListener(onItemClickListener);
+        listView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
+        return listView;
+    }
+
+    private ListView initRecentListView()
+    {
+        ListView listView = new ListView(this);
+        recentAdapter = new UriBrowserAdapter();
+        listView.setAdapter(recentAdapter);
+        listView.setOnItemClickListener(new AdapterView.OnItemClickListener()
+        {
+            @SuppressWarnings({"unchecked"})
+            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l)
+            {
+                showDocument(((AdapterView<UriBrowserAdapter>) adapterView).getAdapter().getItem(i));
+            }
+        });
+        listView.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
+        return listView;
+    }
+
+    private void showDocument(File file)
+    {
+        showDocument(Uri.fromFile(file));
+    }
+
+    protected abstract void showDocument(Uri uri);
+
+    private void setCurrentDir(File newDir)
+    {
+        adapter.setCurrentDirectory(newDir);
+        getWindow().setTitle(newDir.getAbsolutePath());
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState)
+    {
+        super.onSaveInstanceState(outState);
+        outState.putString(CURRENT_DIRECTORY, adapter.getCurrentDirectory().getAbsolutePath());
+    }
+
+    /**
+     * 进入最近浏览文件的Activity
+     */
+    @Override
+    protected void onResume()
+    {
+        super.onResume();
+        recentAdapter.setUris(viewerPreferences.getRecent());
+    }
+}

+ 314 - 0
src/com/poqop/document/BaseViewerActivity.java

@@ -0,0 +1,314 @@
+package com.poqop.document;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.ScaleAnimation;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.Toast;
+
+import com.poqop.R;
+import com.poqop.document.events.CurrentPageListener;
+import com.poqop.document.events.DecodingProgressListener;
+import com.poqop.document.models.CurrentPageModel;
+import com.poqop.document.models.DecodingProgressModel;
+import com.poqop.document.models.ZoomModel;
+import com.poqop.document.views.PageViewZoomControls;
+
+
+public abstract class BaseViewerActivity extends Activity implements DecodingProgressListener, CurrentPageListener
+{
+    private static final int MENU_EXIT = 0;
+    private static final int MENU_GOTO = 1;
+    private static final int DIALOG_GOTO = 0;
+    private static final String DOCUMENT_VIEW_STATE_PREFERENCES = "DjvuDocumentViewState";
+    private DecodeService decodeService;
+    private DocumentView documentView;
+    private ViewerPreferences viewerPreferences;
+    private Toast pageNumberToast;
+    private CurrentPageModel currentPageModel;
+
+    private static final int MAX_VALUE = 3800;  //设置放大的最大倍数
+	private static final float MULTIPLIER = 400.0f;
+	public ZoomModel zoomModel;
+	
+    float lastX;
+	float lastY;
+	float magnify=1.0f;  //放大
+	float reduce=1.0f;   //缩小
+	LinearLayout zoom;   //自定义一个linearlayout用来存放两个缩放按钮
+	
+    /**
+     * Called when the activity is first created.
+     */
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        super.onCreate(savedInstanceState);
+        initDecodeService();
+        final ZoomModel zoomModel = new ZoomModel();
+        final DecodingProgressModel progressModel = new DecodingProgressModel();
+        progressModel.addEventListener(this);
+        currentPageModel = new CurrentPageModel();
+        currentPageModel.addEventListener(this);
+        documentView = new DocumentView(this, zoomModel, progressModel, currentPageModel);
+        zoomModel.addEventListener(documentView);
+        documentView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
+        decodeService.setContentResolver(getContentResolver());
+        decodeService.setContainerView(documentView);
+        documentView.setDecodeService(decodeService);
+        decodeService.open(getIntent().getData());
+        this.zoomModel = zoomModel;
+        ImageView zoomIn,zoomOut;
+        viewerPreferences = new ViewerPreferences(this);
+        
+        /*
+         * 放大按鈕圖片進行放大
+         */
+        zoom=new LinearLayout(this);
+        zoom.setVisibility(View.GONE);
+        zoom.setOrientation(LinearLayout.HORIZONTAL);
+        
+        zoom.setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
+				LinearLayout.LayoutParams.WRAP_CONTENT));
+        
+        /*
+         * 縮小按鈕圖片進行縮小
+         */
+        zoomOut=new ImageView(this);
+		zoomOut.setImageResource(R.drawable.gallery_zoom_out_touch);
+		zoomOut.setLayoutParams(new LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
+				LinearLayout.LayoutParams.WRAP_CONTENT));
+		zoomOut.setOnTouchListener(new View.OnTouchListener() {			
+			public boolean onTouch(View v, MotionEvent event) {
+				System.out.println("寬"+zoom.getWidth());
+		        System.out.println("高:"+zoom.getHeight());
+				switch (event.getAction() & MotionEvent.ACTION_MASK) {
+				case MotionEvent.ACTION_DOWN:
+					lastX = event.getX();
+					setCurrentValue(getToureduceCurrentValues() - (event.getX() - lastX));					
+					break;
+				}
+				return true;
+			}
+		});
+        zoomIn=new ImageView(this);
+		zoomIn.setImageResource(R.drawable.gallery_zoom_in_touch);
+        zoomIn.setOnTouchListener(new View.OnTouchListener() {			
+			public boolean onTouch(View v, MotionEvent event) {
+				switch (event.getAction() & MotionEvent.ACTION_MASK) {
+				case MotionEvent.ACTION_DOWN:
+					lastX = event.getX();
+					setCurrentValue(getToumagnifyCurrentValues() - (event.getX() - lastX));					
+					break;
+				}
+				return true;
+			}
+		});
+		zoom.addView(zoomOut); //将两个按钮添加到一个小布局中
+		zoom.addView(zoomIn);
+		
+        final FrameLayout frameLayout = createMainContainer();
+        frameLayout.addView(documentView);
+        frameLayout.addView(zoom);  //將两个按钮添加到总布局中去 
+        frameLayout.addView(createZoomControls(zoomModel));
+        setFullScreen();
+        setContentView(frameLayout);
+
+        final SharedPreferences sharedPreferences = getSharedPreferences(DOCUMENT_VIEW_STATE_PREFERENCES, 0);
+        documentView.goToPage(sharedPreferences.getInt(getIntent().getData().toString(), 0));
+        documentView.showDocument();
+
+        documentView.setOnClickListener(new View.OnClickListener() {
+			
+			public void onClick(View v) {
+				zoom.setVisibility(View.VISIBLE);
+			}
+		});
+        
+        viewerPreferences.addRecent(getIntent().getData());
+    }
+    
+    
+    
+    // 放大
+  	public float getToumagnifyCurrentValues() {
+  		float mv = (zoomModel.getZoom() - 0.5f) * 400;
+  		return mv;
+  	}
+  	
+  	// 缩小 
+   	public float getToureduceCurrentValues() {
+   		float mv = (zoomModel.getZoom() - 1.0f) * 200;
+   		return mv;
+   	}
+  	
+  	
+  	public void setCurrentValue(float currentValue) {
+ 		if (currentValue < 0.0)
+ 			currentValue = 0.0f;
+ 		if (currentValue > MAX_VALUE)
+ 			currentValue = MAX_VALUE;
+ 		final float zoom = 1.0f + currentValue / MULTIPLIER;
+ 		zoomModel.setZoom(zoom);
+ 	}
+  	
+
+    public void decodingProgressChanged(final int currentlyDecoding)
+    {
+        runOnUiThread(new Runnable()
+        {
+            public void run()
+            {
+                getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS, currentlyDecoding == 0 ? 10000 : currentlyDecoding);
+            }
+        });
+    }
+
+    public void currentPageChanged(int pageIndex)
+    {
+        final String pageText = (pageIndex + 1) + "/" + decodeService.getPageCount();
+        if (pageNumberToast != null)
+        {
+            pageNumberToast.setText(pageText);
+        }
+        else
+        {
+            pageNumberToast = Toast.makeText(this, pageText, 300);
+        }
+        pageNumberToast.setGravity(Gravity.TOP | Gravity.LEFT,0,0);
+        pageNumberToast.show();
+        saveCurrentPage();
+    }
+
+    private void setWindowTitle()
+    {
+        final String name = getIntent().getData().getLastPathSegment();
+        getWindow().setTitle(name);
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState)
+    {
+        super.onPostCreate(savedInstanceState);
+        setWindowTitle();
+    }
+
+    /*
+     * 设置全屏
+     */
+    private void setFullScreen()
+    {
+        if (viewerPreferences.isFullScreen())
+        {
+    //        getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+    //        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        }
+        else
+        {
+            getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);  //在标题栏上加入更新进度条
+        }
+    }
+
+    /*
+     * 设置缩放栏的位置
+     */
+    private PageViewZoomControls createZoomControls(ZoomModel zoomModel)
+    {
+        final PageViewZoomControls controls = new PageViewZoomControls(this, zoomModel);
+        controls.setGravity(Gravity.RIGHT | Gravity.BOTTOM);
+        zoomModel.addEventListener(controls);
+        return controls;
+    }
+
+    private FrameLayout createMainContainer()
+    {
+        return new FrameLayout(this);
+    }
+
+    private void initDecodeService()
+    {
+        if (decodeService == null)
+        {
+            decodeService = createDecodeService();
+        }
+    }
+
+    protected abstract DecodeService createDecodeService();
+
+    @Override
+    protected void onStop()
+    {
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        decodeService.recycle();
+        decodeService = null;
+        super.onDestroy();
+    }
+
+    private void saveCurrentPage()
+    {
+        final SharedPreferences sharedPreferences = getSharedPreferences(DOCUMENT_VIEW_STATE_PREFERENCES, 0);
+        final SharedPreferences.Editor editor = sharedPreferences.edit();
+        editor.putInt(getIntent().getData().toString(), documentView.getCurrentPage());
+        editor.commit();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu)
+    {
+        menu.add(0, MENU_EXIT, 0, "退出");
+        menu.add(0, MENU_GOTO, 0, "跳转");
+//        final MenuItem menuItem = menu.add(0, MENU_FULL_SCREEN, 0, "Full screen").setCheckable(true).setChecked(viewerPreferences.isFullScreen());
+//        setFullScreenMenuItemText(menuItem);
+        return true;
+    }
+
+    private void setFullScreenMenuItemText(MenuItem menuItem)
+    {
+        menuItem.setTitle("Full screen " + (menuItem.isChecked() ? "on" : "off"));
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item)
+    {
+        switch (item.getItemId())
+        {
+            case MENU_EXIT:
+                System.exit(0);
+                return true;
+            case MENU_GOTO:
+                showDialog(DIALOG_GOTO);
+                return true;
+        }
+        return false;
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id)
+    {
+        switch (id)
+        {
+            case DIALOG_GOTO:
+                return new GoToPageDialog(this, documentView, decodeService);
+        }
+        return null;
+    }
+}

+ 37 - 0
src/com/poqop/document/DecodeService.java

@@ -0,0 +1,37 @@
+package com.poqop.document;
+
+import android.content.ContentResolver;
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.view.View;
+
+public interface DecodeService
+{
+    void setContentResolver(ContentResolver contentResolver);
+
+    void setContainerView(View containerView);
+
+    void open(Uri fileUri);
+
+    void decodePage(Object decodeKey, int pageNum, DecodeCallback decodeCallback, float zoom, RectF pageSliceBounds);
+
+    void stopDecoding(Object decodeKey);
+
+    int getEffectivePagesWidth();
+
+    int getEffectivePagesHeight();
+
+    int getPageCount();
+
+    int getPageWidth(int pageIndex);
+
+    int getPageHeight(int pageIndex);
+
+    void recycle();
+
+    public interface DecodeCallback
+    {
+        void decodeComplete(Bitmap bitmap);
+    }
+}

+ 275 - 0
src/com/poqop/document/DecodeServiceBase.java

@@ -0,0 +1,275 @@
+package com.poqop.document;
+
+import android.content.ContentResolver;
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+import android.net.Uri;
+import android.util.Log;
+import android.view.View;
+
+import com.poqop.document.codec.CodecContext;
+import com.poqop.document.codec.CodecDocument;
+import com.poqop.document.codec.CodecPage;
+import com.poqop.document.utils.PathFromUri;
+
+import java.io.IOException;
+import java.lang.ref.SoftReference;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.*;
+
+public class DecodeServiceBase implements DecodeService
+{
+    private static final int PAGE_POOL_SIZE = 16;
+    private final CodecContext codecContext;
+
+    private View containerView;
+    private CodecDocument document;
+    private final ExecutorService executorService = Executors.newSingleThreadExecutor();
+    public static final String DECODE_SERVICE = "ViewDroidDecodeService";
+    private final Map<Object, Future<?>> decodingFutures = new ConcurrentHashMap<Object, Future<?>>();
+    private final HashMap<Integer, SoftReference<CodecPage>> pages = new HashMap<Integer, SoftReference<CodecPage>>();
+    private ContentResolver contentResolver;
+    private Queue<Integer> pageEvictionQueue = new LinkedList<Integer>();
+    private boolean isRecycled;
+
+    public DecodeServiceBase(CodecContext codecContext)
+    {
+        this.codecContext = codecContext;
+    }
+
+    public void setContentResolver(ContentResolver contentResolver)
+    {
+        this.contentResolver = contentResolver;
+        codecContext.setContentResolver(contentResolver);
+    }
+
+    public void setContainerView(View containerView)
+    {
+        this.containerView = containerView;
+    }
+
+    public void open(Uri fileUri)
+    {
+        document = codecContext.openDocument(PathFromUri.retrieve(contentResolver, fileUri));
+    }
+
+    public void decodePage(Object decodeKey, int pageNum, final DecodeCallback decodeCallback, float zoom, RectF pageSliceBounds)
+    {
+        final DecodeTask decodeTask = new DecodeTask(pageNum, decodeCallback, zoom, decodeKey, pageSliceBounds);
+        synchronized (decodingFutures)
+        {
+            if (isRecycled) {
+                return;
+            }
+            final Future<?> future = executorService.submit(new Runnable()
+            {
+                public void run()
+                {
+                    try
+                    {
+                        Thread.currentThread().setPriority(Thread.NORM_PRIORITY-1);
+                        performDecode(decodeTask);
+                    }
+                    catch (IOException e)
+                    {
+                        Log.e(DECODE_SERVICE, "Decode fail", e);
+                    }
+                }
+            });
+            final Future<?> removed = decodingFutures.put(decodeKey, future);
+            if (removed != null)
+            {
+                removed.cancel(false);
+            }
+        }
+    }
+
+    public void stopDecoding(Object decodeKey)
+    {
+        final Future<?> future = decodingFutures.remove(decodeKey);
+        if (future != null)
+        {
+            future.cancel(false);
+        }
+    }
+
+    private void performDecode(DecodeTask currentDecodeTask)
+            throws IOException
+    {
+        if (isTaskDead(currentDecodeTask))
+        {
+            Log.d(DECODE_SERVICE, "Skipping decode task for page " + currentDecodeTask.pageNumber);
+            return;
+        }
+        Log.d(DECODE_SERVICE, "Starting decode of page: " + currentDecodeTask.pageNumber);
+        CodecPage vuPage = getPage(currentDecodeTask.pageNumber);
+        preloadNextPage(currentDecodeTask.pageNumber);
+
+        if (isTaskDead(currentDecodeTask))
+        {
+            return;
+        }
+        Log.d(DECODE_SERVICE, "Start converting map to bitmap");
+        float scale = calculateScale(vuPage) * currentDecodeTask.zoom;
+        final Bitmap bitmap = vuPage.renderBitmap(getScaledWidth(currentDecodeTask, vuPage, scale), getScaledHeight(currentDecodeTask, vuPage, scale), currentDecodeTask.pageSliceBounds);
+        Log.d(DECODE_SERVICE, "Converting map to bitmap finished");
+        if (isTaskDead(currentDecodeTask))
+        {
+            bitmap.recycle();
+            return;
+        }
+        finishDecoding(currentDecodeTask, bitmap);
+    }
+
+    private int getScaledHeight(DecodeTask currentDecodeTask, CodecPage vuPage, float scale)
+    {
+        return Math.round(getScaledHeight(vuPage, scale) * currentDecodeTask.pageSliceBounds.height());
+    }
+
+    private int getScaledWidth(DecodeTask currentDecodeTask, CodecPage vuPage, float scale)
+    {
+        return Math.round(getScaledWidth(vuPage, scale) * currentDecodeTask.pageSliceBounds.width());
+    }
+
+    private int getScaledHeight(CodecPage vuPage, float scale)
+    {
+        return (int) (scale * vuPage.getHeight());
+    }
+
+    private int getScaledWidth(CodecPage vuPage, float scale)
+    {
+        return (int) (scale * vuPage.getWidth());
+    }
+
+    private float calculateScale(CodecPage codecPage)
+    {
+        return 1.0f * getTargetWidth() / codecPage.getWidth();
+    }
+
+    private void finishDecoding(DecodeTask currentDecodeTask, Bitmap bitmap)
+    {
+        updateImage(currentDecodeTask, bitmap);
+        stopDecoding(currentDecodeTask.pageNumber);
+    }
+
+    private void preloadNextPage(int pageNumber) throws IOException
+    {
+        final int nextPage = pageNumber + 1;
+        if (nextPage >= getPageCount())
+        {
+            return;
+        }
+        getPage(nextPage);
+    }
+
+    private CodecPage getPage(int pageIndex)
+    {
+        if (!pages.containsKey(pageIndex) || pages.get(pageIndex).get() == null)
+        {
+            pages.put(pageIndex, new SoftReference<CodecPage>(document.getPage(pageIndex)));
+            pageEvictionQueue.remove(pageIndex);
+            pageEvictionQueue.offer(pageIndex);
+            if (pageEvictionQueue.size() > PAGE_POOL_SIZE) {
+                Integer evictedPageIndex = pageEvictionQueue.poll();
+                CodecPage evictedPage = pages.remove(evictedPageIndex).get();
+                if (evictedPage != null) {
+                    evictedPage.recycle();
+                }
+            }
+        }
+        return pages.get(pageIndex).get();
+    }
+
+    private void waitForDecode(CodecPage vuPage)
+    {
+        vuPage.waitForDecode();
+    }
+
+    private int getTargetWidth()
+    {
+        return containerView.getWidth();
+    }
+
+    public int getEffectivePagesWidth()
+    {
+        final CodecPage page = getPage(0);
+        return getScaledWidth(page, calculateScale(page));
+    }
+
+    public int getEffectivePagesHeight()
+    {
+        final CodecPage page = getPage(0);
+        return getScaledHeight(page, calculateScale(page));
+    }
+
+    public int getPageWidth(int pageIndex)
+    {
+        return getPage(pageIndex).getWidth();
+    }
+
+    public int getPageHeight(int pageIndex)
+    {
+        return getPage(pageIndex).getHeight();
+    }
+
+    private void updateImage(final DecodeTask currentDecodeTask, Bitmap bitmap)
+    {
+        currentDecodeTask.decodeCallback.decodeComplete(bitmap);
+    }
+
+    private boolean isTaskDead(DecodeTask currentDecodeTask)
+    {
+        synchronized (decodingFutures)
+        {
+            return !decodingFutures.containsKey(currentDecodeTask.decodeKey);
+        }
+    }
+
+    public int getPageCount()
+    {
+        return document.getPageCount();
+    }
+
+    private class DecodeTask
+    {
+        private final Object decodeKey;
+        private final int pageNumber;
+        private final float zoom;
+        private final DecodeCallback decodeCallback;
+        private final RectF pageSliceBounds;
+
+        private DecodeTask(int pageNumber, DecodeCallback decodeCallback, float zoom, Object decodeKey, RectF pageSliceBounds)
+        {
+            this.pageNumber = pageNumber;
+            this.decodeCallback = decodeCallback;
+            this.zoom = zoom;
+            this.decodeKey = decodeKey;
+            this.pageSliceBounds = pageSliceBounds;
+        }
+    }
+
+    public void recycle() {
+        synchronized (decodingFutures) {
+            isRecycled = true;
+        }
+        for (Object key : decodingFutures.keySet()) {
+            stopDecoding(key);
+        }
+        executorService.submit(new Runnable() {
+            public void run() {
+                for (SoftReference<CodecPage> codecPageSoftReference : pages.values()) {
+                    CodecPage page = codecPageSoftReference.get();
+                    if (page != null) {
+                        page.recycle();
+                    }
+                }
+                document.recycle();
+                codecContext.recycle();
+            }
+        });
+        executorService.shutdown();
+    }
+}

+ 496 - 0
src/com/poqop/document/DocumentView.java

@@ -0,0 +1,496 @@
+package com.poqop.document;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.util.FloatMath;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.widget.Scroller;
+
+import com.poqop.document.events.ZoomListener;
+import com.poqop.document.models.CurrentPageModel;
+import com.poqop.document.models.DecodingProgressModel;
+import com.poqop.document.models.ZoomModel;
+import com.poqop.document.multitouch.MultiTouchZoom;
+
+/**
+ * 屏幕滚动
+ * 处理用户翻页操作,比如滑动、点击上下左右键等。
+ * 此时根据具体的用户操作计算需要显示哪几页,通知BaseViewerActivity去显示这些页
+ * @author Administrator
+ * 
+ */
+public class DocumentView extends View implements ZoomListener {
+	final ZoomModel zoomModel;
+	private final CurrentPageModel currentPageModel;
+	DecodeService decodeService;
+	private final HashMap<Integer, Page> pages = new HashMap<Integer, Page>();
+	private boolean isInitialized = false;
+	private int pageToGoTo;
+	private float lastX;
+	private float lastY;
+	private VelocityTracker velocityTracker;
+	private final Scroller scroller;
+	DecodingProgressModel progressModel;
+	private RectF viewRect;
+	private boolean inZoom;
+	private long lastDownEventTime;
+	private static final int DOUBLE_TAP_TIME = 500;
+	private MultiTouchZoom multiTouchZoom;
+
+	private static final int MAX_VALUE = 3800;
+	private static final float MULTIPLIER = 400.0f;
+
+	static final int NONE = 0;  //空闲
+	static final int DRAG = 1;  //拖动
+	static final int ZOOM = 2;  //缩放
+	int mode = NONE;
+
+	private float oldDist;
+	private PointF midPoint = new PointF();
+	private boolean isZoom = false;
+
+	
+	private boolean dblclick = true;
+	public DocumentView(Context context, final ZoomModel zoomModel,
+			DecodingProgressModel progressModel,
+			CurrentPageModel currentPageModel) {
+		super(context);
+		this.zoomModel = zoomModel;
+		this.progressModel = progressModel;
+		this.currentPageModel = currentPageModel;
+		setKeepScreenOn(true);
+		scroller = new Scroller(getContext());
+		setFocusable(true);
+		setFocusableInTouchMode(true);
+	}
+	
+	public void setDecodeService(DecodeService decodeService) {
+		this.decodeService = decodeService;
+	}
+
+	private void init() {
+		if (isInitialized) {
+			return;
+		}
+		final int width = decodeService.getEffectivePagesWidth();  //获取试图的宽
+		final int height = decodeService.getEffectivePagesHeight();  //高 
+		for (int i = 0; i < decodeService.getPageCount(); i++) {
+			pages.put(i, new Page(this, i));
+			pages.get(i).setAspectRatio(width, height);
+		}
+		isInitialized = true;
+		invalidatePageSizes();
+		goToPageImpl(pageToGoTo);
+	}
+
+	private void goToPageImpl(final int toPage) {
+		scrollTo(0, pages.get(toPage).getTop());
+	}
+
+	/*
+	 * 让视图更新
+	 */
+	@Override
+	protected void onScrollChanged(int l, int t, int oldl, int oldt) {
+		super.onScrollChanged(l, t, oldl, oldt);
+		// bounds could be not updated
+		post(new Runnable() {
+			public void run() {
+				currentPageModel.setCurrentPageIndex(getCurrentPage());
+			}
+		});
+		if (inZoom) {
+			return;
+		}
+		// on scrollChanged can be called from scrollTo just after new layout
+		// applied so we should wait for relayout
+		post(new Runnable() {
+			public void run() {
+				updatePageVisibility();
+			}
+		});
+	}
+
+	private void updatePageVisibility() {
+		for (Page page : pages.values()) {
+			page.updateVisibility();
+		}
+	}
+
+	public void commitZoom() {
+		for (Page page : pages.values()) {
+			page.invalidate();
+		}
+		inZoom = false;
+	}
+
+	public void showDocument() {
+		// use post to ensure that document view has width and height before
+		// decoding begin
+		post(new Runnable() {
+			public void run() {
+				init();
+				updatePageVisibility();
+			}
+		});
+	}
+
+	public void goToPage(int toPage) {
+		if (isInitialized) {
+			goToPageImpl(toPage);
+		} else {
+			pageToGoTo = toPage;
+		}
+	}
+
+	public int getCurrentPage() {
+		for (Map.Entry<Integer, Page> entry : pages.entrySet()) {
+			if (entry.getValue().isVisible()) {
+				return entry.getKey();
+			}
+		}
+		return 0;
+	}
+
+	/**
+	 * 缩放功能的处理
+	 */
+	public void zoomChanged(float newZoom, float oldZoom) {
+		inZoom = true;
+		stopScroller();
+		final float ratio = newZoom / oldZoom;
+		invalidatePageSizes();
+		scrollTo(
+				(int) ((getScrollX() + getWidth() / 2) * ratio - getWidth() / 2),
+				(int) ((getScrollY() + getHeight() / 2) * ratio - getHeight() / 2));
+		postInvalidate();
+	}
+
+	/**
+	 * 触摸屏事件
+	 */
+	@Override
+	public boolean onTouchEvent(MotionEvent ev) {
+		super.onTouchEvent(ev);
+
+		if (multiTouchZoom != null) {
+			if (multiTouchZoom.onTouchEvent(ev)) {
+				return true;
+			}
+
+			if (multiTouchZoom.isResetLastPointAfterZoom()) {
+				setLastPosition(ev);
+				multiTouchZoom.setResetLastPointAfterZoom(false);
+			}
+		}
+
+		if (velocityTracker == null) {
+			velocityTracker = VelocityTracker.obtain();  //用obtain()函数来获得类的实例,开始计算速度
+		}
+		velocityTracker.addMovement(ev);  //这个方法是将motion event 添加到velocityTracker
+		switch (ev.getAction() & MotionEvent.ACTION_MASK) {
+		case MotionEvent.ACTION_DOWN:
+			mode = DRAG;
+			/*
+			 * 双击放大
+			 * 用第一次点击和第二次点击的时间差来判断是否为双击
+			 */
+			if ((ev.getEventTime() - lastDownEventTime < DOUBLE_TAP_TIME) && dblclick) {
+				setCurrentValue(getmagnifyCurrentValues() - (ev.getX() - lastX));
+				dblclick=false;
+        		mode = DRAG;
+        		/*
+        		 * 双击缩小
+        		 */
+				}else if((ev.getEventTime() - lastDownEventTime < DOUBLE_TAP_TIME) && !dblclick) {
+					setCurrentValue(getReduceCurrentValues() - (ev.getX() - lastX));
+	        		dblclick=true;  
+            }else{
+    			if (!scroller.isFinished()) {
+    				scroller.abortAnimation();
+    				zoomModel.commit();
+    			}
+    			else {
+                    lastDownEventTime = ev.getEventTime();
+                }            	
+            }
+			stopScroller();
+			setLastPosition(ev);
+			
+			/**
+			 * velocityTracker   用来追踪触摸事件(flinging事件和其他手势事件)的速率
+			 * fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY) 
+			 * 方法进行控件滚动时各种位置坐标数值的计算
+			 */
+		case MotionEvent.ACTION_UP:
+			velocityTracker.computeCurrentVelocity(1000); // 初始化速率
+            scroller.fling(getScrollX(), getScrollY(), (int) -velocityTracker.getXVelocity(), 
+            		(int) -velocityTracker.getYVelocity(), getLeftLimit(), getRightLimit(), getTopLimit(), getBottomLimit());
+            velocityTracker.recycle();  //回收
+            velocityTracker = null;	
+            mode = DRAG;
+			break;
+		/**
+		 * API原文是 A non-primary pointer has gone down. 翻译过来就是:非第一个点按下
+		 */
+		case MotionEvent.ACTION_POINTER_DOWN:
+			oldDist = spacing(ev);
+			midPoint(midPoint, ev);
+			isZoom = true;
+			mode = ZOOM;
+			break;
+		case MotionEvent.ACTION_POINTER_UP:
+			mode=DRAG;
+			break;
+		case MotionEvent.ACTION_MOVE:
+			if (mode == DRAG) {
+				scrollBy((int) (lastX - ev.getX()), (int) (lastY - ev.getY()));
+				setLastPosition(ev);
+				break;
+			} else if (mode==ZOOM) {				
+					float newDist = spacing(ev);
+						/**
+						 * 表示新的距离比两个手指刚触碰的距离大》》》放大 ( +10个像素用来延迟一下放大
+						 */
+						if (newDist > oldDist + 10) {
+							oldDist = spacing(ev);			
+							setCurrentValue(getCurrentValue() - (ev.getX() - lastX));
+							lastX = ev.getX();	
+						}
+						/**
+						 * 表示新的距离比两个手指刚触碰的距离小:>>>>缩小
+						 */
+						if (newDist + 20 < oldDist) {
+							oldDist = spacing(ev);			
+							setCurrentValue(getRedCurrentValues()
+									- (ev.getX() - lastX));
+							lastX = ev.getX();
+						}
+						break;
+					}
+				}	
+		return true;
+	}
+
+	// 双击放大
+	public float getmagnifyCurrentValues() {
+		float mv = (zoomModel.getZoom() - 0.2f) * 2200;
+		return mv;
+	}
+	//多点触摸放大
+	public float getCurrentValue() {
+		return (zoomModel.getZoom() - 0.5f) * 350f;
+	}
+
+	// 双击縮小
+	public float getReduceCurrentValues() {
+		return (zoomModel.getZoom() - 1.0f) *0.1f;
+	}
+	
+	//多点触摸缩小
+	public float getRedCurrentValues() {
+		return (zoomModel.getZoom() - 1.0f) *350f;
+	}
+	
+	public void setCurrentValue(float currentValue) {
+		if (currentValue < 0.0)
+			currentValue = 0.0f;
+		if (currentValue > MAX_VALUE)
+			currentValue = MAX_VALUE;
+		final float zoom = 1.0f + currentValue / MULTIPLIER;
+		zoomModel.setZoom(zoom);
+	}
+
+	/*
+	 * 间隔
+	 */
+	private float spacing(MotionEvent event) {
+		float x = event.getX(0) - event.getX(1);
+		float y = event.getY(0) - event.getY(1);
+		return FloatMath.sqrt(x * x + y * y);
+	}
+	
+	/*
+	 * 得到中点
+	 */
+	private void midPoint(PointF point, MotionEvent event) {
+		float x = event.getX(0) + event.getX(1);
+		float y = event.getY(0) + event.getY(1);
+		point.set(x / 2, y / 2);
+	}
+
+	/*
+	 * 获取触摸的X,Y并赋值
+	 */
+	private void setLastPosition(MotionEvent ev) {
+		lastX = ev.getX();
+		lastY = ev.getY();
+	}
+
+	/**
+	 * 按键事件处理,这里你按上下左右键,页面内容是可以上下左右移动的
+	 */
+	@Override
+	public boolean dispatchKeyEvent(KeyEvent event) {
+		if (event.getAction() == KeyEvent.ACTION_DOWN) {
+			switch (event.getKeyCode()) {
+			case KeyEvent.KEYCODE_DPAD_RIGHT:
+				lineByLineMoveTo(1);
+				return true;
+			case KeyEvent.KEYCODE_DPAD_LEFT:
+				lineByLineMoveTo(-1);
+				return true;
+			case KeyEvent.KEYCODE_DPAD_DOWN:
+				verticalDpadScroll(1);
+				return true;
+			case KeyEvent.KEYCODE_DPAD_UP:
+				verticalDpadScroll(-1);
+				return true;
+			}
+		}
+		return super.dispatchKeyEvent(event);
+	}
+
+	private void verticalDpadScroll(int direction) {
+		scroller.startScroll(getScrollX(), getScrollY(), 0, direction
+				* getHeight() / 2);
+		invalidate();
+	}
+
+	private void lineByLineMoveTo(int direction) {
+		if (direction == 1 ? getScrollX() == getRightLimit()
+				: getScrollX() == getLeftLimit()) {
+			scroller.startScroll(getScrollX(), getScrollY(), direction
+					* (getLeftLimit() - getRightLimit()), (int) (direction
+					* pages.get(getCurrentPage()).bounds.height() / 50));
+		} else {
+			scroller.startScroll(getScrollX(), getScrollY(), direction
+					* getWidth() / 2, 0);
+		}
+		invalidate();
+	}
+
+	private int getTopLimit() {
+		return 0;
+	}
+
+	private int getLeftLimit() {
+		return 0;
+	}
+
+	private int getBottomLimit() {
+		return (int) pages.get(pages.size() - 1).bounds.bottom - getHeight();
+	}
+
+	private int getRightLimit() {
+		return (int) (getWidth() * zoomModel.getZoom()) - getWidth();
+	}
+
+	/*
+	 *  scrollTo是在移动到这个坐标时显示出的视图
+	 * @see android.view.View#scrollTo(int, int)
+	 */	 
+	@Override
+	public void scrollTo(int x, int y) {
+		super.scrollTo(Math.min(Math.max(x, getLeftLimit()), getRightLimit()),
+				Math.min(Math.max(y, getTopLimit()), getBottomLimit()));    //获得上下左右的坐标
+		viewRect = null;
+	}
+
+	/*
+	 * RectF 这个类包含一个矩形的四个单精度浮点坐标。矩形通过上下左右4个边的坐标来表示一个矩形
+	 * RectF(int,a,int b,int c,int d);通过四个坐标,构造一个矩形。
+	 */
+	RectF getViewRect() {
+		if (viewRect == null) {
+			viewRect = new RectF(getScrollX(), getScrollY(), getScrollX()
+					+ getWidth(), getScrollY() + getHeight());
+		}
+		return viewRect;
+	}
+
+	/*
+	 * 计算滚动值。
+	 * @see android.view.View#computeScroll()
+	 */
+	@Override
+	public void computeScroll() {
+		if (scroller.computeScrollOffset()) {
+			scrollTo(scroller.getCurrX(), scroller.getCurrY());
+		}
+	}
+
+	@Override
+	protected void onDraw(Canvas canvas) {
+		super.onDraw(canvas);
+		for (Page page : pages.values()) {
+			page.draw(canvas);
+		}
+	}
+
+	@Override
+	protected void onLayout(boolean changed, int left, int top, int right,
+			int bottom) {
+		super.onLayout(changed, left, top, right, bottom);
+		float scrollScaleRatio = getScrollScaleRatio();
+		invalidatePageSizes();
+		invalidateScroll(scrollScaleRatio);
+		commitZoom();
+	}
+
+	/**
+	 * 定义每一页的大小
+	 */
+	void invalidatePageSizes() {
+		if (!isInitialized) {
+			return;
+		}
+		float heightAccum = 0;
+		int width = getWidth();
+		float zoom = zoomModel.getZoom();
+		for (int i = 0; i < pages.size(); i++) {
+			Page page = pages.get(i);
+			float pageHeight = page.getPageHeight(width, zoom);
+			page.setBounds(new RectF(0, heightAccum, width * zoom, heightAccum
+					+ pageHeight));
+			heightAccum += pageHeight;
+		}
+	}
+
+	private void invalidateScroll(float ratio) {
+		if (!isInitialized) {
+			return;
+		}
+		stopScroller();
+		final Page page = pages.get(0);
+		if (page == null || page.bounds == null) {
+			return;
+		}
+		scrollTo((int) (getScrollX() * ratio), (int) (getScrollY() * ratio));
+	}
+
+	private float getScrollScaleRatio() {
+		final Page page = pages.get(0);
+		if (page == null || page.bounds == null) {
+			return 0;
+		}
+		final float v = zoomModel.getZoom();
+		return getWidth() * v / page.bounds.width();
+	}
+
+	//停止动画 Scroller滚动到最终x与y位置时中止动画。
+	private void stopScroller() {
+		if (!scroller.isFinished()) {
+			scroller.abortAnimation(); //中止动画
+		}
+	}
+
+}

+ 69 - 0
src/com/poqop/document/GoToPageDialog.java

@@ -0,0 +1,69 @@
+package com.poqop.document;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+import com.poqop.R;
+
+public class GoToPageDialog extends Dialog
+{
+    private final DocumentView documentView;
+    private final DecodeService decodeService;
+
+    public GoToPageDialog(final Context context, final DocumentView documentView, final DecodeService decodeService)
+    {
+        super(context);
+        this.documentView = documentView;
+        this.decodeService = decodeService;
+        setTitle("跳转至:");
+        setContentView(R.layout.gotopage);
+        final Button button = (Button) findViewById(R.id.goToButton);
+        button.setOnClickListener(new View.OnClickListener()
+        {
+            public void onClick(View view)
+            {
+                goToPageAndDismiss();
+            }
+        });
+        final EditText editText = (EditText) findViewById(R.id.pageNumberTextEdit);
+        editText.setOnEditorActionListener(new TextView.OnEditorActionListener()
+        {
+            public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent)
+            {
+                if (actionId == EditorInfo.IME_NULL || actionId == EditorInfo.IME_ACTION_DONE)
+                {
+                    goToPageAndDismiss();
+                    return true;
+                }
+                return false;
+            }
+        });
+    }
+
+    private void goToPageAndDismiss()
+    {
+        navigateToPage();
+        dismiss();
+    }
+
+    /*
+     * 跳转页面,不存在时的提示
+     */
+    private void navigateToPage()
+    {
+        final EditText text = (EditText) findViewById(R.id.pageNumberTextEdit);
+        final int pageNumber = Integer.parseInt(text.getText().toString());
+        if (pageNumber < 1 || pageNumber > decodeService.getPageCount())
+        {
+            Toast.makeText(getContext(), "该文档只有: 1-" + decodeService.getPageCount(), 2000).show();
+            return;
+        }
+        documentView.goToPage(pageNumber-1);
+    }
+}

+ 55 - 0
src/com/poqop/document/MainBrowserActivity.java

@@ -0,0 +1,55 @@
+package com.poqop.document;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.HashMap;
+
+import org.vudroid.pdfdroid.PdfViewerActivity;
+
+public class MainBrowserActivity extends BaseBrowserActivity
+{
+	
+    private final static HashMap<String, Class<? extends Activity>> extensionToActivity = new HashMap<String, Class<? extends Activity>>();
+
+    static
+    {	
+    	//文件类型
+        extensionToActivity.put("pdf", PdfViewerActivity.class);
+ /*       extensionToActivity.put("djvu", DjvuViewerActivity.class);
+        extensionToActivity.put("djv", DjvuViewerActivity.class);*/
+    }
+
+    /**
+     * FileFilter  :文件类型筛选器
+     */
+    @Override
+    protected FileFilter createFileFilter()
+    {
+        return new FileFilter()
+        {
+            public boolean accept(File pathname)
+            {
+                for (String s : extensionToActivity.keySet())
+                {
+                    if (pathname.getName().endsWith("." + s)) return true;
+                }
+                return pathname.isDirectory();
+            }
+        };
+    }
+
+    @Override
+    protected void showDocument(Uri uri)
+    {
+        final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+        String uriString = uri.toString();
+        String extension = uriString.substring(uriString.lastIndexOf('.') + 1);
+        intent.setClass(this, extensionToActivity.get(extension));
+        startActivity(intent);
+    }
+}

+ 107 - 0
src/com/poqop/document/Page.java

@@ -0,0 +1,107 @@
+package com.poqop.document;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.text.TextPaint;
+
+/**
+ * 显示每页内容,分割线,和页数
+ * @author Administrator
+ */
+class Page {
+    final int index;
+    RectF bounds;
+    private PageTreeNode node;
+    private DocumentView documentView;
+    private final TextPaint textPaint = textPaint();
+    private final Paint fillPaint = fillPaint();
+    private final Paint strokePaint = strokePaint();
+
+    Page(DocumentView documentView, int index) {
+        this.documentView = documentView;
+        this.index = index;
+        node = new PageTreeNode(documentView, new RectF(0, 0, 1, 1), this, 1, null);
+    }
+
+    private float aspectRatio;
+
+    
+    float getPageHeight(int mainWidth, float zoom) {
+        return mainWidth / getAspectRatio() * zoom;
+    }
+
+    public int getTop() {
+        return Math.round(bounds.top);
+    }
+
+    public void draw(Canvas canvas) {
+        if (!isVisible()) {
+            return;
+        }
+        canvas.drawRect(bounds, fillPaint);
+        //渲染文本
+        canvas.drawText("Page " + (index + 1), bounds.centerX(), bounds.centerY(), textPaint);  //pagenumber
+        node.draw(canvas);
+        //画线
+        canvas.drawLine(bounds.left, bounds.top, bounds.right, bounds.top, strokePaint);
+        canvas.drawLine(bounds.left, bounds.bottom, bounds.right, bounds.bottom, strokePaint);
+    }
+
+    private Paint strokePaint() {
+        final Paint strokePaint = new Paint();
+        strokePaint.setColor(Color.BLACK);
+        strokePaint.setStyle(Paint.Style.STROKE);
+        strokePaint.setStrokeWidth(10);
+        return strokePaint;
+    }
+
+    private Paint fillPaint() {
+        final Paint fillPaint = new Paint();
+        fillPaint.setColor(Color.GRAY);
+        fillPaint.setStyle(Paint.Style.FILL);
+        return fillPaint;
+    }
+
+    private TextPaint textPaint() {
+        final TextPaint paint = new TextPaint();
+        paint.setColor(Color.BLACK);
+        paint.setAntiAlias(true);
+        paint.setTextSize(50);
+        paint.setTextAlign(Paint.Align.CENTER);
+        return paint;
+    }
+
+    public float getAspectRatio() {
+        return aspectRatio;
+    }
+
+    public void setAspectRatio(float aspectRatio) {
+        if (this.aspectRatio != aspectRatio) {
+            this.aspectRatio = aspectRatio;
+            documentView.invalidatePageSizes();
+        }
+    }
+
+    public boolean isVisible() {
+        return RectF.intersects(documentView.getViewRect(), bounds);
+    }
+
+    public void setAspectRatio(int width, int height) {
+        setAspectRatio(width * 1.0f / height);
+    }
+
+    void setBounds(RectF pageBounds) {
+        bounds = pageBounds;
+        node.invalidateNodeBounds();
+    }
+
+    public void updateVisibility() {
+        node.updateVisibility();
+    }
+
+    public void invalidate() {
+        node.invalidate();
+    }
+}

+ 278 - 0
src/com/poqop/document/PageTreeNode.java

@@ -0,0 +1,278 @@
+package com.poqop.document;
+
+import android.graphics.*;
+
+import java.lang.ref.SoftReference;
+
+/**
+ *ÏÔʾÄÚÈÝ¡£
+ * @author Administrator
+ *
+ */
+class PageTreeNode {
+    private static final int SLICE_SIZE = 65535;
+    private Bitmap bitmap;
+    private SoftReference<Bitmap> bitmapWeakReference;
+    private boolean decodingNow;
+    private final RectF pageSliceBounds;
+    private final Page page;
+    private PageTreeNode[] children;
+    private final int treeNodeDepthLevel;
+    private Matrix matrix = new Matrix();
+    private final Paint bitmapPaint = new Paint();
+    private DocumentView documentView;
+    private boolean invalidateFlag;
+    private Rect targetRect;
+    private RectF targetRectF;
+
+    PageTreeNode(DocumentView documentView, RectF localPageSliceBounds, Page page, int treeNodeDepthLevel, PageTreeNode parent) {
+        this.documentView = documentView;
+        this.pageSliceBounds = evaluatePageSliceBounds(localPageSliceBounds, parent);
+        this.page = page;
+        this.treeNodeDepthLevel = treeNodeDepthLevel;
+    }
+    
+    /*
+     * ¸üÐÂÏÔʾ
+     */
+    public void updateVisibility() {
+        invalidateChildren();
+        if (children != null) {
+            for (PageTreeNode child : children) {
+                child.updateVisibility();
+            }
+        }
+        if (isVisible()) {
+            if (!thresholdHit()) {
+                if (getBitmap() != null && !invalidateFlag) {
+                    restoreBitmapReference();
+                } else {
+                    decodePageTreeNode();
+                }
+            }
+        }
+        if (!isVisibleAndNotHiddenByChildren()) {
+            stopDecodingThisNode();
+            setBitmap(null);
+        }
+    }
+
+    public void invalidate() {
+        invalidateChildren();
+        invalidateRecursive();
+        updateVisibility();
+    }
+
+    private void invalidateRecursive() {
+        invalidateFlag = true;
+        if (children != null) {
+            for (PageTreeNode child : children) {
+                child.invalidateRecursive();
+            }
+        }
+        stopDecodingThisNode();
+    }
+
+    void invalidateNodeBounds() {
+        targetRect = null;
+        targetRectF = null;
+        if (children != null) {
+            for (PageTreeNode child : children) {
+                child.invalidateNodeBounds();
+            }
+        }
+    }
+
+    void draw(Canvas canvas) {
+        if (getBitmap() != null) {
+            canvas.drawBitmap(getBitmap(), new Rect(0, 0, getBitmap().getWidth(), getBitmap().getHeight()), getTargetRect(), bitmapPaint);
+        }
+        if (children == null) {
+            return;
+        }
+        for (PageTreeNode child : children) {
+            child.draw(canvas);
+        }
+    }
+
+    private boolean isVisible() {
+        return RectF.intersects(documentView.getViewRect(), getTargetRectF());
+    }
+
+    private RectF getTargetRectF() {
+        if (targetRectF == null) {
+            targetRectF = new RectF(getTargetRect());
+        }
+        return targetRectF;
+    }
+
+    
+    private void invalidateChildren() {
+        if (thresholdHit() && children == null && isVisible()) {
+            final int newThreshold = treeNodeDepthLevel * 2;
+            children = new PageTreeNode[]
+                    {
+                            new PageTreeNode(documentView, new RectF(0, 0, 0.5f, 0.5f), page, newThreshold, this),
+                            new PageTreeNode(documentView, new RectF(0.5f, 0, 1.0f, 0.5f), page, newThreshold, this),
+                            new PageTreeNode(documentView, new RectF(0, 0.5f, 0.5f, 1.0f), page, newThreshold, this),
+                            new PageTreeNode(documentView, new RectF(0.5f, 0.5f, 1.0f, 1.0f), page, newThreshold, this)
+                    };
+        }
+        if (!thresholdHit() && getBitmap() != null || !isVisible()) {
+            recycleChildren();
+        }
+    }
+
+    private boolean thresholdHit() {
+        float zoom = documentView.zoomModel.getZoom();
+        int mainWidth = documentView.getWidth();
+        float height = page.getPageHeight(mainWidth, zoom);
+        return (mainWidth * zoom * height) / (treeNodeDepthLevel * treeNodeDepthLevel) > SLICE_SIZE;
+    }
+
+    public Bitmap getBitmap() {
+        return bitmapWeakReference != null ? bitmapWeakReference.get() : null;
+    }
+
+    private void restoreBitmapReference() {
+        setBitmap(getBitmap());
+    }
+
+    private void decodePageTreeNode() {
+        if (isDecodingNow()) {
+            return;
+        }
+        setDecodingNow(true);
+        documentView.decodeService.decodePage(this, page.index, new DecodeService.DecodeCallback() {
+            public void decodeComplete(final Bitmap bitmap) {
+                documentView.post(new Runnable() {
+                    public void run() {
+                        setBitmap(bitmap);
+                        invalidateFlag = false;
+                        setDecodingNow(false);
+                        page.setAspectRatio(documentView.decodeService.getPageWidth(page.index), documentView.decodeService.getPageHeight(page.index));
+                        invalidateChildren();
+                    }
+                });
+            }
+        }, documentView.zoomModel.getZoom(), pageSliceBounds);
+    }
+
+    private RectF evaluatePageSliceBounds(RectF localPageSliceBounds, PageTreeNode parent) {
+        if (parent == null) {
+            return localPageSliceBounds;
+        }
+        final Matrix matrix = new Matrix();
+        matrix.postScale(parent.pageSliceBounds.width(), parent.pageSliceBounds.height());
+        matrix.postTranslate(parent.pageSliceBounds.left, parent.pageSliceBounds.top);
+        final RectF sliceBounds = new RectF();
+        matrix.mapRect(sliceBounds, localPageSliceBounds);
+        return sliceBounds;
+    }
+
+    private void setBitmap(Bitmap bitmap) {
+        if (bitmap != null && bitmap.getWidth() == -1 && bitmap.getHeight() == -1) {
+            return;
+        }
+        if (this.bitmap != bitmap) {
+            if (bitmap != null) {
+                if (this.bitmap != null) {
+                    this.bitmap.recycle();
+                }
+                bitmapWeakReference = new SoftReference<Bitmap>(bitmap);
+                documentView.postInvalidate();
+            }
+            this.bitmap = bitmap;
+        }
+    }
+
+    private boolean isDecodingNow() {
+        return decodingNow;
+    }
+
+    private void setDecodingNow(boolean decodingNow) {
+        if (this.decodingNow != decodingNow) {
+            this.decodingNow = decodingNow;
+            if (decodingNow) {
+                documentView.progressModel.increase();
+            } else {
+                documentView.progressModel.decrease();
+            }
+        }
+    }
+
+    private Rect getTargetRect() {
+        if (targetRect == null) {
+            matrix.reset();
+            matrix.postScale(page.bounds.width(), page.bounds.height());
+            matrix.postTranslate(page.bounds.left, page.bounds.top);
+            RectF targetRectF = new RectF();
+            matrix.mapRect(targetRectF, pageSliceBounds);
+            targetRect = new Rect((int) targetRectF.left, (int) targetRectF.top, (int) targetRectF.right, (int) targetRectF.bottom);
+        }
+        return targetRect;
+    }
+
+    private void stopDecodingThisNode() {
+        if (!isDecodingNow()) {
+            return;
+        }
+        documentView.decodeService.stopDecoding(this);
+        setDecodingNow(false);
+    }
+
+    private boolean isHiddenByChildren() {
+        if (children == null) {
+            return false;
+        }
+        for (PageTreeNode child : children) {
+            if (child.getBitmap() == null) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void recycleChildren() {
+        if (children == null) {
+            return;
+        }
+        for (PageTreeNode child : children) {
+            child.recycle();
+        }
+        if (!childrenContainBitmaps()) {
+            children = null;
+        }
+    }
+
+    private boolean containsBitmaps() {
+        return getBitmap() != null || childrenContainBitmaps();
+    }
+
+    private boolean childrenContainBitmaps() {
+        if (children == null) {
+            return false;
+        }
+        for (PageTreeNode child : children) {
+            if (child.containsBitmaps()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void recycle() {
+        stopDecodingThisNode();
+        setBitmap(null);
+        if (children != null) {
+            for (PageTreeNode child : children) {
+                child.recycle();
+            }
+        }
+    }
+
+    private boolean isVisibleAndNotHiddenByChildren() {
+        return isVisible() && !isHiddenByChildren();
+    }
+
+}

+ 63 - 0
src/com/poqop/document/ViewerPreferences.java

@@ -0,0 +1,63 @@
+package com.poqop.document;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.net.Uri;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.TreeMap;
+
+/**
+ * 最近浏览的文件
+ * @author Administrator
+ * 主要用SharedPreferences来进行存储
+ */
+public class ViewerPreferences
+{
+    private SharedPreferences sharedPreferences;
+    private static final String FULL_SCREEN = "FullScreen";
+
+    public ViewerPreferences(Context context)
+    {
+    	//得到最近浏览的文件参数
+        sharedPreferences = context.getSharedPreferences("ViewerPreferences", 0);
+    }
+
+    public void setFullScreen(boolean fullscreen)
+    {
+        final SharedPreferences.Editor editor = sharedPreferences.edit();
+     //   editor.putBoolean(FULL_SCREEN, fullscreen);
+        editor.commit();
+    }
+
+    public boolean isFullScreen()
+    {
+        return sharedPreferences.getBoolean(FULL_SCREEN, false);
+    }
+
+    public void addRecent(Uri uri)
+    {
+        SharedPreferences.Editor editor = sharedPreferences.edit();  //修改配置信息之后,并提交
+        editor.putString("recent:" + uri.toString(), uri.toString() + "\n" + System.currentTimeMillis());
+        editor.commit();
+    }
+
+    public List<Uri> getRecent()
+    {
+        TreeMap<Long, Uri> treeMap = new TreeMap<Long, Uri>();
+        for (String key : sharedPreferences.getAll().keySet())
+        {
+            if (key.startsWith("recent"))
+            {
+                String uriPlusDate = sharedPreferences.getString(key, null);
+                String[] uriThenDate = uriPlusDate.split("\n");
+                treeMap.put(Long.parseLong(uriThenDate.length > 1 ? uriThenDate[1] : "0"), Uri.parse(uriThenDate[0]));
+            }
+        }
+        ArrayList<Uri> list = new ArrayList<Uri>(treeMap.values());
+        Collections.reverse(list);
+        return list;
+    }
+}

+ 16 - 0
src/com/poqop/document/VuDroidLibraryLoader.java

@@ -0,0 +1,16 @@
+package com.poqop.document;
+
+public class VuDroidLibraryLoader
+{
+    private static boolean alreadyLoaded = false;
+
+    public static void load()
+    {
+        if (alreadyLoaded)
+        {
+            return;
+        }
+        System.loadLibrary("vudroid");
+        alreadyLoaded = true;
+    }
+}

+ 12 - 0
src/com/poqop/document/codec/CodecContext.java

@@ -0,0 +1,12 @@
+package com.poqop.document.codec;
+
+import android.content.ContentResolver;
+
+public interface CodecContext
+{
+    CodecDocument openDocument(String fileName);
+
+    void setContentResolver(ContentResolver contentResolver);
+
+    void recycle();
+}

+ 9 - 0
src/com/poqop/document/codec/CodecDocument.java

@@ -0,0 +1,9 @@
+package com.poqop.document.codec;
+
+public interface CodecDocument {
+    CodecPage getPage(int pageNumber);
+
+    int getPageCount();
+
+    void recycle();
+}

+ 19 - 0
src/com/poqop/document/codec/CodecPage.java

@@ -0,0 +1,19 @@
+package com.poqop.document.codec;
+
+import android.graphics.Bitmap;
+import android.graphics.RectF;
+
+public interface CodecPage
+{
+    boolean isDecoding();
+
+    void waitForDecode();
+
+    int getWidth();
+
+    int getHeight();
+
+    Bitmap renderBitmap(int width, int height, RectF pageSliceBounds);
+
+    void recycle();
+}

+ 10 - 0
src/com/poqop/document/events/BringUpZoomControlsEvent.java

@@ -0,0 +1,10 @@
+package com.poqop.document.events;
+
+public class BringUpZoomControlsEvent extends SafeEvent<BringUpZoomControlsListener>
+{
+    @Override
+    public void dispatchSafely(BringUpZoomControlsListener listener)
+    {
+        listener.toggleZoomControls();
+    }
+}

+ 6 - 0
src/com/poqop/document/events/BringUpZoomControlsListener.java

@@ -0,0 +1,6 @@
+package com.poqop.document.events;
+
+public interface BringUpZoomControlsListener
+{
+    public void toggleZoomControls();
+}

+ 22 - 0
src/com/poqop/document/events/CurrentPageListener.java

@@ -0,0 +1,22 @@
+package com.poqop.document.events;
+
+public interface CurrentPageListener
+{
+    void currentPageChanged(int pageIndex);
+
+    public class CurrentPageChangedEvent extends SafeEvent<CurrentPageListener>
+    {
+        private final int pageIndex;
+
+        public CurrentPageChangedEvent(int pageIndex)
+        {
+            this.pageIndex = pageIndex;
+        }
+
+        @Override
+        public void dispatchSafely(CurrentPageListener listener)
+        {
+            listener.currentPageChanged(pageIndex);
+        }
+    }
+}

+ 22 - 0
src/com/poqop/document/events/DecodingProgressListener.java

@@ -0,0 +1,22 @@
+package com.poqop.document.events;
+
+public interface DecodingProgressListener
+{
+    void decodingProgressChanged(int currentlyDecoding);
+
+    public class DecodingProgressEvent extends SafeEvent<DecodingProgressListener>
+    {
+        private final int currentlyDecoding;
+
+        public DecodingProgressEvent(int currentlyDecoding)
+        {
+            this.currentlyDecoding = currentlyDecoding;
+        }
+
+        @Override
+        public void dispatchSafely(DecodingProgressListener listener)
+        {
+            listener.decodingProgressChanged(currentlyDecoding);
+        }
+    }
+}

+ 6 - 0
src/com/poqop/document/events/Event.java

@@ -0,0 +1,6 @@
+package com.poqop.document.events;
+
+public interface Event<T>
+{
+    void dispatchOn(Object listener);
+}

+ 26 - 0
src/com/poqop/document/events/EventDispatcher.java

@@ -0,0 +1,26 @@
+package com.poqop.document.events;
+
+import java.util.ArrayList;
+
+public class EventDispatcher
+{
+    private final ArrayList<Object> listeners = new ArrayList<Object>();
+
+    public void dispatch(Event event)
+    {
+        for (Object listener : listeners)
+        {
+            event.dispatchOn(listener);
+        }
+    }
+
+    public void addEventListener(Object listener)
+    {
+        listeners.add(listener);
+    }
+
+    public void removeEventListener(Object listener)
+    {
+        listeners.remove(listener);
+    }
+}

+ 36 - 0
src/com/poqop/document/events/SafeEvent.java

@@ -0,0 +1,36 @@
+package com.poqop.document.events;
+
+import java.lang.reflect.Method;
+
+public abstract class SafeEvent<T> implements Event<T>
+{
+    private final Class<?> listenerType;
+
+    protected SafeEvent()
+    {
+        listenerType = getListenerType();
+    }
+
+    private Class<?> getListenerType()
+    {
+        for (Method method : getClass().getMethods())
+        {
+            if ("dispatchSafely".equals(method.getName()) && !method.isSynthetic())
+            {
+                return method.getParameterTypes()[0];
+            }
+        }
+        throw new RuntimeException("Couldn't find dispatchSafely method");
+    }
+
+    @SuppressWarnings({"unchecked"})
+    public final void dispatchOn(Object listener)
+    {
+        if (listenerType.isAssignableFrom(listener.getClass()))
+        {
+            dispatchSafely((T) listener);
+        }
+    }
+
+    public abstract void dispatchSafely(T listener);
+}

+ 19 - 0
src/com/poqop/document/events/ZoomChangedEvent.java

@@ -0,0 +1,19 @@
+package com.poqop.document.events;
+
+public class ZoomChangedEvent extends SafeEvent<ZoomListener>
+{
+    private final float newZoom;
+    private final float oldZoom;
+
+    public ZoomChangedEvent(float newZoom, float oldZoom)
+    {
+        this.newZoom = newZoom;
+        this.oldZoom = oldZoom;
+    }
+
+    @Override
+    public void dispatchSafely(ZoomListener listener)
+    {
+        listener.zoomChanged(newZoom, oldZoom);
+    }
+}

+ 17 - 0
src/com/poqop/document/events/ZoomListener.java

@@ -0,0 +1,17 @@
+package com.poqop.document.events;
+
+public interface ZoomListener
+{
+    void zoomChanged(float newZoom, float oldZoom);
+
+    void commitZoom();
+
+    public class CommitZoomEvent extends SafeEvent<ZoomListener>
+    {
+        @Override
+        public void dispatchSafely(ZoomListener listener)
+        {
+            listener.commitZoom();
+        }
+    }
+}

+ 24 - 0
src/com/poqop/document/models/CurrentPageModel.java

@@ -0,0 +1,24 @@
+package com.poqop.document.models;
+
+import com.poqop.document.events.CurrentPageListener;
+import com.poqop.document.events.EventDispatcher;
+
+/*
+ * µ±Ç°Ò³Ãæ
+ */
+public class CurrentPageModel extends EventDispatcher
+{
+    private int currentPageIndex;
+
+    /*
+     * ÉèÖõ±Ç°Ò³
+     */
+    public void setCurrentPageIndex(int currentPageIndex)
+    {
+        if (this.currentPageIndex != currentPageIndex)
+        {
+            this.currentPageIndex = currentPageIndex;
+            dispatch(new CurrentPageListener.CurrentPageChangedEvent(currentPageIndex));
+        }
+    }
+}

+ 26 - 0
src/com/poqop/document/models/DecodingProgressModel.java

@@ -0,0 +1,26 @@
+package com.poqop.document.models;
+
+import com.poqop.document.events.DecodingProgressListener;
+import com.poqop.document.events.EventDispatcher;
+
+public class DecodingProgressModel extends EventDispatcher
+{
+    private int currentlyDecoding;
+
+    public void increase()
+    {
+        currentlyDecoding++;
+        dispatchChanged();
+    }
+
+    private void dispatchChanged()
+    {
+        dispatch(new DecodingProgressListener.DecodingProgressEvent(currentlyDecoding));
+    }
+
+    public void decrease()
+    {
+        currentlyDecoding--;
+        dispatchChanged();
+    }
+}

+ 71 - 0
src/com/poqop/document/models/ZoomModel.java

@@ -0,0 +1,71 @@
+package com.poqop.document.models;
+
+import com.poqop.document.events.BringUpZoomControlsEvent;
+import com.poqop.document.events.EventDispatcher;
+import com.poqop.document.events.ZoomChangedEvent;
+import com.poqop.document.events.ZoomListener;
+
+public class ZoomModel extends EventDispatcher
+{
+    private float zoom = 1.0f;
+    private static final float INCREMENT_DELTA = 0.1f;
+    private boolean horizontalScrollEnabled;
+    private boolean isCommited;
+
+    public void setZoom(float zoom)
+    {
+        zoom = Math.max(zoom, 1.0f);
+        if (this.zoom != zoom)
+        {
+            float oldZoom = this.zoom;
+            this.zoom = zoom;
+            isCommited = false;
+            dispatch(new ZoomChangedEvent(zoom, oldZoom));
+        }
+    }
+
+    public float getZoom()
+    {
+        return zoom;
+    }
+
+    //Ôö¼ÓËõ·Å
+    public void increaseZoom()
+    {
+        setZoom(getZoom() + INCREMENT_DELTA);
+    }
+    //¼õÉÙËõ·Å
+    public void decreaseZoom()
+    {
+        setZoom(getZoom() - INCREMENT_DELTA);
+    }
+
+    public void toggleZoomControls()
+    {
+        dispatch(new BringUpZoomControlsEvent());
+    }
+
+    public void setHorizontalScrollEnabled(boolean horizontalScrollEnabled)
+    {
+        this.horizontalScrollEnabled = horizontalScrollEnabled;
+    }
+
+    public boolean isHorizontalScrollEnabled()
+    {
+        return horizontalScrollEnabled;
+    }
+
+    public boolean canDecrement()
+    {
+        return zoom > 1.0f;
+    }
+
+    public void commit()
+    {
+        if (!isCommited)
+        {
+            isCommited = true;
+            dispatch(new ZoomListener.CommitZoomEvent());
+        }
+    }
+}

+ 11 - 0
src/com/poqop/document/multitouch/MultiTouchZoom.java

@@ -0,0 +1,11 @@
+package com.poqop.document.multitouch;
+
+import android.view.MotionEvent;
+
+public interface MultiTouchZoom {
+    boolean onTouchEvent(MotionEvent ev);
+
+    boolean isResetLastPointAfterZoom();
+
+    void setResetLastPointAfterZoom(boolean resetLastPointAfterZoom);
+}

+ 47 - 0
src/com/poqop/document/multitouch/MultiTouchZoomImpl.java

@@ -0,0 +1,47 @@
+package com.poqop.document.multitouch;
+
+import android.view.MotionEvent;
+
+import com.poqop.document.models.ZoomModel;
+
+public class MultiTouchZoomImpl implements MultiTouchZoom {
+    private final ZoomModel zoomModel;
+    private boolean resetLastPointAfterZoom;
+    private float lastZoomDistance;
+
+    public MultiTouchZoomImpl(ZoomModel zoomModel) {
+        this.zoomModel = zoomModel;
+    }
+
+    public boolean onTouchEvent(MotionEvent ev) {
+       /* if ((ev.getAction() & MotionEvent.ACTION_POINTER_DOWN) == MotionEvent.ACTION_POINTER_DOWN) {
+            lastZoomDistance = getZoomDistance(ev);
+            return true;
+        }
+        if ((ev.getAction() & MotionEvent.ACTION_POINTER_UP) == MotionEvent.ACTION_POINTER_UP) {
+            lastZoomDistance = 0;
+            zoomModel.commit();
+            resetLastPointAfterZoom = true;
+            return true;
+        }*/
+        if (ev.getAction() == MotionEvent.ACTION_MOVE && lastZoomDistance != 0) {
+           /* float zoomDistance = getZoomDistance(ev);
+            zoomModel.setZoom(zoomModel.getZoom() * zoomDistance / lastZoomDistance);
+            lastZoomDistance = zoomDistance;*/
+            return true;
+        }
+        return false;
+    }
+
+  /*  private float getZoomDistance(MotionEvent ev) {
+        return (float) Math.sqrt(Math.pow(ev.getX(0) - ev.getX(1), 2) + Math.pow(ev.getY(0) - ev.getY(1), 2));
+    }*/
+
+    public boolean isResetLastPointAfterZoom() {
+        return resetLastPointAfterZoom;
+    }
+
+    public void setResetLastPointAfterZoom(boolean resetLastPointAfterZoom) {
+        this.resetLastPointAfterZoom = resetLastPointAfterZoom;
+    }
+}

+ 98 - 0
src/com/poqop/document/presentation/BrowserAdapter.java

@@ -0,0 +1,98 @@
+package com.poqop.document.presentation;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.poqop.R;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.*;
+
+public class BrowserAdapter extends BaseAdapter
+{
+    private final Context context;
+    private File currentDirectory;
+    private List<File> files = Collections.emptyList();
+    private final FileFilter filter;
+
+    public BrowserAdapter(Context context, FileFilter filter)
+    {
+        this.context = context;
+        this.filter = filter;
+    }
+
+    public int getCount()
+    {
+        return files.size();
+    }
+
+    public File getItem(int i)
+    {
+        return files.get(i);
+    }
+
+    public long getItemId(int i)
+    {
+        return i;
+    }
+
+    public View getView(int i, View view, ViewGroup viewGroup)
+    {
+        final View browserItem = LayoutInflater.from(context).inflate(R.layout.browseritem, viewGroup, false);
+        final ImageView imageView = (ImageView) browserItem.findViewById(R.id.browserItemIcon);
+        final File file = files.get(i);
+        final TextView textView = (TextView) browserItem.findViewById(R.id.browserItemText);
+        textView.setText(file.getName());
+        if (file.equals(currentDirectory.getParentFile()))
+        {
+            imageView.setImageResource(R.drawable.arrowup);
+            textView.setText(file.getAbsolutePath());
+        }
+        else if (file.isDirectory())
+        {
+            imageView.setImageResource(R.drawable.folderopen);
+        }
+        else
+        {
+            imageView.setImageResource(R.drawable.book);
+        }
+        return browserItem;
+    }
+
+    public void setCurrentDirectory(File currentDirectory)
+    {
+        final File[] fileArray = currentDirectory.listFiles(filter);
+        ArrayList<File> files = new ArrayList<File>(fileArray != null ? Arrays.asList(fileArray) : Collections.<File>emptyList());
+        this.currentDirectory = currentDirectory;
+        Collections.sort(files, new Comparator<File>()
+        {
+            public int compare(File o1, File o2)
+            {
+                if (o1.isDirectory() && o2.isFile()) return -1;
+                if (o1.isFile() && o2.isDirectory()) return 1;
+                return o1.getName().compareTo(o2.getName());
+            }
+        });
+        if (currentDirectory.getParentFile() != null)
+        {
+            files.add(0, currentDirectory.getParentFile());
+        }
+        setFiles(files);
+    }
+
+    public void setFiles(List<File> files)
+    {
+        this.files = files;
+        notifyDataSetInvalidated();
+    }
+
+    public File getCurrentDirectory()
+    {
+        return currentDirectory;
+    }
+}

+ 50 - 0
src/com/poqop/document/presentation/UriBrowserAdapter.java

@@ -0,0 +1,50 @@
+package com.poqop.document.presentation;
+
+import android.net.Uri;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.poqop.R;
+
+import java.util.Collections;
+import java.util.List;
+
+public class UriBrowserAdapter extends BaseAdapter
+{
+    private List<Uri> uris = Collections.emptyList();
+
+    public int getCount()
+    {
+        return uris.size();
+    }
+
+    public Uri getItem(int i)
+    {
+        return uris.get(i);
+    }
+
+    public long getItemId(int i)
+    {
+        return i; 
+    }
+
+    public View getView(int i, View view, ViewGroup viewGroup)
+    {
+        final View browserItem = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.browseritem, viewGroup, false);
+        final ImageView imageView = (ImageView) browserItem.findViewById(R.id.browserItemIcon);
+        final Uri uri = uris.get(i);
+        final TextView textView = (TextView) browserItem.findViewById(R.id.browserItemText);
+        textView.setText(uri.getLastPathSegment());
+        imageView.setImageResource(R.drawable.book);
+        return browserItem;
+    }
+
+    public void setUris(List<Uri> uris)
+    {
+        this.uris = uris;
+        notifyDataSetInvalidated();
+    }
+}

+ 32 - 0
src/com/poqop/document/utils/MD5StringUtil.java

@@ -0,0 +1,32 @@
+package com.poqop.document.utils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+public class MD5StringUtil
+{
+    private static final MessageDigest digest;
+
+    static
+    {
+        try
+        {
+            digest = MessageDigest.getInstance("MD5");
+        }
+        catch (NoSuchAlgorithmException e)
+        {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static String md5StringFor(String s)
+    {
+        final byte[] hash = digest.digest(s.getBytes());
+        final StringBuilder builder = new StringBuilder();
+        for (byte b : hash)
+        {
+            builder.append(Integer.toString(b & 0xFF, 16));    
+        }
+        return builder.toString();
+    }
+}

+ 22 - 0
src/com/poqop/document/utils/PathFromUri.java

@@ -0,0 +1,22 @@
+package com.poqop.document.utils;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+
+public class PathFromUri
+{
+    public static String retrieve(ContentResolver resolver, Uri uri)
+    {
+        if (uri.getScheme().equals("file"))
+        {
+            return uri.getPath();
+        }
+        final Cursor cursor = resolver.query(uri, new String[]{"_data"}, null, null, null);
+        if (cursor.moveToFirst())
+        {
+            return cursor.getString(0);
+        }
+        throw new RuntimeException("Can't retrieve path from uri: " + uri.toString());
+    }
+}

+ 60 - 0
src/com/poqop/document/views/PageViewZoomControls.java

@@ -0,0 +1,60 @@
+package com.poqop.document.views;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.TranslateAnimation;
+import android.widget.LinearLayout;
+
+import com.poqop.document.events.BringUpZoomControlsListener;
+import com.poqop.document.models.ZoomModel;
+
+public class PageViewZoomControls extends LinearLayout implements BringUpZoomControlsListener
+{
+    public PageViewZoomControls(Context context, final ZoomModel zoomModel)
+    {
+        super(context);
+        show();
+        setOrientation(LinearLayout.HORIZONTAL);
+        setGravity(Gravity.BOTTOM);
+    //    addView(new ZoomRoll(context, zoomModel));
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event)
+    {
+        return false;
+    }
+
+    public void toggleZoomControls()
+    {
+        if (getVisibility() == View.VISIBLE)
+        {
+            hide();
+        }
+        else
+        {
+            show();
+        }
+    }
+
+    private void show()
+    {
+        fade(View.VISIBLE, getWidth(), 0.0f);
+    }
+
+    private void hide()
+    {
+        fade(View.GONE, 0.0f, getWidth());
+    }
+
+    private void fade(int visibility, float startDelta, float endDelta)
+    {
+        Animation anim = new TranslateAnimation(0,0, startDelta, endDelta);
+        anim.setDuration(500);
+        startAnimation(anim);
+        setVisibility(visibility);
+    }
+}

+ 95 - 0
src/com/poqop/document/views/ZoomRoll.java

@@ -0,0 +1,95 @@
+package com.poqop.document.views;
+
+import android.content.Context;
+import android.graphics.*;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Scroller;
+import android.widget.Toast;
+
+import com.poqop.R;
+
+import com.poqop.document.models.ZoomModel;
+
+public class ZoomRoll extends View
+{
+    private final Bitmap left;
+    private final Bitmap right;
+    private final Bitmap center;
+    private final Bitmap serifs;
+    private final Bitmap title;
+    /**
+     * VelocityTracker :是用来跟踪触摸速度的类
+     * 
+     */
+    private VelocityTracker velocityTracker;  
+    private Scroller scroller;
+    private float lastX;
+    private static final int MAX_VALUE = 1000;
+    private final ZoomModel zoomModel;
+    private static final float MULTIPLIER = 400.0f;
+
+    private static final float MULTOP=10.0f;
+    
+    public ZoomRoll(Context context, ZoomModel zoomModel)
+    {
+        super(context);
+        this.zoomModel = zoomModel;
+        left = BitmapFactory.decodeResource(context.getResources(), R.drawable.left);
+        right = BitmapFactory.decodeResource(context.getResources(), R.drawable.right);
+        center = BitmapFactory.decodeResource(context.getResources(), R.drawable.center);
+        serifs = BitmapFactory.decodeResource(context.getResources(), R.drawable.serifs);
+    
+        title = BitmapFactory.decodeResource(context.getResources(), R.drawable.zoomin);
+       
+        scroller = new Scroller(context);
+        
+        //设置子View的布局
+        setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
+    }
+
+    
+    
+    /**
+     * 测量。。
+     * setMeasuredDimension(int width,int heith,)
+     * 将被用来渲染组件.应当尽量在传递进来的width和height 声明之间.
+     */
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
+    {
+        setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), Math.max(left.getHeight(), right.getHeight()));
+    }
+
+    /*
+     * 必要时由父控件调用请求或通知其一个子节点需要更新它的mScrollX和mScrollY的值。
+     */
+    @Override
+    public void computeScroll()
+    {
+        if (scroller.computeScrollOffset())
+        {
+            setCurrentValue(scroller.getCurrX());
+            invalidate();
+        }
+        else
+        {
+            zoomModel.commit();
+        }
+    }
+
+    public float getCurrentValue()
+    {
+        return (zoomModel.getZoom() - 1.2f) * MULTIPLIER;
+    }
+
+    public void setCurrentValue(float currentValue)
+    {
+        if (currentValue < 0.0) currentValue = 0.0f;
+        if (currentValue > MAX_VALUE) currentValue = MAX_VALUE;
+        final float zoom = 1.0f + currentValue / MULTIPLIER;
+        zoomModel.setZoom(zoom);
+    }
+}

+ 15 - 0
src/org/vudroid/pdfdroid/PdfViewerActivity.java

@@ -0,0 +1,15 @@
+package org.vudroid.pdfdroid;
+
+import com.poqop.document.BaseViewerActivity;
+import com.poqop.document.DecodeService;
+import com.poqop.document.DecodeServiceBase;
+import org.vudroid.pdfdroid.codec.PdfContext;
+
+public class PdfViewerActivity extends BaseViewerActivity
+{
+    @Override
+    protected DecodeService createDecodeService()
+    {
+        return new DecodeServiceBase(new PdfContext());
+    }
+}

+ 27 - 0
src/org/vudroid/pdfdroid/codec/PdfContext.java

@@ -0,0 +1,27 @@
+package org.vudroid.pdfdroid.codec;
+
+import android.content.ContentResolver;
+import com.poqop.document.VuDroidLibraryLoader;
+import com.poqop.document.codec.CodecContext;
+import com.poqop.document.codec.CodecDocument;
+
+public class PdfContext implements CodecContext
+{
+    static
+    {
+        VuDroidLibraryLoader.load();
+    }
+
+    public CodecDocument openDocument(String fileName)
+    {
+        return PdfDocument.openDocument(fileName, "");
+    }
+
+    public void setContentResolver(ContentResolver contentResolver)
+    {
+        //TODO
+    }
+
+    public void recycle() {
+    }
+}

+ 50 - 0
src/org/vudroid/pdfdroid/codec/PdfDocument.java

@@ -0,0 +1,50 @@
+package org.vudroid.pdfdroid.codec;
+
+import com.poqop.document.codec.CodecDocument;
+import com.poqop.document.codec.CodecPage;
+
+public class PdfDocument implements CodecDocument
+{
+    private long docHandle;
+    private static final int FITZMEMORY = 512 * 1024;
+
+    private PdfDocument(long docHandle)
+    {
+        this.docHandle = docHandle;
+    }
+
+    public CodecPage getPage(int pageNumber)
+    {
+        return PdfPage.createPage(docHandle, pageNumber + 1);
+    }
+
+    public int getPageCount()
+    {
+        return getPageCount(docHandle);
+    }
+
+    static PdfDocument openDocument(String fname, String pwd)
+    {
+        return new PdfDocument(open(FITZMEMORY, fname, pwd));
+    }
+
+    private static native long open(int fitzmemory, String fname, String pwd);
+
+    private static native void free(long handle);
+
+    private static native int getPageCount(long handle);
+
+    @Override
+    protected void finalize() throws Throwable
+    {
+        recycle();
+        super.finalize();
+    }
+
+    public synchronized void recycle() {
+        if (docHandle != 0) {
+            free(docHandle);
+            docHandle = 0;
+        }
+    }
+}

+ 121 - 0
src/org/vudroid/pdfdroid/codec/PdfPage.java

@@ -0,0 +1,121 @@
+package org.vudroid.pdfdroid.codec;
+
+import android.graphics.Bitmap;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import com.poqop.document.codec.CodecPage;
+
+import java.nio.ByteBuffer;
+
+public class PdfPage implements CodecPage
+{
+    private long pageHandle;
+    private long docHandle;
+
+    private PdfPage(long pageHandle, long docHandle)
+    {
+        this.pageHandle = pageHandle;
+        this.docHandle = docHandle;
+    }
+
+    public boolean isDecoding()
+    {
+        return false;  //TODO
+    }
+
+    public void waitForDecode()
+    {
+        //TODO
+    }
+
+    public int getWidth()
+    {
+        return (int) getMediaBox().width();
+    }
+
+    public int getHeight()
+    {
+        return (int) getMediaBox().height();
+    }
+
+    public Bitmap renderBitmap(int width, int height, RectF pageSliceBounds)
+    {
+        Matrix matrix = new Matrix();
+        matrix.postScale(width / getMediaBox().width(), -height / getMediaBox().height());
+        matrix.postTranslate(0, height);
+        matrix.postTranslate(-pageSliceBounds.left*width, -pageSliceBounds.top*height);
+        matrix.postScale(1/pageSliceBounds.width(), 1/pageSliceBounds.height());
+        return render(new Rect(0,0,width,height), matrix);
+    }
+
+    static PdfPage createPage(long dochandle, int pageno)
+    {
+        return new PdfPage(open(dochandle, pageno), dochandle);
+    }
+
+    @Override
+    protected void finalize() throws Throwable
+    {
+        recycle();
+        super.finalize();
+    }
+
+    public synchronized void recycle() {
+        if (pageHandle != 0) {
+            free(pageHandle);
+            pageHandle = 0;
+        }
+    }
+
+    private RectF getMediaBox()
+    {
+        float[] box = new float[4];
+        getMediaBox(pageHandle, box);
+        return new RectF(box[0], box[1], box[2], box[3]);
+    }
+
+    public Bitmap render(Rect viewbox, Matrix matrix)
+	{
+        int[] mRect = new int[4];
+        mRect[0] = viewbox.left;
+		mRect[1] = viewbox.top;
+		mRect[2] = viewbox.right;
+		mRect[3] = viewbox.bottom;
+
+        float[] matrixSource = new float[9];
+        float[] matrixArray = new float[6];
+        matrix.getValues(matrixSource);
+		matrixArray[0] = matrixSource[0];
+		matrixArray[1] = matrixSource[3];
+		matrixArray[2] = matrixSource[1];
+		matrixArray[3] = matrixSource[4];
+		matrixArray[4] = matrixSource[2];
+		matrixArray[5] = matrixSource[5];
+
+        int width = viewbox.width();
+        int height = viewbox.height();
+        int[] bufferarray = new int[width * height];
+        nativeCreateView(docHandle, pageHandle, mRect, matrixArray, bufferarray);
+        return Bitmap.createBitmap(bufferarray, width, height, Bitmap.Config.RGB_565);
+        /*ByteBuffer buffer = ByteBuffer.allocateDirect(width * height * 2);
+        render(docHandle, docHandle, mRect, matrixArray, buffer, ByteBuffer.allocateDirect(width * height * 8));
+        final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565);
+        bitmap.copyPixelsFromBuffer(buffer);
+        return bitmap;*/
+	}
+
+    private static native void getMediaBox(long handle, float[] mediabox);
+
+    private static native void free(long handle);
+
+    private static native long open(long dochandle, int pageno);
+
+    private static native void render(long dochandle, long pagehandle,
+		int[] viewboxarray, float[] matrixarray,
+		ByteBuffer byteBuffer, ByteBuffer tempBuffer);
+
+    private native void nativeCreateView(long dochandle, long pagehandle,
+		int[] viewboxarray, float[] matrixarray,
+		int[] bufferarray);
+}