Advertisement
Guest User

Oded's ZoomableScrollView

a guest
Jul 31st, 2018
151
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.71 KB | None | 0 0
  1. import android.annotation.SuppressLint;
  2. import android.content.Context;
  3. import android.graphics.Canvas;
  4. import android.graphics.Matrix;
  5. import android.graphics.Rect;
  6. import android.util.AttributeSet;
  7. import android.view.MotionEvent;
  8. import android.view.ScaleGestureDetector;
  9. import android.view.View;
  10. import android.view.ViewParent;
  11. import android.widget.FrameLayout;
  12.  
  13. public class ZoomableScrollView extends FrameLayout {
  14.  
  15. private static final int INVALID_POINTER_ID = 1;
  16. private int mActivePointerId = INVALID_POINTER_ID;
  17.  
  18. private float mScaleFactor = 1;
  19. private ScaleGestureDetector mScaleDetector;
  20. private Matrix mScaleMatrix = new Matrix();
  21. private Matrix mScaleMatrixInverse = new Matrix();
  22.  
  23. private float mPosX;
  24. private float mPosY;
  25. private Matrix mTranslateMatrix = new Matrix();
  26. private Matrix mTranslateMatrixInverse = new Matrix();
  27.  
  28. private float mLastTouchX;
  29. private float mLastTouchY;
  30.  
  31. private float mFocusY;
  32. private float mFocusX;
  33.  
  34. private int mCanvasWidth;
  35. private int mCanvasHeight;
  36.  
  37. private float[] mInvalidateWorkingArray = new float[6];
  38. private float[] mDispatchTouchEventWorkingArray = new float[2];
  39. private float[] mOnTouchEventWorkingArray = new float[2];
  40.  
  41. private boolean mIsScaling;
  42.  
  43. public ZoomableScrollView(Context context) {
  44. super(context);
  45. mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
  46. mTranslateMatrix.setTranslate(0, 0);
  47. mScaleMatrix.setScale(1, 1);
  48. }
  49.  
  50. public ZoomableScrollView(Context context, AttributeSet attributeSet) {
  51. super(context, attributeSet);
  52. mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
  53. mTranslateMatrix.setTranslate(0, 0);
  54. mScaleMatrix.setScale(1, 1);
  55. }
  56.  
  57. @Override
  58. protected void onLayout(boolean changed, int l, int t, int r, int b) {
  59. int childCount = getChildCount();
  60. for (int i = 0; i < childCount; i++) {
  61. View child = getChildAt(i);
  62. if (child.getVisibility() != GONE) {
  63. child.layout(l, t, l+child.getMeasuredWidth(), t += child.getMeasuredHeight());
  64. }
  65. }
  66. }
  67.  
  68. @Override
  69. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  70. super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  71.  
  72. int height = 0;
  73. int width = 0;
  74. int childCount = getChildCount();
  75. for (int i = 0; i < childCount; i++) {
  76. View child = getChildAt(i);
  77. if (child.getVisibility() != GONE) {
  78. measureChild(child, widthMeasureSpec, heightMeasureSpec);
  79. height += child.getMeasuredHeight();
  80. width = Math.max(width, child.getMeasuredWidth());
  81. }
  82. }
  83. mCanvasWidth = width;
  84. mCanvasHeight = height;
  85. }
  86.  
  87. @Override
  88. protected void dispatchDraw(Canvas canvas) {
  89. canvas.save();
  90. canvas.translate(mPosX, mPosY);
  91. canvas.scale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
  92. super.dispatchDraw(canvas);
  93. canvas.restore();
  94. }
  95.  
  96. @Override
  97. public boolean dispatchTouchEvent(MotionEvent ev) {
  98. mDispatchTouchEventWorkingArray[0] = ev.getX();
  99. mDispatchTouchEventWorkingArray[1] = ev.getY();
  100. mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
  101. ev.setLocation(mDispatchTouchEventWorkingArray[0],
  102. mDispatchTouchEventWorkingArray[1]);
  103. return super.dispatchTouchEvent(ev);
  104. }
  105.  
  106. /**
  107. * Although the docs say that you shouldn't override this, I decided to do
  108. * so because it offers me an easy way to change the invalidated area to my
  109. * likening.
  110. */
  111. @Override
  112. public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
  113.  
  114. mInvalidateWorkingArray[0] = dirty.left;
  115. mInvalidateWorkingArray[1] = dirty.top;
  116. mInvalidateWorkingArray[2] = dirty.right;
  117. mInvalidateWorkingArray[3] = dirty.bottom;
  118.  
  119. mInvalidateWorkingArray = scaledPointsToScreenPoints(mInvalidateWorkingArray);
  120. dirty.set(Math.round(mInvalidateWorkingArray[0]), Math.round(mInvalidateWorkingArray[1]),
  121. Math.round(mInvalidateWorkingArray[2]), Math.round(mInvalidateWorkingArray[3]));
  122.  
  123. location[0] *= mScaleFactor;
  124. location[1] *= mScaleFactor;
  125. return super.invalidateChildInParent(location, dirty);
  126. }
  127.  
  128. private float[] scaledPointsToScreenPoints(float[] a) {
  129. mScaleMatrix.mapPoints(a);
  130. mTranslateMatrix.mapPoints(a);
  131. return a;
  132. }
  133.  
  134. private float[] screenPointsToScaledPoints(float[] a){
  135. mTranslateMatrixInverse.mapPoints(a);
  136. mScaleMatrixInverse.mapPoints(a);
  137. return a;
  138. }
  139.  
  140. @SuppressLint("ClickableViewAccessibility")
  141. @Override
  142. public boolean onTouchEvent(MotionEvent ev) {
  143. mOnTouchEventWorkingArray[0] = ev.getX();
  144. mOnTouchEventWorkingArray[1] = ev.getY();
  145.  
  146. mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);
  147.  
  148. ev.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);
  149. mScaleDetector.onTouchEvent(ev);
  150.  
  151. final int action = ev.getAction();
  152. switch (action & MotionEvent.ACTION_MASK) {
  153. case MotionEvent.ACTION_DOWN: {
  154. final float x = ev.getX();
  155. final float y = ev.getY();
  156.  
  157. mLastTouchX = x;
  158. mLastTouchY = y;
  159.  
  160. // Save the ID of this pointer
  161. mActivePointerId = ev.getPointerId(0);
  162. break;
  163. }
  164.  
  165. case MotionEvent.ACTION_MOVE: {
  166. // Find the index of the active pointer and fetch its position
  167. final int pointerIndex = ev.findPointerIndex(mActivePointerId);
  168. final float x = ev.getX(pointerIndex);
  169. final float y = ev.getY(pointerIndex);
  170.  
  171. if (mIsScaling && ev.getPointerCount() == 1) {
  172. // Don't move during a QuickScale.
  173. mLastTouchX = x;
  174. mLastTouchY = y;
  175.  
  176. break;
  177. }
  178.  
  179. float dx = x - mLastTouchX;
  180. float dy = y - mLastTouchY;
  181.  
  182. float[] topLeft = {0f, 0f};
  183. float[] bottomRight = {getWidth(), getHeight()};
  184. /*
  185. * Corners of the view in screen coordinates, so dx/dy should not be allowed to
  186. * push these beyond the canvas bounds.
  187. */
  188. float[] scaledTopLeft = screenPointsToScaledPoints(topLeft);
  189. float[] scaledBottomRight = screenPointsToScaledPoints(bottomRight);
  190.  
  191. dx = Math.min(Math.max(dx, scaledBottomRight[0] - mCanvasWidth), scaledTopLeft[0]);
  192. dy = Math.min(Math.max(dy, scaledBottomRight[1] - mCanvasHeight), scaledTopLeft[1]);
  193.  
  194. mPosX += dx;
  195. mPosY += dy;
  196.  
  197. mTranslateMatrix.preTranslate(dx, dy);
  198. mTranslateMatrix.invert(mTranslateMatrixInverse);
  199.  
  200. mLastTouchX = x;
  201. mLastTouchY = y;
  202.  
  203. invalidate();
  204. break;
  205. }
  206.  
  207. case MotionEvent.ACTION_UP: {
  208. mActivePointerId = INVALID_POINTER_ID;
  209. break;
  210. }
  211.  
  212. case MotionEvent.ACTION_CANCEL: {
  213. mActivePointerId = INVALID_POINTER_ID;
  214. break;
  215. }
  216.  
  217. case MotionEvent.ACTION_POINTER_UP: {
  218. // Extract the index of the pointer that left the touch sensor
  219. final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
  220. final int pointerId = ev.getPointerId(pointerIndex);
  221. if (pointerId == mActivePointerId) {
  222. // This was our active pointer going up. Choose a new
  223. // active pointer and adjust accordingly.
  224. final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
  225. mLastTouchX = ev.getX(newPointerIndex);
  226. mLastTouchY = ev.getY(newPointerIndex);
  227. mActivePointerId = ev.getPointerId(newPointerIndex);
  228. }
  229. break;
  230. }
  231. }
  232. return true;
  233. }
  234.  
  235. private float getMaxScale() {
  236. return 2f;
  237. }
  238.  
  239. private float getMinScale() {
  240. return 1f;
  241. }
  242.  
  243. private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
  244. @Override
  245. public boolean onScaleBegin(ScaleGestureDetector detector) {
  246. mIsScaling = true;
  247.  
  248. mFocusX = detector.getFocusX();
  249. mFocusY = detector.getFocusY();
  250.  
  251. float[] foci = {mFocusX, mFocusY};
  252. float[] scaledFoci = screenPointsToScaledPoints(foci);
  253.  
  254. mFocusX = scaledFoci[0];
  255. mFocusY = scaledFoci[1];
  256.  
  257. return true;
  258. }
  259.  
  260. @Override
  261. public void onScaleEnd(ScaleGestureDetector detector) {
  262. mIsScaling = false;
  263. }
  264.  
  265. @Override
  266. public boolean onScale(ScaleGestureDetector detector) {
  267. mScaleFactor *= detector.getScaleFactor();
  268. mScaleFactor = Math.max(getMinScale(), Math.min(mScaleFactor, getMaxScale()));
  269. mScaleMatrix.setScale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
  270. mScaleMatrix.invert(mScaleMatrixInverse);
  271. invalidate();
  272.  
  273. return true;
  274. }
  275. }
  276.  
  277. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement