Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import android.annotation.SuppressLint;
- import android.content.Context;
- import android.graphics.Canvas;
- import android.graphics.Matrix;
- import android.graphics.Rect;
- import android.util.AttributeSet;
- import android.view.MotionEvent;
- import android.view.ScaleGestureDetector;
- import android.view.View;
- import android.view.ViewParent;
- import android.widget.FrameLayout;
- public class ZoomableScrollView extends FrameLayout {
- private static final int INVALID_POINTER_ID = 1;
- private int mActivePointerId = INVALID_POINTER_ID;
- private float mScaleFactor = 1;
- private ScaleGestureDetector mScaleDetector;
- private Matrix mScaleMatrix = new Matrix();
- private Matrix mScaleMatrixInverse = new Matrix();
- private float mPosX;
- private float mPosY;
- private Matrix mTranslateMatrix = new Matrix();
- private Matrix mTranslateMatrixInverse = new Matrix();
- private float mLastTouchX;
- private float mLastTouchY;
- private float mFocusY;
- private float mFocusX;
- private int mCanvasWidth;
- private int mCanvasHeight;
- private float[] mInvalidateWorkingArray = new float[6];
- private float[] mDispatchTouchEventWorkingArray = new float[2];
- private float[] mOnTouchEventWorkingArray = new float[2];
- private boolean mIsScaling;
- public ZoomableScrollView(Context context) {
- super(context);
- mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
- mTranslateMatrix.setTranslate(0, 0);
- mScaleMatrix.setScale(1, 1);
- }
- public ZoomableScrollView(Context context, AttributeSet attributeSet) {
- super(context, attributeSet);
- mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
- mTranslateMatrix.setTranslate(0, 0);
- mScaleMatrix.setScale(1, 1);
- }
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- child.layout(l, t, l+child.getMeasuredWidth(), t += child.getMeasuredHeight());
- }
- }
- }
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- int height = 0;
- int width = 0;
- int childCount = getChildCount();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- if (child.getVisibility() != GONE) {
- measureChild(child, widthMeasureSpec, heightMeasureSpec);
- height += child.getMeasuredHeight();
- width = Math.max(width, child.getMeasuredWidth());
- }
- }
- mCanvasWidth = width;
- mCanvasHeight = height;
- }
- @Override
- protected void dispatchDraw(Canvas canvas) {
- canvas.save();
- canvas.translate(mPosX, mPosY);
- canvas.scale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
- super.dispatchDraw(canvas);
- canvas.restore();
- }
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- mDispatchTouchEventWorkingArray[0] = ev.getX();
- mDispatchTouchEventWorkingArray[1] = ev.getY();
- mDispatchTouchEventWorkingArray = screenPointsToScaledPoints(mDispatchTouchEventWorkingArray);
- ev.setLocation(mDispatchTouchEventWorkingArray[0],
- mDispatchTouchEventWorkingArray[1]);
- return super.dispatchTouchEvent(ev);
- }
- /**
- * Although the docs say that you shouldn't override this, I decided to do
- * so because it offers me an easy way to change the invalidated area to my
- * likening.
- */
- @Override
- public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
- mInvalidateWorkingArray[0] = dirty.left;
- mInvalidateWorkingArray[1] = dirty.top;
- mInvalidateWorkingArray[2] = dirty.right;
- mInvalidateWorkingArray[3] = dirty.bottom;
- mInvalidateWorkingArray = scaledPointsToScreenPoints(mInvalidateWorkingArray);
- dirty.set(Math.round(mInvalidateWorkingArray[0]), Math.round(mInvalidateWorkingArray[1]),
- Math.round(mInvalidateWorkingArray[2]), Math.round(mInvalidateWorkingArray[3]));
- location[0] *= mScaleFactor;
- location[1] *= mScaleFactor;
- return super.invalidateChildInParent(location, dirty);
- }
- private float[] scaledPointsToScreenPoints(float[] a) {
- mScaleMatrix.mapPoints(a);
- mTranslateMatrix.mapPoints(a);
- return a;
- }
- private float[] screenPointsToScaledPoints(float[] a){
- mTranslateMatrixInverse.mapPoints(a);
- mScaleMatrixInverse.mapPoints(a);
- return a;
- }
- @SuppressLint("ClickableViewAccessibility")
- @Override
- public boolean onTouchEvent(MotionEvent ev) {
- mOnTouchEventWorkingArray[0] = ev.getX();
- mOnTouchEventWorkingArray[1] = ev.getY();
- mOnTouchEventWorkingArray = scaledPointsToScreenPoints(mOnTouchEventWorkingArray);
- ev.setLocation(mOnTouchEventWorkingArray[0], mOnTouchEventWorkingArray[1]);
- mScaleDetector.onTouchEvent(ev);
- final int action = ev.getAction();
- switch (action & MotionEvent.ACTION_MASK) {
- case MotionEvent.ACTION_DOWN: {
- final float x = ev.getX();
- final float y = ev.getY();
- mLastTouchX = x;
- mLastTouchY = y;
- // Save the ID of this pointer
- mActivePointerId = ev.getPointerId(0);
- break;
- }
- case MotionEvent.ACTION_MOVE: {
- // Find the index of the active pointer and fetch its position
- final int pointerIndex = ev.findPointerIndex(mActivePointerId);
- final float x = ev.getX(pointerIndex);
- final float y = ev.getY(pointerIndex);
- if (mIsScaling && ev.getPointerCount() == 1) {
- // Don't move during a QuickScale.
- mLastTouchX = x;
- mLastTouchY = y;
- break;
- }
- float dx = x - mLastTouchX;
- float dy = y - mLastTouchY;
- float[] topLeft = {0f, 0f};
- float[] bottomRight = {getWidth(), getHeight()};
- /*
- * Corners of the view in screen coordinates, so dx/dy should not be allowed to
- * push these beyond the canvas bounds.
- */
- float[] scaledTopLeft = screenPointsToScaledPoints(topLeft);
- float[] scaledBottomRight = screenPointsToScaledPoints(bottomRight);
- dx = Math.min(Math.max(dx, scaledBottomRight[0] - mCanvasWidth), scaledTopLeft[0]);
- dy = Math.min(Math.max(dy, scaledBottomRight[1] - mCanvasHeight), scaledTopLeft[1]);
- mPosX += dx;
- mPosY += dy;
- mTranslateMatrix.preTranslate(dx, dy);
- mTranslateMatrix.invert(mTranslateMatrixInverse);
- mLastTouchX = x;
- mLastTouchY = y;
- invalidate();
- break;
- }
- case MotionEvent.ACTION_UP: {
- mActivePointerId = INVALID_POINTER_ID;
- break;
- }
- case MotionEvent.ACTION_CANCEL: {
- mActivePointerId = INVALID_POINTER_ID;
- break;
- }
- case MotionEvent.ACTION_POINTER_UP: {
- // Extract the index of the pointer that left the touch sensor
- final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
- final int pointerId = ev.getPointerId(pointerIndex);
- if (pointerId == mActivePointerId) {
- // This was our active pointer going up. Choose a new
- // active pointer and adjust accordingly.
- final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
- mLastTouchX = ev.getX(newPointerIndex);
- mLastTouchY = ev.getY(newPointerIndex);
- mActivePointerId = ev.getPointerId(newPointerIndex);
- }
- break;
- }
- }
- return true;
- }
- private float getMaxScale() {
- return 2f;
- }
- private float getMinScale() {
- return 1f;
- }
- private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
- @Override
- public boolean onScaleBegin(ScaleGestureDetector detector) {
- mIsScaling = true;
- mFocusX = detector.getFocusX();
- mFocusY = detector.getFocusY();
- float[] foci = {mFocusX, mFocusY};
- float[] scaledFoci = screenPointsToScaledPoints(foci);
- mFocusX = scaledFoci[0];
- mFocusY = scaledFoci[1];
- return true;
- }
- @Override
- public void onScaleEnd(ScaleGestureDetector detector) {
- mIsScaling = false;
- }
- @Override
- public boolean onScale(ScaleGestureDetector detector) {
- mScaleFactor *= detector.getScaleFactor();
- mScaleFactor = Math.max(getMinScale(), Math.min(mScaleFactor, getMaxScale()));
- mScaleMatrix.setScale(mScaleFactor, mScaleFactor, mFocusX, mFocusY);
- mScaleMatrix.invert(mScaleMatrixInverse);
- invalidate();
- return true;
- }
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement