DocumentView.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. package com.poqop.document;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. import android.content.Context;
  5. import android.graphics.Canvas;
  6. import android.graphics.PointF;
  7. import android.graphics.RectF;
  8. import android.util.FloatMath;
  9. import android.view.KeyEvent;
  10. import android.view.MotionEvent;
  11. import android.view.VelocityTracker;
  12. import android.view.View;
  13. import android.widget.Scroller;
  14. import com.poqop.document.events.ZoomListener;
  15. import com.poqop.document.models.CurrentPageModel;
  16. import com.poqop.document.models.DecodingProgressModel;
  17. import com.poqop.document.models.ZoomModel;
  18. import com.poqop.document.multitouch.MultiTouchZoom;
  19. /**
  20. * 屏幕滚动
  21. * 处理用户翻页操作,比如滑动、点击上下左右键等。
  22. * 此时根据具体的用户操作计算需要显示哪几页,通知BaseViewerActivity去显示这些页
  23. * @author Administrator
  24. *
  25. */
  26. public class DocumentView extends View implements ZoomListener {
  27. final ZoomModel zoomModel;
  28. private final CurrentPageModel currentPageModel;
  29. DecodeService decodeService;
  30. private final HashMap<Integer, Page> pages = new HashMap<Integer, Page>();
  31. private boolean isInitialized = false;
  32. private int pageToGoTo;
  33. private float lastX;
  34. private float lastY;
  35. private VelocityTracker velocityTracker;
  36. private final Scroller scroller;
  37. DecodingProgressModel progressModel;
  38. private RectF viewRect;
  39. private boolean inZoom;
  40. private long lastDownEventTime;
  41. private static final int DOUBLE_TAP_TIME = 500;
  42. private MultiTouchZoom multiTouchZoom;
  43. private static final int MAX_VALUE = 3800;
  44. private static final float MULTIPLIER = 400.0f;
  45. static final int NONE = 0; //空闲
  46. static final int DRAG = 1; //拖动
  47. static final int ZOOM = 2; //缩放
  48. int mode = NONE;
  49. private float oldDist;
  50. private PointF midPoint = new PointF();
  51. private boolean isZoom = false;
  52. private boolean dblclick = true;
  53. public DocumentView(Context context, final ZoomModel zoomModel,
  54. DecodingProgressModel progressModel,
  55. CurrentPageModel currentPageModel) {
  56. super(context);
  57. this.zoomModel = zoomModel;
  58. this.progressModel = progressModel;
  59. this.currentPageModel = currentPageModel;
  60. setKeepScreenOn(true);
  61. scroller = new Scroller(getContext());
  62. setFocusable(true);
  63. setFocusableInTouchMode(true);
  64. }
  65. public void setDecodeService(DecodeService decodeService) {
  66. this.decodeService = decodeService;
  67. }
  68. private void init() {
  69. if (isInitialized) {
  70. return;
  71. }
  72. final int width = decodeService.getEffectivePagesWidth(); //获取试图的宽
  73. final int height = decodeService.getEffectivePagesHeight(); //高
  74. for (int i = 0; i < decodeService.getPageCount(); i++) {
  75. pages.put(i, new Page(this, i));
  76. pages.get(i).setAspectRatio(width, height);
  77. }
  78. isInitialized = true;
  79. invalidatePageSizes();
  80. goToPageImpl(pageToGoTo);
  81. }
  82. private void goToPageImpl(final int toPage) {
  83. scrollTo(0, pages.get(toPage).getTop());
  84. }
  85. /*
  86. * 让视图更新
  87. */
  88. @Override
  89. protected void onScrollChanged(int l, int t, int oldl, int oldt) {
  90. super.onScrollChanged(l, t, oldl, oldt);
  91. // bounds could be not updated
  92. post(new Runnable() {
  93. public void run() {
  94. currentPageModel.setCurrentPageIndex(getCurrentPage());
  95. }
  96. });
  97. if (inZoom) {
  98. return;
  99. }
  100. // on scrollChanged can be called from scrollTo just after new layout
  101. // applied so we should wait for relayout
  102. post(new Runnable() {
  103. public void run() {
  104. updatePageVisibility();
  105. }
  106. });
  107. }
  108. private void updatePageVisibility() {
  109. for (Page page : pages.values()) {
  110. page.updateVisibility();
  111. }
  112. }
  113. public void commitZoom() {
  114. for (Page page : pages.values()) {
  115. page.invalidate();
  116. }
  117. inZoom = false;
  118. }
  119. public void showDocument() {
  120. // use post to ensure that document view has width and height before
  121. // decoding begin
  122. post(new Runnable() {
  123. public void run() {
  124. init();
  125. updatePageVisibility();
  126. }
  127. });
  128. }
  129. public void goToPage(int toPage) {
  130. if (isInitialized) {
  131. goToPageImpl(toPage);
  132. } else {
  133. pageToGoTo = toPage;
  134. }
  135. }
  136. public int getCurrentPage() {
  137. for (Map.Entry<Integer, Page> entry : pages.entrySet()) {
  138. if (entry.getValue().isVisible()) {
  139. return entry.getKey();
  140. }
  141. }
  142. return 0;
  143. }
  144. /**
  145. * 缩放功能的处理
  146. */
  147. public void zoomChanged(float newZoom, float oldZoom) {
  148. inZoom = true;
  149. stopScroller();
  150. final float ratio = newZoom / oldZoom;
  151. invalidatePageSizes();
  152. scrollTo(
  153. (int) ((getScrollX() + getWidth() / 2) * ratio - getWidth() / 2),
  154. (int) ((getScrollY() + getHeight() / 2) * ratio - getHeight() / 2));
  155. postInvalidate();
  156. }
  157. /**
  158. * 触摸屏事件
  159. */
  160. @Override
  161. public boolean onTouchEvent(MotionEvent ev) {
  162. super.onTouchEvent(ev);
  163. if (multiTouchZoom != null) {
  164. if (multiTouchZoom.onTouchEvent(ev)) {
  165. return true;
  166. }
  167. if (multiTouchZoom.isResetLastPointAfterZoom()) {
  168. setLastPosition(ev);
  169. multiTouchZoom.setResetLastPointAfterZoom(false);
  170. }
  171. }
  172. if (velocityTracker == null) {
  173. velocityTracker = VelocityTracker.obtain(); //用obtain()函数来获得类的实例,开始计算速度
  174. }
  175. velocityTracker.addMovement(ev); //这个方法是将motion event 添加到velocityTracker
  176. switch (ev.getAction() & MotionEvent.ACTION_MASK) {
  177. case MotionEvent.ACTION_DOWN:
  178. mode = DRAG;
  179. /*
  180. * 双击放大
  181. * 用第一次点击和第二次点击的时间差来判断是否为双击
  182. */
  183. if ((ev.getEventTime() - lastDownEventTime < DOUBLE_TAP_TIME) && dblclick) {
  184. setCurrentValue(getmagnifyCurrentValues() - (ev.getX() - lastX));
  185. dblclick=false;
  186. mode = DRAG;
  187. /*
  188. * 双击缩小
  189. */
  190. }else if((ev.getEventTime() - lastDownEventTime < DOUBLE_TAP_TIME) && !dblclick) {
  191. setCurrentValue(getReduceCurrentValues() - (ev.getX() - lastX));
  192. dblclick=true;
  193. }else{
  194. if (!scroller.isFinished()) {
  195. scroller.abortAnimation();
  196. zoomModel.commit();
  197. }
  198. else {
  199. lastDownEventTime = ev.getEventTime();
  200. }
  201. }
  202. stopScroller();
  203. setLastPosition(ev);
  204. /**
  205. * velocityTracker 用来追踪触摸事件(flinging事件和其他手势事件)的速率
  206. * fling(int startX, int startY, int velocityX, int velocityY, int minX, int maxX, int minY, int maxY)
  207. * 方法进行控件滚动时各种位置坐标数值的计算
  208. */
  209. case MotionEvent.ACTION_UP:
  210. velocityTracker.computeCurrentVelocity(1000); // 初始化速率
  211. scroller.fling(getScrollX(), getScrollY(), (int) -velocityTracker.getXVelocity(),
  212. (int) -velocityTracker.getYVelocity(), getLeftLimit(), getRightLimit(), getTopLimit(), getBottomLimit());
  213. velocityTracker.recycle(); //回收
  214. velocityTracker = null;
  215. mode = DRAG;
  216. break;
  217. /**
  218. * API原文是 A non-primary pointer has gone down. 翻译过来就是:非第一个点按下
  219. */
  220. case MotionEvent.ACTION_POINTER_DOWN:
  221. oldDist = spacing(ev);
  222. midPoint(midPoint, ev);
  223. isZoom = true;
  224. mode = ZOOM;
  225. break;
  226. case MotionEvent.ACTION_POINTER_UP:
  227. mode=DRAG;
  228. break;
  229. case MotionEvent.ACTION_MOVE:
  230. if (mode == DRAG) {
  231. scrollBy((int) (lastX - ev.getX()), (int) (lastY - ev.getY()));
  232. setLastPosition(ev);
  233. break;
  234. } else if (mode==ZOOM) {
  235. float newDist = spacing(ev);
  236. /**
  237. * 表示新的距离比两个手指刚触碰的距离大》》》放大 ( +10个像素用来延迟一下放大
  238. */
  239. if (newDist > oldDist + 10) {
  240. oldDist = spacing(ev);
  241. setCurrentValue(getCurrentValue() - (ev.getX() - lastX));
  242. lastX = ev.getX();
  243. }
  244. /**
  245. * 表示新的距离比两个手指刚触碰的距离小:>>>>缩小
  246. */
  247. if (newDist + 20 < oldDist) {
  248. oldDist = spacing(ev);
  249. setCurrentValue(getRedCurrentValues()
  250. - (ev.getX() - lastX));
  251. lastX = ev.getX();
  252. }
  253. break;
  254. }
  255. }
  256. return true;
  257. }
  258. // 双击放大
  259. public float getmagnifyCurrentValues() {
  260. float mv = (zoomModel.getZoom() - 0.2f) * 2200;
  261. return mv;
  262. }
  263. //多点触摸放大
  264. public float getCurrentValue() {
  265. return (zoomModel.getZoom() - 0.5f) * 350f;
  266. }
  267. // 双击縮小
  268. public float getReduceCurrentValues() {
  269. return (zoomModel.getZoom() - 1.0f) *0.1f;
  270. }
  271. //多点触摸缩小
  272. public float getRedCurrentValues() {
  273. return (zoomModel.getZoom() - 1.0f) *350f;
  274. }
  275. public void setCurrentValue(float currentValue) {
  276. if (currentValue < 0.0)
  277. currentValue = 0.0f;
  278. if (currentValue > MAX_VALUE)
  279. currentValue = MAX_VALUE;
  280. final float zoom = 1.0f + currentValue / MULTIPLIER;
  281. zoomModel.setZoom(zoom);
  282. }
  283. /*
  284. * 间隔
  285. */
  286. private float spacing(MotionEvent event) {
  287. float x = event.getX(0) - event.getX(1);
  288. float y = event.getY(0) - event.getY(1);
  289. return FloatMath.sqrt(x * x + y * y);
  290. }
  291. /*
  292. * 得到中点
  293. */
  294. private void midPoint(PointF point, MotionEvent event) {
  295. float x = event.getX(0) + event.getX(1);
  296. float y = event.getY(0) + event.getY(1);
  297. point.set(x / 2, y / 2);
  298. }
  299. /*
  300. * 获取触摸的X,Y并赋值
  301. */
  302. private void setLastPosition(MotionEvent ev) {
  303. lastX = ev.getX();
  304. lastY = ev.getY();
  305. }
  306. /**
  307. * 按键事件处理,这里你按上下左右键,页面内容是可以上下左右移动的
  308. */
  309. @Override
  310. public boolean dispatchKeyEvent(KeyEvent event) {
  311. if (event.getAction() == KeyEvent.ACTION_DOWN) {
  312. switch (event.getKeyCode()) {
  313. case KeyEvent.KEYCODE_DPAD_RIGHT:
  314. lineByLineMoveTo(1);
  315. return true;
  316. case KeyEvent.KEYCODE_DPAD_LEFT:
  317. lineByLineMoveTo(-1);
  318. return true;
  319. case KeyEvent.KEYCODE_DPAD_DOWN:
  320. verticalDpadScroll(1);
  321. return true;
  322. case KeyEvent.KEYCODE_DPAD_UP:
  323. verticalDpadScroll(-1);
  324. return true;
  325. }
  326. }
  327. return super.dispatchKeyEvent(event);
  328. }
  329. private void verticalDpadScroll(int direction) {
  330. scroller.startScroll(getScrollX(), getScrollY(), 0, direction
  331. * getHeight() / 2);
  332. invalidate();
  333. }
  334. private void lineByLineMoveTo(int direction) {
  335. if (direction == 1 ? getScrollX() == getRightLimit()
  336. : getScrollX() == getLeftLimit()) {
  337. scroller.startScroll(getScrollX(), getScrollY(), direction
  338. * (getLeftLimit() - getRightLimit()), (int) (direction
  339. * pages.get(getCurrentPage()).bounds.height() / 50));
  340. } else {
  341. scroller.startScroll(getScrollX(), getScrollY(), direction
  342. * getWidth() / 2, 0);
  343. }
  344. invalidate();
  345. }
  346. private int getTopLimit() {
  347. return 0;
  348. }
  349. private int getLeftLimit() {
  350. return 0;
  351. }
  352. private int getBottomLimit() {
  353. return (int) pages.get(pages.size() - 1).bounds.bottom - getHeight();
  354. }
  355. private int getRightLimit() {
  356. return (int) (getWidth() * zoomModel.getZoom()) - getWidth();
  357. }
  358. /*
  359. * scrollTo是在移动到这个坐标时显示出的视图
  360. * @see android.view.View#scrollTo(int, int)
  361. */
  362. @Override
  363. public void scrollTo(int x, int y) {
  364. super.scrollTo(Math.min(Math.max(x, getLeftLimit()), getRightLimit()),
  365. Math.min(Math.max(y, getTopLimit()), getBottomLimit())); //获得上下左右的坐标
  366. viewRect = null;
  367. }
  368. /*
  369. * RectF 这个类包含一个矩形的四个单精度浮点坐标。矩形通过上下左右4个边的坐标来表示一个矩形
  370. * RectF(int,a,int b,int c,int d);通过四个坐标,构造一个矩形。
  371. */
  372. RectF getViewRect() {
  373. if (viewRect == null) {
  374. viewRect = new RectF(getScrollX(), getScrollY(), getScrollX()
  375. + getWidth(), getScrollY() + getHeight());
  376. }
  377. return viewRect;
  378. }
  379. /*
  380. * 计算滚动值。
  381. * @see android.view.View#computeScroll()
  382. */
  383. @Override
  384. public void computeScroll() {
  385. if (scroller.computeScrollOffset()) {
  386. scrollTo(scroller.getCurrX(), scroller.getCurrY());
  387. }
  388. }
  389. @Override
  390. protected void onDraw(Canvas canvas) {
  391. super.onDraw(canvas);
  392. for (Page page : pages.values()) {
  393. page.draw(canvas);
  394. }
  395. }
  396. @Override
  397. protected void onLayout(boolean changed, int left, int top, int right,
  398. int bottom) {
  399. super.onLayout(changed, left, top, right, bottom);
  400. float scrollScaleRatio = getScrollScaleRatio();
  401. invalidatePageSizes();
  402. invalidateScroll(scrollScaleRatio);
  403. commitZoom();
  404. }
  405. /**
  406. * 定义每一页的大小
  407. */
  408. void invalidatePageSizes() {
  409. if (!isInitialized) {
  410. return;
  411. }
  412. float heightAccum = 0;
  413. int width = getWidth();
  414. float zoom = zoomModel.getZoom();
  415. for (int i = 0; i < pages.size(); i++) {
  416. Page page = pages.get(i);
  417. float pageHeight = page.getPageHeight(width, zoom);
  418. page.setBounds(new RectF(0, heightAccum, width * zoom, heightAccum
  419. + pageHeight));
  420. heightAccum += pageHeight;
  421. }
  422. }
  423. private void invalidateScroll(float ratio) {
  424. if (!isInitialized) {
  425. return;
  426. }
  427. stopScroller();
  428. final Page page = pages.get(0);
  429. if (page == null || page.bounds == null) {
  430. return;
  431. }
  432. scrollTo((int) (getScrollX() * ratio), (int) (getScrollY() * ratio));
  433. }
  434. private float getScrollScaleRatio() {
  435. final Page page = pages.get(0);
  436. if (page == null || page.bounds == null) {
  437. return 0;
  438. }
  439. final float v = zoomModel.getZoom();
  440. return getWidth() * v / page.bounds.width();
  441. }
  442. //停止动画 Scroller滚动到最终x与y位置时中止动画。
  443. private void stopScroller() {
  444. if (!scroller.isFinished()) {
  445. scroller.abortAnimation(); //中止动画
  446. }
  447. }
  448. }