简体   繁体   English

如何在Android中使用平移图像进行缩放缩放

[英]How does pinch zoom work with panning for image in Android


Goal 目标

An activity is made to view image, we can pinch zoom or pan the image. 进行活动以查看图像,我们可以捏缩放或平移图像。 The image is centered in the screen in the beginning. 图像在开头的中心位于屏幕中。 Pinch zoom is centered at the center of the image, even after the image is panned somewhere else in the screen. 即使图像在屏幕中的其他位置平移后,捏合缩放也以图像的中心为中心。

The image for displaying is downloaded from a given URL, and the URL is passed from extra of an intent to start the image viewing activity. 从给定的URL下载用于显示的图像,并且从额外的意图传递URL以开始图像查看活动。

Pinch zoom is implemented by postScale() , pan by postTranslate() . 缩放由postScale() ,通过postTranslate()


Problem 问题

After panning the image somewhere, the pinch-zoom center is still at the center of the screen. 在某处平移图像后,捏缩放中心仍然位于屏幕的中心。 Tried to follow the center of the image when it's been moved to a new place but my code doesn't work that way. 当它被移动到一个新的地方时,试图跟随图像的中心,但我的代码不能那样工作。 Please give some idea. 请提出一些想法。

The image downloading and panning work well. 图像下载和平移工作良好。


Code

activity_image_viewer_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" >

    <LinearLayout
        android:orientation="vertical" 
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:gravity="center"
        android:background="@color/MyPureBlack" >

        <LinearLayout
          android:id="@+id/progressbar_wrapper"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <ProgressBar 
                android:id="@+id/progressbar"
                style="?android:attr/progressBarStyleHorizontal" 
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:max="100"
                android:progress="0"
                android:layout_marginLeft="20dp"
                android:layout_marginRight="20dp"
                android:layout_gravity="center" >
            </ProgressBar>
        </LinearLayout>

        <ImageView
            android:id="@+id/image_viewer" 
            android:visibility="gone"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent" 
            android:background="@color/MyPureBlack"
            android:scaleType="matrix" >
        </ImageView>
    </LinearLayout>
</FrameLayout>

ActivityImageViewer.java

package com.com2us.hubapp.android;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;

import org.apache.http.util.ByteArrayBuffer;

import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.FloatMath;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.AlphaAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;

public class ActivityImageViewer extends Activity {
    File imageFile = null;

    // Matrices for pinch zoom and pan
    Matrix matrix = new Matrix();
    Matrix savedMatrix = new Matrix();
    Matrix savedMatrixZoom = new Matrix();

    // State of motion event
    static final int NONE = 0;
    static final int PAN = 1;
    static final int PINCH_ZOOM = 2;
    int mode = NONE;

    // The first pointer down
    PointF start = new PointF();

    // The center of the image (Failed to track it when the image has been moved)
    PointF centerOfImage = new PointF();

    // oldest is the Cartesian distance between first two pointers when the second pointer is down
    float oldDist = 1f;

    // MIN_SCALE/MAX_SCALE is the min/max scale factor
    private final float MIN_SCALE = 0.5f;
    private final float MAX_SCALE = 3.0f;

    // TOUCH_SENSITIVE is the minimum Cartesian distance between the first two pointers that triggers the pinch zoom
    private final float TOUCH_SENSITIVE = 10.0f;
    private final float SPACING_LEFT_AND_RIGHT = 30.0f;
    private final float SPACING_TOP_AND_BOTTOM = 30.0f;

    // The ImageView widget
    private ImageView image_viewer;

    // The progress bar shows what current progress is before the image downloading is completed
    private ProgressBar progressbar;
    private LinearLayout progressbar_wrapper;

    // An async task that downloads the image from a given URL
    private DownloadFilesTask downloadFilesTask;

    private class DownloadFilesTask extends AsyncTask<String, Integer, Bitmap> {
        protected Bitmap doInBackground(String... urls) {
            InputStream input = null;
            OutputStream output = null;
            try {
                URL url = new URL(urls[0]);
                URLConnection connection = url.openConnection();
                connection.connect();
                int lenghtOfFile = connection.getContentLength();
                // download the file
                InputStream is = connection.getInputStream();
                BufferedInputStream bis = new BufferedInputStream(is, 8190);

                ByteArrayBuffer baf = new ByteArrayBuffer(50);
                int current = 0;
                while ((current = bis.read()) != -1) {
                    baf.append((byte)current);
                }
                byte[] imageData = baf.toByteArray();
                Bitmap bmp = BitmapFactory.decodeByteArray(imageData, 0, imageData.length);

                //final int percent = (int) (total * 100 / lenghtOfFile);
                //publishProgress(percent);
                //lenghtOfFile

                return bmp;
            } catch (Exception e) {

            } finally {
                try {
                    if (output != null)
                        output.close();
                    output = null;
                } catch (IOException e) {

                }
                try {
                    if (input != null)
                        input.close();
                    input = null;
                } catch (IOException e) {

                }

            }
            return null;
        } // protected Bitmap doInBackground(String... urls) {}

        protected void onProgressUpdate(Integer... progress) {
            progressbar.setProgress(progress[0]);
        }

        protected void onPostExecute(Bitmap bmp) {
            if (bmp != null) {
                final AlphaAnimation animationAfter = new AlphaAnimation(0.0f, 1.0f);
                animationAfter.setDuration(300);
                animationAfter.setFillEnabled(true);
                animationAfter.setFillAfter(true);
                image_viewer.setAnimation(animationAfter);
                image_viewer.setImageBitmap(bmp);
                ViewTreeObserver viewTreeObserver = image_viewer.getViewTreeObserver();
                viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        Drawable drawable = image_viewer.getDrawable();
                        int dx = (image_viewer.getWidth() - drawable.getIntrinsicWidth()) / 2;
                        int dy = (image_viewer.getHeight() - drawable.getIntrinsicHeight()) / 2;
                        matrix.postTranslate(dx, dy);
                        image_viewer.setImageMatrix(matrix);
                    }
                });
                progressbar_wrapper.setVisibility(View.GONE);
                image_viewer.setVisibility(View.VISIBLE);
            } else {
                android.os.Handler handler = new android.os.Handler();
                handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        finish();
                    }
                }, 2000);
            }
        } // End of protected void onPostExecute(Bitmap bmp) {}
    } // End of private class DownloadFilesTask extends AsyncTask<String, Integer, Bitmap> {}

    // These are activity life cycle handling
    // onCreate
    @Override
    public void onCreate(Bundle savedInstanceState) {
        //setTheme(R.style.HubTheme);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_image_viewer);
        progressbar_wrapper = (LinearLayout) findViewById(R.id.progressbar_wrapper);
        image_viewer = (ImageView) findViewById(R.id.image_viewer);
        progressbar = (ProgressBar) findViewById(R.id.progressbar);
        image_viewer.setOnTouchListener(new MyOnTouchListener());
        final String uriForImage = getIntent().getStringExtra("url");
        downloadFilesTask = new DownloadFilesTask();
        downloadFilesTask.execute(uriForImage);
    }

    // onStart
    @Override
    protected void onStart() {
        super.onStart();
    }

    // onResume
    @Override
    protected void onResume() {
        super.onResume();
    }

    // onPause
    @Override
    protected void onPause() {
        super.onPause();
    }

    // onStop
    @Override
    protected void onStop() {
        super.onStop();
    }

    // onRestart
    @Override
    protected void onRestart() {
        super.onRestart();
    }

    // onDestroy
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (imageFile != null) {
            try {
                Drawable drawable = image_viewer.getDrawable();
                BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
                Bitmap bitmap = bitmapDrawable.getBitmap();
                bitmap.recycle();

                drawable = null;
                bitmapDrawable = null;
                bitmap = null;

            } catch (NullPointerException e) {
            }
        }
    }

    // onKeyDown
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            this.onBackPressed();
        }
        return true;
    }

    // onBackPressed
    public void onBackPressed() {
        finish();
    }

    // onConfigurationChanged
    @Override
    public void onConfigurationChanged(Configuration newConfig) {

        super.onConfigurationChanged(newConfig);
        if (newConfig.equals(Configuration.ORIENTATION_LANDSCAPE)) {

        } else if (newConfig.equals(Configuration.ORIENTATION_PORTRAIT)) {

        }
    }

    // onLowMemory
    @Override
    public void onLowMemory() {
        super.onLowMemory();
        finish();
    }

    // Get the Cartesian distance between the first two pointers
    private float spacing(MotionEvent event) {
        float x = 0;
        float y = 0;
        try {
            Method getX = MotionEvent.class.getMethod("getX", Integer.TYPE);
            Method getY = MotionEvent.class.getMethod("getX", Integer.TYPE);

            // x = event.getX(0) - event.getX(1);
            // y = event.getY(0) - event.getY(1);
            float x1 = (Float) getX.invoke(event, 0);
            float x2 = (Float) getX.invoke(event, 1);
            x = x1 - x2;
            float y1 = (Float) getY.invoke(event, 0);
            float y2 = (Float) getY.invoke(event, 1);
            y = y1 - y2;

        } catch (SecurityException e) {
        } catch (NoSuchMethodException e) {
        } catch (IllegalArgumentException e) {
        } catch (IllegalAccessException e) {
        } catch (InvocationTargetException e) {
        }
        return FloatMath.sqrt(x * x + y * y);
    }

    // Some flags set manually for convenience
    private final int MotionEvent_ACTION_MASK         = 255; // that is 0xFF or 11111111
    private final int MotionEvent_ACTION_POINTER_DOWN = 5;   // that is 101
    private final int MotionEvent_ACTION_POINTER_UP   = 6;   // that is 110

    private class MyOnTouchListener implements OnTouchListener {

        // onTouch
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            ImageView view = (ImageView) v;
            Drawable drawable = view.getDrawable();
            if (drawable == null)
                return true;
            switch (event.getAction() & MotionEvent_ACTION_MASK) {
            case MotionEvent.ACTION_DOWN:
                savedMatrix.set(matrix);
                start.set(event.getX(), event.getY());
                mode = PAN;
                break;
            case MotionEvent_ACTION_POINTER_DOWN:
                oldDist = spacing(event);
                if (oldDist > TOUCH_SENSITIVE) {
                    savedMatrix.set(matrix);
                    mode = PINCH_ZOOM;
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent_ACTION_POINTER_UP:
                mode = NONE;
                break;
            case MotionEvent.ACTION_MOVE:
                if (mode == PAN) {
                    // /////////////////////////////////////////
                    matrix.set(savedMatrix);
                    float[] matrixValues = new float[9];
                    Rect viewRect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
                    matrix.getValues(matrixValues);
                    float currentY = matrixValues[Matrix.MTRANS_Y];
                    float currentX = matrixValues[Matrix.MTRANS_X];
                    float currentScale = matrixValues[Matrix.MSCALE_X];
                    float currentHeight = drawable.getIntrinsicHeight() * currentScale;
                    float currentWidth = drawable.getIntrinsicWidth() * currentScale;
                    float dx = event.getX() - start.x;
                    float dy = event.getY() - start.y;
                    float newX = currentX + dx;
                    float newY = currentY + dy;

                    RectF drawingRect = new RectF(newX, newY, newX + currentWidth, newY + currentHeight);
                    float diffUp = Math.min(viewRect.bottom - drawingRect.bottom, viewRect.top - drawingRect.top) - SPACING_TOP_AND_BOTTOM;
                    float diffDown = Math.max(viewRect.bottom - drawingRect.bottom, viewRect.top - drawingRect.top) + SPACING_TOP_AND_BOTTOM;
                    float diffLeft = Math.min(viewRect.left - drawingRect.left, viewRect.right - drawingRect.right) - SPACING_LEFT_AND_RIGHT;
                    float diffRight = Math.max(viewRect.left - drawingRect.left, viewRect.right - drawingRect.right) + SPACING_LEFT_AND_RIGHT;
                    if (diffUp > 0) {
                        dy += diffUp;
                    }
                    if (diffDown < 0) {
                        dy += diffDown;
                    }
                    if (diffLeft > 0) {
                        dx += diffLeft;
                    }
                    if (diffRight < 0) {
                        dx += diffRight;
                    }
                    matrix.postTranslate(dx, dy);
                } else if (mode == PINCH_ZOOM) {
                    float newDist = spacing(event);
                    if (newDist > TOUCH_SENSITIVE) {
                        matrix.set(savedMatrix);
                        float scale = newDist / oldDist;

                        // Get the center of the image. (Failed to get it when image has been moved)
                        Rect viewRect = new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
                        centerOfImage.x = viewRect.centerX();
                        centerOfImage.y = viewRect.centerY();

                        float[] f = new float[9];
                        Matrix tmp = new Matrix(matrix);
                        tmp.postScale(scale, scale, centerOfImage.x, centerOfImage.y);
                        tmp.getValues(f);
                        float scaleX = f[Matrix.MSCALE_X];
                        if (scaleX < MIN_SCALE || scaleX > MAX_SCALE) {
                            matrix.set(savedMatrixZoom);
                        } else {
                            matrix.postScale(scale, scale, centerOfImage.x, centerOfImage.y);
                            savedMatrixZoom.set(matrix);
                        }


                    }
                }
                break;
            }
            view.setImageMatrix(matrix);
            return true;
        } // End of public boolean onTouch(View v, MotionEvent event) {}

    } // End of private class MyOnTouchListener implements OnTouchListener {}

} // End of public class ActivityImageViewer extends Activity {}

You can use the Scale Gesture Detector for pinch to zoom. 您可以使用“缩放手势检测器”进行缩放以进行缩放。 Instead of creating pinch to zoom from scratch you can do something like following, 而不是创建从头开始缩放,你可以做以下事情,

public class MyCustomView extends View {

private ScaleGestureDetector mScaleDetector;
private float mScaleFactor = 1.f;

public MyCustomView(Context mContext){
    ...
    // View code goes here
    ...
    mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
}

@Override
public boolean onTouchEvent(MotionEvent ev) {
    // Let the ScaleGestureDetector inspect all events.
    mScaleDetector.onTouchEvent(ev);
    return true;
}

@Override
public void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    canvas.save();
    canvas.scale(mScaleFactor, mScaleFactor);
    ...
    // onDraw() code goes here
    ...
    canvas.restore();
}

private class ScaleListener 
        extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        mScaleFactor *= detector.getScaleFactor();

        // Don't let the object get too small or too large.
        mScaleFactor = Math.max(0.1f, Math.min(mScaleFactor, 5.0f));

        invalidate();
        return true;
    }
}
}

Note : Your translation will reside in onDraw method to scale an image. 注意:您的翻译将驻留在onDraw方法中以缩放图像。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM