Android 曲面视图上的收缩缩放

Android 曲面视图上的收缩缩放,android,surfaceview,pinchzoom,Android,Surfaceview,Pinchzoom,我正在尝试在SurfaceView上实现收缩缩放。我已经做了很多关于这个的研究,并且我找到了一个实现收缩缩放的类。下面是我如何修改它的: public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener { private SurfaceView mSurfaceView; private enum Mode { NONE, DRAG,

我正在尝试在
SurfaceView
上实现收缩缩放。我已经做了很多关于这个的研究,并且我找到了一个实现收缩缩放的类。下面是我如何修改它的:

public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {

private SurfaceView mSurfaceView;

private enum Mode {
    NONE,
    DRAG,
    ZOOM
}

private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;

private Mode mode;
private float scale = 1.0f;
private float lastScaleFactor = 0f;

// Where the finger first  touches the screen
private float startX = 0f;
private float startY = 0f;

// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;

public ZoomLayout(Context context) {
    super(context);
    init(context);
}

public ZoomLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}

public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(context);
}

private void init(Context context) {
    final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
    setOnTouchListener(new OnTouchListener() {
        public boolean onTouch(View view, MotionEvent motionEvent) {
            ZoomLayout.this.mSurfaceView = (SurfaceView) view.findViewById(R.id.mSurfaceView);

            switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN:
                    Log.i(TAG, "DOWN");
                    if (scale > MIN_ZOOM) {
                        mode = Mode.DRAG;
                        startX = motionEvent.getX() - prevDx;
                        startY = motionEvent.getY() - prevDy;
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mode == Mode.DRAG) {
                        dx = motionEvent.getX() - startX;
                        dy = motionEvent.getY() - startY;
                    }
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    mode = Mode.ZOOM;
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    mode = Mode.NONE;
                    break;
                case MotionEvent.ACTION_UP:
                    Log.i(TAG, "UP");
                    mode = Mode.NONE;
                    prevDx = dx;
                    prevDy = dy;
                    break;
            }
            scaleDetector.onTouchEvent(motionEvent);

            if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
                getParent().requestDisallowInterceptTouchEvent(true);
                float maxDx = (child().getWidth() - (child().getWidth() / scale)) / 2 * scale;
                float maxDy = (child().getHeight() - (child().getHeight() / scale))/ 2 * scale;
                dx = Math.min(Math.max(dx, -maxDx), maxDx);
                dy = Math.min(Math.max(dy, -maxDy), maxDy);
                Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
                        + ", max " + maxDx);
                applyScaleAndTranslation();
            }

            return true;
        }
    });
}

// ScaleGestureDetector

public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
    Log.i(TAG, "onScaleBegin");
    return true;
}

public boolean onScale(ScaleGestureDetector scaleDetector) {
    float scaleFactor = scaleDetector.getScaleFactor();
    Log.i(TAG, "mode:" + this.mode + ", onScale:" + scaleFactor);
    if (this.lastScaleFactor == 0.0f || Math.signum(scaleFactor) == Math.signum(this.lastScaleFactor)) {
        this.scale *= scaleFactor;
        this.scale = Math.max(MIN_ZOOM, Math.min(this.scale, MAX_ZOOM));
        this.lastScaleFactor = scaleFactor;
    } else {
        this.lastScaleFactor = 0.0f;
    }
    if (this.mSurfaceView != null) {
        int orgWidth = getWidth();
        int _width = (int) (((float) orgWidth) * this.scale);
        int _height = (int) (((float) getHeight()) * this.scale);
        LayoutParams params = (LayoutParams) this.mSurfaceView.getLayoutParams();
        params.height = _height;
        params.width = _width;
        this.mSurfaceView.setLayoutParams(params);
        child().setScaleX(this.scale);
        child().setScaleY(this.scale);
    }
    return true;
}

@Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
    Log.i(TAG, "onScaleEnd");
}

private void applyScaleAndTranslation() {
    child().setScaleX(scale);
    child().setScaleY(scale);
    child().setTranslationX(dx);
    child().setTranslationY(dy);
}

private View child() {
    return getChildAt(0);
}
我将其实现到我的布局中,如下所示:

<pacageName.control.ZoomLayout
    android:id="@+id/mZoomLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:measureAllChildren="true">

        <SurfaceView
            android:id="@+id/mSurfaceView"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

</pacageName.control.ZoomLayout>
我现在面临的问题是,它会缩放到X-0和Y-0,这意味着它会缩放到屏幕的左上角,而不是缩放到我手指之间的点。。我认为这可能与以下方面有关:

public class ZoomableSurfaceView extends SurfaceView {

private ScaleGestureDetector SGD;
private Context context;
private boolean isSingleTouch;
private float width, height = 0;
private float scale = 1f;
private float minScale = 1f;
private float maxScale = 5f;
int left, top, right, bottom;

public ZoomableSurfaceView(Context context) {
    super(context);
    this.context = context;
    init();
}

public ZoomableSurfaceView(Context context, AttributeSet attrs) {
    super(context, attrs);
    this.context = context;
    init();
}

public ZoomableSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    this.context = context;
    init();
}

private void init() {
    setOnTouchListener(new MyTouchListeners());
    SGD = new ScaleGestureDetector(context, new ScaleListener());
    this.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {

        }
    });
}

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    if (width == 0 && height == 0) {
        width = ZoomableSurfaceView.this.getWidth();
        height = ZoomableSurfaceView.this.getHeight();
        this.left = left;
        this.right = right;
        this.top = top;
        this.bottom = bottom;
    }

}

private class MyTouchListeners implements View.OnTouchListener {

    float dX, dY;

    MyTouchListeners() {
        super();
    }

    @Override
    public boolean onTouch(View view, MotionEvent event) {
        SGD.onTouchEvent(event);
        if (event.getPointerCount() > 1) {
            isSingleTouch = false;
        } else {
            if (event.getAction() == MotionEvent.ACTION_UP) {
                isSingleTouch = true;
            }
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                dX = ZoomableSurfaceView.this.getX() - event.getRawX();
                dY = ZoomableSurfaceView.this.getY() - event.getRawY();
                break;

            case MotionEvent.ACTION_MOVE:
                if (isSingleTouch) {
                    ZoomableSurfaceView.this.animate()
                            .x(event.getRawX() + dX)
                            .y(event.getRawY() + dY)
                            .setDuration(0)
                            .start();
                    checkDimension(ZoomableSurfaceView.this);
                }
                break;
            default:
                return true;
        }
        return true;
    }
}

private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
    @Override
    public boolean onScale(ScaleGestureDetector detector) {
        //Log.e("onGlobalLayout: ", scale + " " + width + " " + height);
        scale *= detector.getScaleFactor();
        scale = Math.max(minScale, Math.min(scale, maxScale));
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams((int) (width * scale), (int) (height * scale));
        Log.e("onGlobalLayout: ", (int) (width * scale) + " " + (int) (height * scale));
        ZoomableSurfaceView.this.setLayoutParams(params);
        checkDimension(ZoomableSurfaceView.this);
        return true;
    }
}

private void checkDimension(View vi) {
    if (vi.getX() > left) {
        vi.animate()
                .x(left)
                .y(vi.getY())
                .setDuration(0)
                .start();
    }

    if ((vi.getWidth() + vi.getX()) < right) {
        vi.animate()
                .x(right - vi.getWidth())
                .y(vi.getY())
                .setDuration(0)
                .start();
    }

    if (vi.getY() > top) {
        vi.animate()
                .x(vi.getX())
                .y(top)
                .setDuration(0)
                .start();
    }

    if ((vi.getHeight() + vi.getY()) < bottom) {
        vi.animate()
                .x(vi.getX())
                .y(bottom - vi.getHeight())
                .setDuration(0)
                .start();
    }
}
}
@Override
    public boolean onScale(ScaleGestureDetector detector) {
        //Log.e("onGlobalLayout: ", scale + " " + width + " " + height);
        scale *= detector.getScaleFactor();
        scale = Math.max(minScale, Math.min(scale, maxScale));
        FrameLayout.LayoutParams params = new FrameLayout.LayoutParams((int) (width * scale), (int) (height * scale));
        Log.e("onGlobalLayout: ", (int) (width * scale) + " " + (int) (height * scale));
        ZoomableSurfaceView.this.setLayoutParams(params);
        checkDimension(ZoomableSurfaceView.this);
        return true;
    }
我有一个想法,我应该检测两个手指的中心点,并将其添加到
OnScale
。我应该如何进行


编辑3:

好了,我终于让变焦在我的J7 Pro跑步牛轧糖上完美工作了。但是现在的问题是,我的S4运行棒棒糖中的
SurfaceView
没有放大,而是移动到了左上角。我已经测试过在我的自定义zoomview中放置一个
ImageView
,图像可以像预期的那样完全缩放。这就是我的自定义缩放类现在的样子:

public class ZoomLayout extends FrameLayout implements ScaleGestureDetector.OnScaleGestureListener {

private enum Mode {
    NONE,
    DRAG,
    ZOOM
}

private static final String TAG = "ZoomLayout";
private static final float MIN_ZOOM = 1.0f;
private static final float MAX_ZOOM = 4.0f;

private Mode mode = Mode.NONE;
private float scale = 1.0f;
private float lastScaleFactor = 0f;

// Where the finger first  touches the screen
private float startX = 0f;
private float startY = 0f;

// How much to translate the canvas
private float dx = 0f;
private float dy = 0f;
private float prevDx = 0f;
private float prevDy = 0f;

public ZoomLayout(Context context) {
    super(context);
    init(context);
}

public ZoomLayout(Context context, AttributeSet attrs) {
    super(context, attrs);
    init(context);
}

public ZoomLayout(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(context);
}

private void init(Context context) {
    final ScaleGestureDetector scaleDetector = new ScaleGestureDetector(context, this);
    setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View view, MotionEvent motionEvent) {
            switch (motionEvent.getAction() & MotionEvent.ACTION_MASK) {
                case MotionEvent.ACTION_DOWN:
                    Log.i(TAG, "DOWN");
                    if (scale > MIN_ZOOM) {
                        mode = Mode.DRAG;
                        startX = motionEvent.getX() - prevDx;
                        startY = motionEvent.getY() - prevDy;
                    }
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mode == Mode.DRAG) {
                        dx = motionEvent.getX() - startX;
                        dy = motionEvent.getY() - startY;
                    }
                    break;
                case MotionEvent.ACTION_POINTER_DOWN:
                    mode = Mode.ZOOM;
                    break;
                case MotionEvent.ACTION_POINTER_UP:
                    mode = Mode.NONE; // changed from DRAG, was messing up zoom
                    break;
                case MotionEvent.ACTION_UP:
                    Log.i(TAG, "UP");
                    mode = Mode.NONE;
                    prevDx = dx;
                    prevDy = dy;
                    break;
            }
            scaleDetector.onTouchEvent(motionEvent);

            if ((mode == Mode.DRAG && scale >= MIN_ZOOM) || mode == Mode.ZOOM) {
                getParent().requestDisallowInterceptTouchEvent(true);
                float maxDx = child().getWidth() * (scale - 1);  // adjusted for zero pivot
                float maxDy = child().getHeight() * (scale - 1);  // adjusted for zero pivot
                dx = Math.min(Math.max(dx, -maxDx), 0);  // adjusted for zero pivot
                dy = Math.min(Math.max(dy, -maxDy), 0);  // adjusted for zero pivot
                Log.i(TAG, "Width: " + child().getWidth() + ", scale " + scale + ", dx " + dx
                        + ", max " + maxDx);
                applyScaleAndTranslation();
            }

            return true;
        }
    });
}

// ScaleGestureDetector
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleDetector) {
    Log.i(TAG, "onScaleBegin");
    return true;
}

@Override
public boolean onScale(ScaleGestureDetector scaleDetector) {
    float scaleFactor = scaleDetector.getScaleFactor();
    Log.i(TAG, "onScale(), scaleFactor = " + scaleFactor);
    if (lastScaleFactor == 0 || (Math.signum(scaleFactor) == Math.signum(lastScaleFactor))) {
        float prevScale = scale;
        scale *= scaleFactor;
        scale = Math.max(MIN_ZOOM, Math.min(scale, MAX_ZOOM));
        lastScaleFactor = scaleFactor;
        float adjustedScaleFactor = scale / prevScale;
        // added logic to adjust dx and dy for pinch/zoom pivot point
        Log.d(TAG, "onScale, adjustedScaleFactor = " + adjustedScaleFactor);
        Log.d(TAG, "onScale, BEFORE dx/dy = " + dx + "/" + dy);
        float focusX = scaleDetector.getFocusX();
        float focusY = scaleDetector.getFocusY();
        Log.d(TAG, "onScale, focusX/focusy = " + focusX + "/" + focusY);
        dx += (dx - focusX) * (adjustedScaleFactor - 1);
        dy += (dy - focusY) * (adjustedScaleFactor - 1);
        Log.d(TAG, "onScale, dx/dy = " + dx + "/" + dy);
    } else {
        lastScaleFactor = 0;
    }
    return true;
}

@Override
public void onScaleEnd(ScaleGestureDetector scaleDetector) {
    Log.i(TAG, "onScaleEnd");
}

private void applyScaleAndTranslation() {
    child().setScaleX(scale);
    child().setScaleY(scale);
    child().setPivotX(0f);  // default is to pivot at view center
    child().setPivotY(0f);  // default is to pivot at view center
    child().setTranslationX(dx);
    child().setTranslationY(dy);
}

private View child() {
    return getChildAt(0);
}

编辑4:添加行为视频:

请查看将我的zoomLayout类更新为edit 3-


不要使用
SurfaceView
,而是使用
TextureView
,它具有更多的功能,并且优于
SurfaceView
。看


您的代码确实正确,应该可以正常工作,但在某些情况下,绘制在
SurfaceView
上的
MediaPlayer
不会调整大小,并且会在手机的0,0位置产生缩放错觉。如果为
SurfaceView
设置小于父对象的大小,并为父对象设置背景,则会注意到这一点


代码中的解决方案

样本项目:

该项目扩展TextureView并实现触摸以创建缩放效果


通过缩放其父框架布局来缩放SurfaceView仅在牛轧糖(API 24)及以上版本中有效


此缩放不会更改SurfaceView分辨率,即它比通过更改布局参数缩放SurfaceView更安全、更平滑(但分辨率是固定的)。这是自定义视图的唯一选项,例如GStreamerSurfaceView

可能同时运行SurfaceView+缩放它对于某些设备来说太重了。您可能想分析您的app@VladyslavMatviienko好的,我在这两个设备上都做了一个配置文件,并且这两个设备上的内存和CPU都不高。编辑3对我来说非常适合,谢谢你的解决方案!