使用双指触控实现 Android 相机预览缩放

7

我正在尝试在相机预览模式下使用双指触摸手势进行缩放,但无法实现。我已经在ImageView上完成了缩放控件,并且它可以正常工作。现在我想在相机预览模式下实现这一点,也就是说,在启动相机时,我们可以使用双指手势进行缩放。

以下是我添加的用于缩放图像的双指手势代码。

@Override
public boolean onTouch(View v, MotionEvent event) {
    // TODO Auto-generated method stub
    ImageView view = (ImageView) v;

    switch (event.getAction() & MotionEvent.ACTION_MASK) {
    case MotionEvent.ACTION_DOWN:
        savedMatrix.set(matrix);
        start.set(event.getX(), event.getY());
        Log.d(TAG, "mode=DRAG");
        mode = DRAG;
        break;
    case MotionEvent.ACTION_POINTER_DOWN:
        oldDist = spacing(event);
        Log.d(TAG, "oldDist=" + oldDist);
        if (oldDist > 10f) {
            savedMatrix.set(matrix);
            midPoint(mid, event);
            mode = ZOOM;
            Log.d(TAG, "mode=ZOOM");
        }
        break;
    case MotionEvent.ACTION_UP:
    case MotionEvent.ACTION_POINTER_UP:
        mode = NONE;
        Log.d(TAG, "mode=NONE");
        break;
    case MotionEvent.ACTION_MOVE:
        if (mode == DRAG) {

            // for draging the image

              matrix.set(savedMatrix); matrix.postTranslate(event.getX() -
              start.x, event.getY() - start.y);

            break;
        } else if (mode == ZOOM) {
            float newDist = spacing(event);
            Log.d(TAG, "newDist=" + newDist);
            if (newDist > 10f) {
                matrix.set(savedMatrix);
                float scale = newDist / oldDist;
                matrix.postScale(scale, scale, mid.x, mid.y);
            }
        }
        break;
    }

    view.setImageMatrix(matrix);
    return true;
}

请告诉我如何在相机预览中实现手势缩放控制,至少提供一个教程链接。
谢谢 Vikash
3个回答

10

这是我在 Github 上找到的一个解决方案: https://github.com/Betulaphobe/ChatCapsule/blob/8d4f00a7d4c9166aa7ce974670fdf3033a6064f4/chc_application/src/chc/helpers/CameraPreview.java

@Override
public boolean onTouchEvent(MotionEvent event) {
    // Get the pointer ID
    Camera.Parameters params = mCamera.getParameters();
    int action = event.getAction();


    if (event.getPointerCount() > 1) {
        // handle multi-touch events
        if (action == MotionEvent.ACTION_POINTER_DOWN) {
            mDist = getFingerSpacing(event);
        } else if (action == MotionEvent.ACTION_MOVE && params.isZoomSupported()) {
            mCamera.cancelAutoFocus();
            handleZoom(event, params);
        }
    } else {
        // handle single touch events
        if (action == MotionEvent.ACTION_UP) {
            handleFocus(event, params);
        }
    }
    return true;
}

private void handleZoom(MotionEvent event, Camera.Parameters params) {
    int maxZoom = params.getMaxZoom();
    int zoom = params.getZoom();
    float newDist = getFingerSpacing(event);
    if (newDist > mDist) {
        //zoom in
        if (zoom < maxZoom)
            zoom++;
    } else if (newDist < mDist) {
        //zoom out
        if (zoom > 0)
            zoom--;
    }
    mDist = newDist;
    params.setZoom(zoom);
    mCamera.setParameters(params);
}

public void handleFocus(MotionEvent event, Camera.Parameters params) {
    int pointerId = event.getPointerId(0);
    int pointerIndex = event.findPointerIndex(pointerId);
    // Get the pointer's current position
    float x = event.getX(pointerIndex);
    float y = event.getY(pointerIndex);

    List<String> supportedFocusModes = params.getSupportedFocusModes();
    if (supportedFocusModes != null && supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
        mCamera.autoFocus(new Camera.AutoFocusCallback() {
            @Override
            public void onAutoFocus(boolean b, Camera camera) {
                // currently set to auto-focus on single touch
            }
        });
    }
}

/** Determine the space between the first two fingers */
private float getFingerSpacing(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);
}

这段代码片段让我在查找确切的x,y位置时遇到了问题。你能帮我解决一下吗? - user2875895
你可以从MotionEvent中获取x,y坐标。 float x = event.getX(pointerIndex); float y = event.getY(pointerIndex); - mtbomb
这个 handleFocus 函数中的 xy 值到底是做什么用的?看起来它们没有被使用。 - Jas

3

我找到了解决我的问题的方法,以下是解决方案。

 case MotionEvent.ACTION_MOVE:


              if (mode == ZOOM) {
                float newDist = spacing(event);

                double zoomDist = newDist-oldDist;

                if(zoomDist > 0){
                    if(zoomDist > 50 && zoomDist <= 200){

                        if (curZoomLevel < mZoomMax && gestureZoom == 0) {
                            gestureZoom ++;
                            GestureZoomIn();
                        }

                    }else if(zoomDist > 200 && zoomDist <= 300){


                        if (curZoomLevel < mZoomMax && gestureZoom == 1) {

                            gestureZoom ++;
                            GestureZoomIn();
                        }
                    }else if(zoomDist > 300 && zoomDist <= 400){

                        if (curZoomLevel < mZoomMax && gestureZoom == 2) {

                            gestureZoom++;
                            GestureZoomIn();
                        }

//

 private void GestureZoomIn(){
       if (mParameters.isZoomSupported()){
            mZoomMax = mParameters.getMaxZoom();
            if (zoom_text_value<mZoomMax) {
                zoom_text_value++;
                curZoomLevel++;
                zoom_float=zoom_float+0.5;
                onZoomValueChanged(curZoomLevel);

//

private void GestureZoomOut(){
       if (mParameters.isZoomSupported()){

            if (0<zoom_text_value) {
                zoom_text_value--;
                curZoomLevel--;
                zoom_float=zoom_float-0.5;
                onZoomValueChanged(curZoomLevel);

我使用上述代码实现了多重缩放相机预览。

你能帮我学习如何在自定义相机上添加缩放功能吗? - Rishi Gautam
先生,这是我的CameraDemo类和预览类http://pastie.org/private/fpyiijzziolla5sgw4m2ra和http://pastie.org/private/6fzecgzc7ecsojmhccgwng,请查看此链接。 - Rishi Gautam
我的XML类,请问您能给我您的电子邮件地址吗?这样我就可以发送我的项目了。 - Rishi Gautam
你好,我尝试了你的解决方案,但是你使用了 onZoomValueChanged 方法。能否请你向我解释一下这个方法是做什么的?谢谢你的帮助。 - user2586419
嗨@VikashKumar,这篇文章非常有用,请问您能否分享一下捏合缩放的代码?谢谢。 - Dilip
显示剩余3条评论

3

请看我的实现。支持两个相机API:Camera和Camera2。您可以使用双指触摸进行缩放。

以下是我在活动中使用代码的方法。

对于相机API,只需:

private void setupZoomHandler(final Camera.Parameters parameters) {
    if ( parameters.isZoomSupported() ) {
        SimpleZoomHandlerBuilder.forView( mCameraPreview )
                .setMaxZoom( parameters.getMaxZoom() )
                .setZoomListener( new SimpleZoomHandler.IZoomHandlerListener() {
                    @Override
                    public void onZoomChanged(int newZoom) {
                        Camera.Parameters params = mCamera.getParameters();
                        params.setZoom( newZoom );
                        mCamera.setParameters( params );
                    }
                } )
                .build();
    }
}

对于Camera2 API,只需:

private void setupZoomHandler(CameraCharacteristics cameraCharacteristics) {
    ActiveArrayZoomHandlerBuilder.forView( mTextureView )
            .setActiveArraySize( cameraCharacteristics.get( CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE ) )
            .setMaxZoom( cameraCharacteristics.get( CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM ) * 10 )
            .setZoomListener( new ActiveArrayZoomHandler.IZoomHandlerListener() {
                @Override
                public void onZoomChanged(Rect zoom) {
                    mCaptureRequestBuilder.set( CaptureRequest.SCALER_CROP_REGION, zoom );
                    updatePreview();
                }
            } )
            .build();
}

这里是处理缩放所需的类:

public abstract class AbstractZoomHandler implements View.OnTouchListener {

private static final int FINGER_SPACING_DELTA_FOR_ZOOM = 25;
private static final int FINGER_SPACING_ZOOM_INCREMENT = 5;

private static final float DEFAULT_ZOOM_HARDNESS = 0.4f;

private float lastFingerSpacingTime;
private float fingerSpacingBuffer;

protected int zoomLevel;
protected float zoomIncrement;
protected float maxZoom;

public AbstractZoomHandler(View touchableView) {
    touchableView.setOnTouchListener( this );
    this.lastFingerSpacingTime = 0;
    this.fingerSpacingBuffer = 0;
    this.zoomLevel = 1;
    setZoomHardness( DEFAULT_ZOOM_HARDNESS );
}

public void setMaxZoom(float maxZoom) {
    this.maxZoom = maxZoom;
}

public void setZoomHardness(float zoomHardness) {
    this.zoomIncrement = Math.round( zoomHardness * FINGER_SPACING_ZOOM_INCREMENT );
}

@Override
public boolean onTouch(View v, MotionEvent event) {
    if ( !isPrepared() ) {
        return true;
    }
    if ( event.getAction() == MotionEvent.ACTION_UP ) {
        fingerSpacingBuffer = 0;
        return true;
    }
    if ( isTwoFingersTouchEvent( event ) ) {
        int newZoomLevel = performTwoFingersZoom( event );
        recalculateZoom( newZoomLevel );
    }
    return true;
}

private boolean isTwoFingersTouchEvent(MotionEvent event) {
    return event.getPointerCount() == 2;
}

private int performTwoFingersZoom(MotionEvent event) {
    int newZoomLevel = zoomLevel;
    float currentFingerSpacingTime = getFingerSpacing( event );
    fingerSpacingBuffer += currentFingerSpacingTime - lastFingerSpacingTime;
    if ( fingerSpacingBuffer >= FINGER_SPACING_DELTA_FOR_ZOOM && maxZoom > zoomLevel ) {
        newZoomLevel += zoomIncrement;
        fingerSpacingBuffer = 0;
    } else if ( fingerSpacingBuffer <= -FINGER_SPACING_DELTA_FOR_ZOOM && zoomLevel > 1 ) {
        newZoomLevel -= zoomIncrement;
        ;
        fingerSpacingBuffer = 0;
    }
    lastFingerSpacingTime = currentFingerSpacingTime;
    return newZoomLevel;
}

private void recalculateZoom(int newZoomLevel) {
    if ( newZoomLevel == zoomLevel ||
            newZoomLevel < 0 ||
            newZoomLevel > maxZoom) {
        return;
    }
    zoomLevel = newZoomLevel;
    notifyZoomChanged( zoomLevel );
}

private float getFingerSpacing(MotionEvent event) {
    float x = event.getX( 0 ) - event.getX( 1 );
    float y = event.getY( 0 ) - event.getY( 1 );
    return (float) Math.sqrt( x * x + y * y );
}

public abstract void notifyZoomChanged(int zoom);

public abstract boolean isPrepared();

}

相机相关:

public class SimpleZoomHandler extends AbstractZoomHandler {

private IZoomHandlerListener zoomHandlerListener;

SimpleZoomHandler(View touchableView) {
    super( touchableView );
}

public void setZoomHandlerListener(IZoomHandlerListener zoomHandlerListener) {
    this.zoomHandlerListener = zoomHandlerListener;
}

@Override
public void notifyZoomChanged(int zoom) {
    zoomHandlerListener.onZoomChanged( zoom );
}

@Override
public boolean isPrepared() {
    return zoomHandlerListener != null;
}

public interface IZoomHandlerListener {

    void onZoomChanged(int newZoom);

}
}

对于Camera2:

public class ActiveArrayZoomHandler extends AbstractZoomHandler {

private IZoomHandlerListener zoomHandlerListener;
private Rect activeArraySize;

ActiveArrayZoomHandler(View touchableView) {
    super( touchableView );
}

public void setZoomHandlerListener(IZoomHandlerListener zoomHandlerListener) {
    this.zoomHandlerListener = zoomHandlerListener;
}

public void setActiveArraySize(Rect activeArraySize) {
    this.activeArraySize = activeArraySize;
}


@Override
public void notifyZoomChanged(int zoom) {
    int minW = (int) (activeArraySize.width() / maxZoom);
    int minH = (int) (activeArraySize.height() / maxZoom);
    int difW = activeArraySize.width() - minW;
    int difH = activeArraySize.height() - minH;
    int cropW = difW / 100 * zoomLevel;
    int cropH = difH / 100 * zoomLevel;
    cropW -= cropW & 3;
    cropH -= cropH & 3;
    Rect zoomRect = new Rect( cropW, cropH, activeArraySize.width() - cropW, activeArraySize.height() - cropH );
    zoomHandlerListener.onZoomChanged( zoomRect );
}

@Override
public boolean isPrepared() {
    return zoomHandlerListener != null && activeArraySize != null;
}

public interface IZoomHandlerListener {

    void onZoomChanged(Rect zoom);

}
}

并且它们各自拥有Builder,使其具备流畅的界面。针对相机API:

public class SimpleZoomHandlerBuilder {

private SimpleZoomHandler simpleZoomHandler;

public static SimpleZoomHandlerBuilder forView(View touchableView) {
    return new SimpleZoomHandlerBuilder( touchableView );
}

private SimpleZoomHandlerBuilder(View touchableView) {
    simpleZoomHandler = new SimpleZoomHandler( touchableView );
}

public SimpleZoomHandlerBuilder setZoomListener(SimpleZoomHandler.IZoomHandlerListener listener) {
    simpleZoomHandler.setZoomHandlerListener( listener );
    return this;
}

public SimpleZoomHandlerBuilder setMaxZoom(float maxZoom) {
    simpleZoomHandler.setMaxZoom( maxZoom );
    return this;
}

public SimpleZoomHandler build() {
    return simpleZoomHandler;
}

}

对于camera2 API:

public class ActiveArrayZoomHandlerBuilder {

private ActiveArrayZoomHandler activeArrayZoomHandler;

public static ActiveArrayZoomHandlerBuilder forView(View touchableView) {
    return new ActiveArrayZoomHandlerBuilder( touchableView );
}

private ActiveArrayZoomHandlerBuilder(View touchableView) {
    activeArrayZoomHandler = new ActiveArrayZoomHandler( touchableView );
}

public ActiveArrayZoomHandlerBuilder setZoomListener(ActiveArrayZoomHandler.IZoomHandlerListener listener) {
    activeArrayZoomHandler.setZoomHandlerListener( listener );
    return this;
}

public ActiveArrayZoomHandlerBuilder setMaxZoom(float maxZoom) {
    activeArrayZoomHandler.setMaxZoom( maxZoom );
    return this;
}

public ActiveArrayZoomHandlerBuilder setActiveArraySize(Rect activeArraySize) {
    activeArrayZoomHandler.setActiveArraySize( activeArraySize );
    return this;
}

public ActiveArrayZoomHandler build() {
    return activeArrayZoomHandler;
}

}

1
它运行良好。我们能否在缩放的同时实现onTapFocus? - Peddiraju G
@PeddiRaju,camera2中updatePreview()的实现如何? - AndroidRuntimeException
我已经发现了 = captureSession.setRepeatingRequest(previewRequestBuilder.build(), captureCallback, backgroundHandler); - AndroidRuntimeException
@AndroidRuntimeException getFingerSpacing获取pointerindex异常如何在x和y坐标中解决 - newxamarin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接