在SurfaceView上捏缩放

问题描述 投票:7回答:2

请阅读编辑3 ..

我正在尝试在SurfaceView上实现捏缩放。我已经做了很多关于这方面的研究,我找到了this类来实现捏缩放。这是我如何修改它:

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>

我遇到的问题是缩放不是'平滑',并且缩放的行为在不同的设备(三星S4(棒棒糖)和J7Pro(牛轧糖))中是不同的。

我不确定为什么捏缩放是“毛病”,为什么它不会在两个不同的设备上以相同的方式缩放..

有没有人处理过这个问题,或许可以指出我正确的方向?


编辑1:下面添加的内存和CPU消耗图像 -

CPU


编辑2:在尝试别的东西之后,我正面临一个新问题 -

好的,所以我将ZoomLayout完全更改为以下内容:

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

我现在面临的问题是它缩放到X - 0和Y - 0意味着它缩放到屏幕的左上角而不是缩放到我的手指之间的点。我认为它可能与以下:

@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运行Nougat完美地完成了变焦。但现在的问题是,我的S4运行Lollipop中的SurfaceView没有放大,而是我的SurfaceView被移动到左上角。我已经测试过在我的自定义缩放视图中放置一个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类以编辑3后,请查看当前缩放的样子 -

Video of SurfaceView zoom behaviour

android surfaceview pinchzoom
2个回答
4
投票

使用SurfaceView而不是使用TextureViewSurfaceView具有更多功能,并且优于TextureView Documentation。见MediaPlayer


你的代码确实是正确的并且应该可以工作但是在某些情况下,在SurfaceView上绘制的SurfaceView没有调整大小并且在手机的位置0,0处发生缩放的错觉。如果您设置的尺寸小于Pinch Zoom的父级并为父级设置背景,您会注意到这一点


代码解决方案

示例项目:IMAGE: DEMONSTRATION

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

qazxswpoi


0
投票

通过缩放其父FrameLayout来缩放SurfaceView将仅适用于Nougat(API 24)及更高版本

这种缩放不会改变SurfaceView分辨率,即它比通过更改其LayoutParams(但分辨率是固定的)缩放SurfaceView更安全和平滑。这是自定义视图的唯一选项,例如GStreamerSurfaceView

© www.soinside.com 2019 - 2024. All rights reserved.