Android ImageView 缩放和翻译问题

Posted

技术标签:

【中文标题】Android ImageView 缩放和翻译问题【英文标题】:Android ImageView Scaling and translating issue 【发布时间】:2014-03-05 04:58:07 【问题描述】:

我正在开发一个 android 应用程序 (API 19 4.4),但我遇到了 ImageViews 的一些问题。 我有一个 SurfaceView,我在其中动态添加了我想对触摸事件做出反应的 ImageView。 到目前为止,我已经设法使 ImageView 平滑地移动和缩放,但我有一个烦人的行为。

当我将图像缩小到某个限制(我会说是原始大小的一半)并尝试移动它时,图像会闪烁。 经过短暂的分析,它似乎在屏幕上围绕手指点对称地切换位置,累积距离,最后消失(所有这一切都发生得非常快(

这是我的代码

public class MyImageView extends ImageView 
private ScaleGestureDetector mScaleDetector ;
private static final int MAX_SIZE = 1024;

private static final String TAG = "MyImageView";
PointF DownPT = new PointF(); // Record Mouse Position When Pressed Down
PointF StartPT = new PointF(); // Record Start Position of 'img'

public MyImageView(Context context) 
    super(context);
    mScaleDetector = new ScaleGestureDetector(context,new MySimpleOnScaleGestureListener());
    setBackgroundColor(Color.RED);
    setScaleType(ScaleType.MATRIX);
    setAdjustViewBounds(true);
    RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);

    lp.setMargins(-MAX_SIZE, -MAX_SIZE, -MAX_SIZE, -MAX_SIZE);
    this.setLayoutParams(lp);
    this.setX(MAX_SIZE);
    this.setY(MAX_SIZE);



int firstPointerID;
boolean inScaling=false;
@Override
public boolean onTouchEvent(MotionEvent event) 
    // get pointer index from the event object
    int pointerIndex = event.getActionIndex();
    // get pointer ID
    int pointerId = event.getPointerId(pointerIndex);
    //First send event to scale detector to find out, if it's a scale
    boolean res = mScaleDetector.onTouchEvent(event);

    if (!mScaleDetector.isInProgress()) 
        int eid = event.getAction();
        switch (eid & MotionEvent.ACTION_MASK)
        
        case MotionEvent.ACTION_MOVE :
            if(pointerId == firstPointerID) 

                PointF mv = new PointF( (int)(event.getX() - DownPT.x), (int)( event.getY() - DownPT.y));

                this.setX((int)(StartPT.x+mv.x));
                this.setY((int)(StartPT.y+mv.y));
                StartPT = new PointF( this.getX(), this.getY() );

            
            break;
        case MotionEvent.ACTION_DOWN : 
            firstPointerID = pointerId;
            DownPT.x = (int) event.getX();
            DownPT.y = (int) event.getY();
            StartPT = new PointF( this.getX(), this.getY() );
            break;
        
        case MotionEvent.ACTION_POINTER_DOWN: 
            break;
        
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_POINTER_UP:
        case MotionEvent.ACTION_CANCEL: 
            firstPointerID = -1;
            break;
        
        default :
            break;
        
        return true;
    
    return true;



public boolean onScaling(ScaleGestureDetector detector) 

    this.setScaleX(this.getScaleX()*detector.getScaleFactor());
    this.setScaleY(this.getScaleY()*detector.getScaleFactor());
    invalidate();
    return true;


private class MySimpleOnScaleGestureListener extends SimpleOnScaleGestureListener 


    @Override
    public boolean onScale(ScaleGestureDetector detector) 
        return onScaling(detector);
    

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) 
        Log.d(TAG, "onScaleBegin");
        return true;
    

    @Override
    public void onScaleEnd(ScaleGestureDetector arg0) 
        Log.d(TAG, "onScaleEnd");
    

我还有另一个关于轮换的问题。我应该如何实现这个? 我可以以某种方式使用 ScalegestureDetector 还是让我在视图触摸事件中使其工作?我希望能够以相同的手势缩放和旋转(并以另一个手势移动)。

感谢您的帮助,我将不胜感激!

对不起我的英语

【问题讨论】:

为什么在 SurfaceView 中使用 ImageView 而不是直接绘制 Bitmap? 如果我直接绘制位图,如何用手指移动、缩放、旋转? (我可以在我的表面视图中有十几个不同的视图,我想用手势单独管理) 使用矩阵(canvas.drawBitmap(Bitmap, Matrix, Paint)) 我的意思是:如何检测我的手指在哪个位图上进行交互? 看看我在我的一个项目github.com/pskink/PatchworkDrawable/blob/master/…中的getLayersAt()方法中是如何做到的 【参考方案1】:

这是两个手指移动/缩放/旋转的工作示例(注意:由于使用了智能检测器,代码很短 - 请参阅 MatrixGestureDetector):

class ViewPort extends View 
    List<Layer> layers = new LinkedList<Layer>();
    int[] ids = R.drawable.layer0, R.drawable.layer1, R.drawable.layer2;

    public ViewPort(Context context) 
        super(context);
        Resources res = getResources();
        for (int i = 0; i < ids.length; i++) 
            Layer l = new Layer(context, this, BitmapFactory.decodeResource(res, ids[i]));
            layers.add(l);
        
    

    @Override
    protected void onDraw(Canvas canvas) 
        for (Layer l : layers) 
            l.draw(canvas);
        
    

    private Layer target;

    @Override
    public boolean onTouchEvent(MotionEvent event) 
        if (event.getAction() == MotionEvent.ACTION_DOWN) 
            target = null;
            for (int i = layers.size() - 1; i >= 0; i--) 
                Layer l = layers.get(i);
                if (l.contains(event)) 
                    target = l;
                    layers.remove(l);
                    layers.add(l);
                    invalidate();
                    break;
                
            
        
        if (target == null) 
            return false;
        
        return target.onTouchEvent(event);
    


class Layer implements MatrixGestureDetector.OnMatrixChangeListener 
    Matrix matrix = new Matrix();
    Matrix inverse = new Matrix();
    RectF bounds;
    View parent;
    Bitmap bitmap;
    MatrixGestureDetector mgd = new MatrixGestureDetector(matrix, this);

    public Layer(Context ctx, View p, Bitmap b) 
        parent = p;
        bitmap = b;
        bounds = new RectF(0, 0, b.getWidth(), b.getHeight());
        matrix.postTranslate(50 + (float) Math.random() * 50, 50 + (float) Math.random() * 50);
    

    public boolean contains(MotionEvent event) 
        matrix.invert(inverse);
        float[] pts = event.getX(), event.getY();
        inverse.mapPoints(pts);
        if (!bounds.contains(pts[0], pts[1])) 
            return false;
        
        return Color.alpha(bitmap.getPixel((int) pts[0], (int) pts[1])) != 0;
    

    public boolean onTouchEvent(MotionEvent event) 
        mgd.onTouchEvent(event);
        return true;
    

    @Override
    public void onChange(Matrix matrix) 
        parent.invalidate();
    

    public void draw(Canvas canvas) 
        canvas.drawBitmap(bitmap, matrix, null);
    


class MatrixGestureDetector 
    private static final String TAG = "MatrixGestureDetector";

    private int ptpIdx = 0;
    private Matrix mTempMatrix = new Matrix();
    private Matrix mMatrix;
    private OnMatrixChangeListener mListener;
    private float[] mSrc = new float[4];
    private float[] mDst = new float[4];
    private int mCount;

    interface OnMatrixChangeListener 
        void onChange(Matrix matrix);
    

    public MatrixGestureDetector(Matrix matrix, MatrixGestureDetector.OnMatrixChangeListener listener) 
        this.mMatrix = matrix;
        this.mListener = listener;
    

    public void onTouchEvent(MotionEvent event) 
        if (event.getPointerCount() > 2) 
            return;
        

        int action = event.getActionMasked();
        int index = event.getActionIndex();

        switch (action) 
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_POINTER_DOWN:
                int idx = index * 2;
                mSrc[idx] = event.getX(index);
                mSrc[idx + 1] = event.getY(index);
                mCount++;
                ptpIdx = 0;
                break;

            case MotionEvent.ACTION_MOVE:
                for (int i = 0; i < mCount; i++) 
                    idx = ptpIdx + i * 2;
                    mDst[idx] = event.getX(i);
                    mDst[idx + 1] = event.getY(i);
                
                mTempMatrix.setPolyToPoly(mSrc, ptpIdx, mDst, ptpIdx, mCount);
                mMatrix.postConcat(mTempMatrix);
                if(mListener != null) 
                    mListener.onChange(mMatrix);
                
                System.arraycopy(mDst, 0, mSrc, 0, mDst.length);
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_POINTER_UP:
                if (event.getPointerId(index) == 0) ptpIdx = 2;
                mCount--;
                break;
        
    

【讨论】:

您好,抱歉耽搁了。感谢你的宝贵时间。我通过不使用“setscale”而是设置 layoutparam 来解决我的问题。 Universalimageloader 加载器可以很好地处理位图。 这段代码是杰作 :) 干得好,感谢分享 :) 希望我能投票一百万次。几个星期以来一直在寻找一个简单的解决方案...... 我向 ViewPort 添加了一个方法,允许用户从 MainActivity 添加图像。不幸的是,当我在我的方法中调用 invalidate() 时,不会调用 onDraw 并且不会出现图像: public void addLayer(Integer id) Resources res = getResources(); ids.add(id);层 l = new Layer(mContext, this, BitmapFactory.decodeResource(res, id));层.add(l);无效(); @LukeWaggoner 现在解决方案更加简单 - 请参阅 MatrixGestureDetector 课程 - 我在半小时内完成了它,所以它仍然可能有一些错误...... ;-(【参考方案2】:

我尝试使用矩阵在视图上而不是位图上实现多点触摸,现在我成功了。现在我认为这对您对多个图像的个人手势有帮助。试试吧,它最适合我。

public class MultiTouchImageView extends ImageView implements OnTouchListener

float[] lastEvent = null;
float d = 0f;
float newRot = 0f;
public static String fileNAME;
public static int framePos = 0;
//private ImageView view;
private boolean isZoomAndRotate;
private boolean isOutSide;
// We can be in one of these 3 states
private static final int NONE = 0;
private static final int DRAG = 1;
private static final int ZOOM = 2;
private int mode = NONE;

private PointF start = new PointF();
private PointF mid = new PointF();
float oldDist = 1f;
public MultiTouchImageView(Context context) 
    super(context);



public MultiTouchImageView(Context context, AttributeSet attrs, int defStyle) 
    super(context, attrs, defStyle);



public MultiTouchImageView(Context context, AttributeSet attrs) 
    super(context, attrs);



@SuppressWarnings("deprecation")
@Override
public boolean onTouch(View v, MotionEvent event) 
    //view = (ImageView) v;
    bringToFront();
    // Handle touch events here...
    switch (event.getAction() & MotionEvent.ACTION_MASK) 
    case MotionEvent.ACTION_DOWN:
        //savedMatrix.set(matrix);
        start.set(event.getX(), event.getY());
        mode = DRAG;
        lastEvent = null;
        break;
    case MotionEvent.ACTION_POINTER_DOWN:
        oldDist = spacing(event);
        if (oldDist > 10f) 
            midPoint(mid, event);
            mode = ZOOM;
        

        lastEvent = new float[4];
        lastEvent[0] = event.getX(0);
        lastEvent[1] = event.getX(1);
        lastEvent[2] = event.getY(0);
        lastEvent[3] = event.getY(1);
        d =  rotation(event);
        break;
    case MotionEvent.ACTION_UP:
        isZoomAndRotate = false;
    case MotionEvent.ACTION_OUTSIDE:
        isOutSide = true;
        mode = NONE;
        lastEvent = null;
    case MotionEvent.ACTION_POINTER_UP:
        mode = NONE;
        lastEvent = null;
        break;
    case MotionEvent.ACTION_MOVE:
        if(!isOutSide)
            if (mode == DRAG && !isZoomAndRotate) 
                isZoomAndRotate = false;
                setTranslationX((event.getX() - start.x) + getTranslationX());
                setTranslationY((event.getY() - start.y) + getTranslationY());
             else if (mode == ZOOM && event.getPointerCount() == 2) 
                isZoomAndRotate = true;
                boolean isZoom = false;
                if(!isRotate(event))
                    float newDist = spacing(event);
                    if (newDist > 10f) 
                        float scale = newDist / oldDist * getScaleX();
                        setScaleX(scale);
                        setScaleY(scale);
                        isZoom = true;
                    
                
                else if(!isZoom)
                    newRot = rotation(event);
                    setRotation((float)(getRotation() + (newRot - d)));
                
            
        

        break;
    
    new GestureDetector(new MyGestureDectore());
    Constants.currentSticker = this;
    return true;

private class MyGestureDectore extends GestureDetector.SimpleOnGestureListener

    @Override
    public boolean onDoubleTap(MotionEvent e) 
        bringToFront();
        return false;
    

    @Override
    public boolean onDoubleTapEvent(MotionEvent e) 
        return false;
    


private float rotation(MotionEvent event) 
    double delta_x = (event.getX(0) - event.getX(1));
    double delta_y = (event.getY(0) - event.getY(1));
    double radians = Math.atan2(delta_y, delta_x);
    return (float) Math.toDegrees(radians);

private float spacing(MotionEvent event) 
    float x = event.getX(0) - event.getX(1);
    float y = event.getY(0) - event.getY(1);
    return FloatMath.sqrt(x * x + y * y);


private void midPoint(PointF point, MotionEvent event) 
    float x = event.getX(0) + event.getX(1);
    float y = event.getY(0) + event.getY(1);
    point.set(x / 2, y / 2);


private boolean isRotate(MotionEvent event)
    int dx1 = (int) (event.getX(0) - lastEvent[0]);
    int dy1 = (int) (event.getY(0) - lastEvent[2]);
    int dx2 = (int) (event.getX(1) - lastEvent[1]);
    int dy2 = (int) (event.getY(1) - lastEvent[3]);
    Log.d("dx1 ", ""+ dx1);
    Log.d("dx2 ", "" + dx2);
    Log.d("dy1 ", "" + dy1);
    Log.d("dy2 ", "" + dy2);
    //pointer 1
    if(Math.abs(dx1) > Math.abs(dy1) && Math.abs(dx2) > Math.abs(dy2)) 
        if(dx1 >= 2.0 && dx2 <=  -2.0)
            Log.d("first pointer ", "right");
            return true;
        
        else if(dx1 <= -2.0 && dx2 >= 2.0)
            Log.d("first pointer ", "left");
            return true;
        
    
    else 
         if(dy1 >= 2.0 && dy2 <=  -2.0)
                Log.d("seccond pointer ", "top");
                return true;
            
            else if(dy1 <= -2.0 && dy2 >= 2.0)
                Log.d("second pointer ", "bottom");
                return true; 
            

    

    return false;


【讨论】:

你能给我 setTranslationX(), setTranslationY(), setRotation(), setScaleX(), setScaleY() 方法的代码吗【参考方案3】:

我终于用这个了(间距是用来计算两个手指之间的距离),我在缩放后偏移了imageview以保持居中,现在可以正常工作了:

    float newDist = spacing(event);
            float scale = newDist / oldDist;

            int oldH =getLayoutParams().height;
            int oldW =getLayoutParams().width;

            int newH =(int) (getLayoutParams().height*scale);
            int newW =(int) (getLayoutParams().width*scale);

            if(newH<MAX_SIZE && newW<MAX_SIZE)
                //scale the height and width of the view
                getLayoutParams().height = newH;
                getLayoutParams().width = newW;

                //calculate the X and Y offset to apply after scaling to keep the image centered
                int xOffset = (int)(getLayoutParams().height - oldH)/2;
                int yOffset = (int)(getLayoutParams().width - oldW)/2;

                setX(getX()-xOffset);
                setY(getY()-yOffset);
                requestLayout();
                setAdjustViewBounds(true);

                oldDist=newDist; 

【讨论】:

gtreat,但你真的认为它比使用矩阵更紧凑、更简单、更优雅吗? 事实上,我没有,这就是为什么我把你的代码放在我的包里以备不时之需......我使用矩阵模式的测试在我的视图中缩放了画布而不是视图本身,也许我错过了一些东西。 我缩放我的视图而不是画布以将我的触摸事件仅保留到视图的可见部分(如果我减小画布的大小,只有新的可见部分应该触发触摸事件)而不是整个视图(我不确定你是否理解我的意思)。如果你的代码能解决问题,我会改变它(我还没有时间测试,但我会的) 不,我不明白你的意思,抱歉,但顺便说一句,如果两个或多个 ImageView 重叠怎么办?你的代码能用吗? 您可以将您的代码粘贴到 pastebin.com 吗?我想看看【参考方案4】:

由于 scaleType 设置为矩阵,所有这些示例都支持错误的手势。当我尝试缩放时,我无法将图像保持在中心并控制缩放量。所以我做了一些研究并为此编写了一个小而简单但非常令人愉悦的代码:https://***.com/a/65697376/13339685

【讨论】:

以上是关于Android ImageView 缩放和翻译问题的主要内容,如果未能解决你的问题,请参考以下文章

Android 在 ImageView 中绘制边框

android imageView:设置拖动和捏缩放参数

(Android - xml) Gridview 和 ImageView 缩放类型 centerCrop

Android Studio:缩放 ImageView 边界以适应图像

在 Android 上缩放图像会使图像呈现在 ImageView 边界之外

避免 ImageView 中的图像缩放