Можем ли мы использовать детектор жестов масштаба для масштабирования в Android?

Можем ли мы использовать детектор жестов масштаба для масштабирования в Android?


person user562237    schedule 26.04.2011    source источник


Ответы (6)


Вы можете использовать это

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;

public class MyImageView extends View {

private static final int INVALID_POINTER_ID = -1;

    private Drawable mImage;
    private float mPosX;
    private float mPosY;

    private float mLastTouchX;
    private float mLastTouchY;
    private int mActivePointerId = INVALID_POINTER_ID;

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

    public MyImageView(Context context) {
        this(context, null, 0);
    mImage=act.getResources().getDrawable(context.getResources().getIdentifier("imag­ename", "drawable", "packagename"));

        mImage.setBounds(0, 0, mImage.getIntrinsicWidth(), mImage.getIntrinsicHeight());
    }

    public MyImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public MyImageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        // Let the ScaleGestureDetector inspect all events.
        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;
            mActivePointerId = ev.getPointerId(0);
            break;
        }

        case MotionEvent.ACTION_MOVE: {
            final int pointerIndex = ev.findPointerIndex(mActivePointerId);
            final float x = ev.getX(pointerIndex);
            final float y = ev.getY(pointerIndex);

            // Only move if the ScaleGestureDetector isn't processing a gesture.
            if (!mScaleDetector.isInProgress()) {
                final float dx = x - mLastTouchX;
                final float dy = y - mLastTouchY;

                mPosX += dx;
                mPosY += dy;

                invalidate();
            }

            mLastTouchX = x;
            mLastTouchY = y;

            break;
        }

        case MotionEvent.ACTION_UP: {
            mActivePointerId = INVALID_POINTER_ID;
            break;
        }

        case MotionEvent.ACTION_CANCEL: {
            mActivePointerId = INVALID_POINTER_ID;
            break;
        }

        case MotionEvent.ACTION_POINTER_UP: {
            final int pointerIndex = (ev.getAction() & 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;
    }

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

        canvas.save();
        Log.d("DEBUG", "X: "+mPosX+" Y: "+mPosY);
        canvas.translate(mPosX, mPosY);
        canvas.scale(mScaleFactor, mScaleFactor);
        mImage.draw(canvas);
        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, 10.0f));

            invalidate();
            return true;
        }
    }

}

чтобы назвать это в вашем activity.setContentView(new MyImageView(this));

person Community    schedule 26.04.2011
comment
при уменьшении масштаба вы попадаете в точку 0,0. он не увеличивает начальную точку ваших пальцев. Почему это так? - person sajjoo; 09.01.2012
comment
Саджу: Я думаю, вы можете сделать это, используя ScaleGestureDetector.getFocusX,Y() - person Timmmm; 28.07.2012
comment
Я пробовал это, но масштабирование очень не отвечает и прерывисто (я рисую линии на холсте программно). Есть идеи, почему? - person Rookatu; 29.05.2013
comment
Как я могу добавить canvas.drawCircle вместо изображения - person Jagadesh Seeram; 21.03.2015
comment
спасибо @Flexo и Jazz, вы сэкономили мне много времени - person Basant; 27.03.2021

Вы можете создать многоразовый класс, который реализует OnTouchListener для достижения этой цели.

public class MyScaleGestures implements OnTouchListener, OnScaleGestureListener {       
    private View view;
    private ScaleGestureDetector gestureScale;
    private float scaleFactor = 1;  
    private boolean inScale = false;

    public MyScaleGestures (Context c){ gestureScale = new ScaleGestureDetector(c, this); }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        this.view = view; 
        gestureScale.onTouchEvent(event);
        return true;
    }   

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        scaleFactor *= detector.getScaleFactor();
        scaleFactor = (scaleFactor < 1 ? 1 : scaleFactor); // prevent our view from becoming too small //
        scaleFactor = ((float)((int)(scaleFactor * 100))) / 100; // Change precision to help with jitter when user just rests their fingers //
        view.setScaleX(scaleFactor);
        view.setScaleY(scaleFactor);
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        inScale = true;
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) { inScale = false; }
}

Затем назначьте его как OnTouchListener вашего View вот так.

myView.setOnTouchListener(new MyScaleGestures(context));

Если вы хотите добавить возможность прокрутки в View, вам нужно будет реализовать onScroll из интерфейса OnGestureListener. Для этого вы можете добавить это переопределение в класс MyScaleGestures.

@Override
public boolean onScroll(MotionEvent event1, MotionEvent event2, float x, float y) {
    float newX = view.getX();
    float newY = view.getY();
    if(!inScale){
        newX -= x;
        newY -= y;
    }
    WindowManager wm = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
    Display d = wm.getDefaultDisplay();
    Point p = new Point();
    d.getSize(p);

    if (newX > (view.getWidth() * scaleFactor - p.x) / 2){
        newX = (view.getWidth() * scaleFactor - p.x) / 2;
    } else if (newX < -((view.getWidth() * scaleFactor - p.x) / 2)){
        newX = -((view.getWidth() * scaleFactor - p.x) / 2);
    }

    if (newY > (view.getHeight() * scaleFactor - p.y) / 2){
        newY = (view.getHeight() * scaleFactor - p.y) / 2;
    } else if (newY < -((view.getHeight() * scaleFactor - p.y) / 2)){
        newY = -((view.getHeight() * scaleFactor - p.y) / 2);
    }

    view.setX(newX);
    view.setY(newY);

    return true;
}

Конечный результат выполнения всего вышеперечисленного даст вам класс, подобный этому:

public class StandardGestures implements OnTouchListener, OnGestureListener, OnDoubleTapListener, OnScaleGestureListener {
    private View view;
    private GestureDetector gesture;
    private ScaleGestureDetector gestureScale;
    private float scaleFactor = 1;
    private boolean inScale;

    public StandardGestures(Context c){
        gesture = new GestureDetector(c, this);
        gestureScale = new ScaleGestureDetector(c, this);
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        this.view = view;
        gesture.onTouchEvent(event);
        gestureScale.onTouchEvent(event);
        return true;
    }

    @Override
    public boolean onDown(MotionEvent event) {
        return true;
    }

    @Override
    public boolean onFling(MotionEvent event1, MotionEvent event2, float x, float y) {
        return true;
    }

    @Override
    public void onLongPress(MotionEvent event) {
    }

    @Override
    public boolean onScroll(MotionEvent event1, MotionEvent event2, float x, float y) {
        float newX = view.getX();
        float newY = view.getY();
        if(!inScale){
            newX -= x;
            newY -= y;
        }
        WindowManager wm = (WindowManager) view.getContext().getSystemService(Context.WINDOW_SERVICE);
        Display d = wm.getDefaultDisplay();
        Point p = new Point();
        d.getSize(p);

        if (newX > (view.getWidth() * scaleFactor - p.x) / 2){
            newX = (view.getWidth() * scaleFactor - p.x) / 2;
        } else if (newX < -((view.getWidth() * scaleFactor - p.x) / 2)){
            newX = -((view.getWidth() * scaleFactor - p.x) / 2);
        }

        if (newY > (view.getHeight() * scaleFactor - p.y) / 2){
            newY = (view.getHeight() * scaleFactor - p.y) / 2;
        } else if (newY < -((view.getHeight() * scaleFactor - p.y) / 2)){
            newY = -((view.getHeight() * scaleFactor - p.y) / 2);
        }

        view.setX(newX);
        view.setY(newY);

        return true;
    }

    @Override
    public void onShowPress(MotionEvent event) {
    }

    @Override
    public boolean onSingleTapUp(MotionEvent event) {
        return true;
    }

    @Override
    public boolean onDoubleTap(MotionEvent event) {
        view.setVisibility(View.GONE);
        return true;
    }

    @Override
    public boolean onDoubleTapEvent(MotionEvent event) {
        return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent event) {
        return true;
    }

    @Override
    public boolean onScale(ScaleGestureDetector detector) {

        scaleFactor *= detector.getScaleFactor();
        scaleFactor = scaleFactor < 1 ? 1 : scaleFactor; // prevent our image from becoming too small
        scaleFactor = (float) (int) (scaleFactor * 100) / 100; // Change precision to help with jitter when user just rests their fingers //
        view.setScaleX(scaleFactor);
        view.setScaleY(scaleFactor);
        onScroll(null, null, 0, 0); // call scroll to make sure our bounds are still ok //
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        inScale = true;
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        inScale = false;
        onScroll(null, null, 0, 0); // call scroll to make sure our bounds are still ok //
    }
}
person Chris Stillwell    schedule 09.10.2014
comment
Это отличный ответ, так как код не предполагает создания нового класса, производного от ImageView. К сожалению, конструктор и имя класса не совпадают, логическое значение inScale отсутствует и имеет смысл только при наличии метода onScroll(). Но что более важно, чем все вышеперечисленное, метод onScroll() ничего не переопределяет (должен ли класс быть производным от другого слушателя?), и, следовательно, не работает. Или, может быть, я пропустил что-то большое. Можно ли улучшить этот ответ? - person Baltasarq; 18.10.2018
comment
@Baltasarq Спасибо, что указали на это. Класс в ответе использует код из гораздо большего класса, который я использую. Как вы обнаружили, я вырезал слишком много, пытаясь применить это к ответу. Я обновил свой ответ, чтобы исправить ошибки, которые вы нашли. Спасибо за помощь в их ловле и помощь мне улучшить его. - person Chris Stillwell; 18.10.2018
comment
спасибо за быстрый ответ и исправление. Другой вопрос, думаю можно заменить inScale на this.gestureScale.isInProgress(). Это правда или лучше использовать свой собственный логический тип? - person Baltasarq; 19.10.2018
comment
И еще вопрос. При реализации GestureDector.OnGestureListener мне пришлось предоставить onLongPress(), onDown(), onFling(), onShowPress() и onSingleTapUp(). - person Baltasarq; 19.10.2018
comment
Хорошо, прокрутка не работает. Я думаю, мне нужно настроить другого слушателя в образе или в ScaleGestureDetector... - person Baltasarq; 19.10.2018
comment
@Baltasarq, я не уверен, поможет это или нет, но, возможно, стоит попробовать. В onDown(), onFling() и onSingleTapUp() я возвращаю true, и у меня есть комментарий в моем коде, в котором говорится, что возврат true необходим, чтобы не блокировать ввод в этих событиях. Я прикрепил полный класс к своему ответу на случай, если я что-то еще пропустил. - person Chris Stillwell; 19.10.2018
comment
Спасибо, постараюсь и сообщу. - person Baltasarq; 19.10.2018
comment
Нет, извините, не получилось. Я заставил это работать, управляя соответствующими сообщениями в файле onTouch(). - person Baltasarq; 21.10.2018

ScaleGestureDetector доступен, начиная с Android 2.2 (он же Froyo, уровень API 8). См.: http://android-developers.blogspot.com/2010/06/making-sense-of-multitouch.html

В версиях 2.0/2.1 у вас нет ScaleGestureDetector, но вы можете обеспечить масштабирование, используя запись в блоге ZDNet Эда Бернетта, на которую Pieter888 ссылается выше: http://www.zdnet.com/blog/burnette/how-to-use-multi-touch-in-android-2-part-6-implementing-the-pinch-zoom-gesture/1847

person Dalbergia    schedule 31.05.2011

на самом деле есть библиотека, которая использует этот класс только для масштабирования изображений.

он называется "TouchImageView"

person android developer    schedule 22.10.2013

TouchImageView

public class TouchImageView extends ImageView {
    Matrix matrix;
    // We can be in one of these 3 states
    static final int NONE = 0;
    static final int DRAG = 1;
    static final int ZOOM = 2;

    int mode = NONE;

    // Remember some things for zooming
    PointF last = new PointF();
    PointF start = new PointF();
    float minScale = 1f;
    float maxScale = 3f;
    float[] m;
    int viewWidth, viewHeight;

    static final int CLICK = 3;

    float saveScale = 1f;

    protected float origWidth, origHeight;

    int oldMeasuredWidth, oldMeasuredHeight;

    ScaleGestureDetector mScaleDetector;

    Context context;

    public TouchImageView(Context context) {
        super(context);
        sharedConstructing(context);
    }

    public TouchImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        sharedConstructing(context);
    }

    private void sharedConstructing(Context context) {

        super.setClickable(true);

        this.context = context;

        mScaleDetector = new ScaleGestureDetector(context, new ScaleListener());

        matrix = new Matrix();

        m = new float[9];

        setImageMatrix(matrix);

        setScaleType(ScaleType.MATRIX);

        setOnTouchListener(new OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {

                mScaleDetector.onTouchEvent(event);

                PointF curr = new PointF(event.getX(), event.getY());

                switch (event.getAction()) {

                    case MotionEvent.ACTION_DOWN:

                        last.set(curr);

                        start.set(last);

                        mode = DRAG;

                        break;

                    case MotionEvent.ACTION_MOVE:

                        if (mode == DRAG) {

                            float deltaX = curr.x - last.x;

                            float deltaY = curr.y - last.y;

                            float fixTransX = getFixDragTrans(deltaX, viewWidth, origWidth * saveScale);

                            float fixTransY = getFixDragTrans(deltaY, viewHeight, origHeight * saveScale);

                            matrix.postTranslate(fixTransX, fixTransY);

                            fixTrans();

                            last.set(curr.x, curr.y);

                        }

                        break;

                    case MotionEvent.ACTION_UP:

                        mode = NONE;

                        int xDiff = (int) Math.abs(curr.x - start.x);

                        int yDiff = (int) Math.abs(curr.y - start.y);

                        if (xDiff < CLICK && yDiff < CLICK)

                            performClick();

                        break;

                    case MotionEvent.ACTION_POINTER_UP:

                        mode = NONE;

                        break;

                }

                setImageMatrix(matrix);

                invalidate();

                return true; // indicate event was handled
            }
        });
    }

    public void setMaxZoom(float x) {

        maxScale = x;
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {

        @Override
        public boolean onScaleBegin(ScaleGestureDetector detector) {

            mode = ZOOM;

            return true;
        }

        @Override
        public boolean onScale(ScaleGestureDetector detector) {

            float mScaleFactor = detector.getScaleFactor();

            float origScale = saveScale;

            saveScale *= mScaleFactor;

            if (saveScale > maxScale) {

                saveScale = maxScale;

                mScaleFactor = maxScale / origScale;

            } else if (saveScale < minScale) {

                saveScale = minScale;

                mScaleFactor = minScale / origScale;

            }

            if (origWidth * saveScale <= viewWidth || origHeight * saveScale <= viewHeight)

                matrix.postScale(mScaleFactor, mScaleFactor, viewWidth / 2, viewHeight / 2);

            else

                matrix.postScale(mScaleFactor, mScaleFactor, detector.getFocusX(), detector.getFocusY());

            fixTrans();

            return true;
        }
    }

    void fixTrans() {

        matrix.getValues(m);

        float transX = m[Matrix.MTRANS_X];

        float transY = m[Matrix.MTRANS_Y];

        float fixTransX = getFixTrans(transX, viewWidth, origWidth * saveScale);

        float fixTransY = getFixTrans(transY, viewHeight, origHeight * saveScale);

        if (fixTransX != 0 || fixTransY != 0)

            matrix.postTranslate(fixTransX, fixTransY);
    }

    float getFixTrans(float trans, float viewSize, float contentSize) {

        float minTrans, maxTrans;

        if (contentSize <= viewSize) {

            minTrans = 0;

            maxTrans = viewSize - contentSize;

        } else {

            minTrans = viewSize - contentSize;

            maxTrans = 0;

        }

        if (trans < minTrans)

            return -trans + minTrans;

        if (trans > maxTrans)

            return -trans + maxTrans;

        return 0;
    }

    float getFixDragTrans(float delta, float viewSize, float contentSize) {

        if (contentSize <= viewSize) {

            return 0;

        }

        return delta;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        viewWidth = MeasureSpec.getSize(widthMeasureSpec);

        viewHeight = MeasureSpec.getSize(heightMeasureSpec);

        //
        // Rescales image on rotation
        //
        if (oldMeasuredHeight == viewWidth && oldMeasuredHeight == viewHeight

                || viewWidth == 0 || viewHeight == 0)

            return;

        oldMeasuredHeight = viewHeight;

        oldMeasuredWidth = viewWidth;

        if (saveScale == 1) {

            //Fit to screen.

            float scale;

            Drawable drawable = getDrawable();

            if (drawable == null || drawable.getIntrinsicWidth() == 0 || drawable.getIntrinsicHeight() == 0)

                return;

            int bmWidth = drawable.getIntrinsicWidth();

            int bmHeight = drawable.getIntrinsicHeight();

            Log.e("bmSize", "bmWidth: " + bmWidth + " bmHeight : " + bmHeight);

            float scaleX = (float) viewWidth / (float) bmWidth;

            float scaleY = (float) viewHeight / (float) bmHeight;

            scale = Math.min(scaleX, scaleY);

            matrix.setScale(scale, scale);

            // Center the image

            float redundantYSpace = (float) viewHeight - (scale * (float) bmHeight);

            float redundantXSpace = (float) viewWidth - (scale * (float) bmWidth);

            redundantYSpace /= (float) 2;

            redundantXSpace /= (float) 2;

            matrix.postTranslate(redundantXSpace, redundantYSpace);

            origWidth = viewWidth - 2 * redundantXSpace;

            origHeight = viewHeight - 2 * redundantYSpace;

            setImageMatrix(matrix);

        }

        fixTrans();
    }
}

MainActivity.java

public class SecondActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);


        TouchImageView img = new TouchImageView(this);
        img.setImageResource(R.drawable.ic_launcher);
        img.setMaxZoom(4f);
        setContentView(img);
    }
}
person Maradiya Krupa    schedule 10.03.2017

да, мы можем вот пример кода, где onPinch() и onZoom() - это действия, которые нужно реализовать самостоятельно

public class simpleOnScaleGestureListener extends
        SimpleOnScaleGestureListener {

    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        startScale = detector.getScaleFactor();
        return true;
    }

    @Override
    public boolean onScaleBegin(ScaleGestureDetector detector) {
        return true;
    }

    @Override
    public void onScaleEnd(ScaleGestureDetector detector) {
        endScale = detector.getScaleFactor();



        if (startScale > endScale) {
            Log.i("onScaleEnd", "Pinch Dection");
            onPinch();
        } else if (startScale < endScale) {
            Log.i("onScaleEnd", "Zoom Dection");
            onZoom();
        }


    }

}
person AMD    schedule 30.04.2013
comment
Я думаю, вы слишком много упускаете из виду, чтобы этот ответ был полезен. Это только ваш ScaleGestureListener, а как насчет остального? - person Simon Forsberg; 23.06.2013
comment
я вызвал onpinch() и onzoom(), у которых есть реализация pinchzoom на моем customeview, вы можете реализовать свой собственный - person AMD; 24.06.2013
comment
вот что я ищу - person Dave Doga Oz; 26.05.2015