java - 捏合缩放如何与 Android 中的图像平移一起使用

标签 java android matrix zooming


目标

创建了一个查看图像的 Activity ,我们可以缩放或平移图像。图像一开始位于屏幕中央。双指缩放以图像的中心为中心,即使在图像被平移到屏幕的其他位置之后也是如此。

用于显示的图像是从给定的 URL 下载的,并且该 URL 是从额外的 intent 传递来启动图像查看 Activity 的。

双指缩放由postScale()实现,平移由postTranslate()实现。


问题

将图像平移到某处后,双指缩放中心仍位于屏幕中央。当它被移动到一个新的地方时,试图跟随图像的中心,但我的代码不能那样工作。请给出一些想法。

图像下载和平移效果很好。


代码

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 {}

最佳答案

您可以使用缩放手势检测器进行捏合缩放。您可以执行以下操作,而不是从头开始缩放,

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;
    }
}
}

注意:您的翻译将驻留在 onDraw 方法中以缩放图像。

关于java - 捏合缩放如何与 Android 中的图像平移一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18264660/

相关文章:

java - 获取 InterruptedException 以忽略中断

java - 测试 java jersey 应用程序

java - 完全删除AndEngine中的场景

java - 如何返回 DocumentSnapShot 作为方法的结果?

c# - 在 XNA、C# 中使用矩阵时对操作顺序感到困惑

java - 如何通过单击另一个 Activity 中的按钮来更改一个 Activity 中 TextView 的颜色?

java - SAML LogoutRequest 到 xml 表示

android - ASN.1 编码例程 :OPENSSL_internal:HEADER_TOO_LONG

algorithm - 如何在矩阵中找到一个单词,其中每个字符都在唯一的行上

matlab - 随机排列矩阵行,保持相同值的行相邻