【问题标题】:Cannot draw on surfaceview because Canvas is null无法在surfaceview上绘图,因为Canvas为空
【发布时间】:2015-02-22 15:54:30
【问题描述】:

我正在制作一个像 Snapchat 这样的应用程序,您可以在其中拍照、预览并在预览期间对其进行绘图。为了实现这一点,我使用了表面视图。

这是我的拍照和绘图代码

CameraActivity.java:

public class CameraActivity extends Activity {
    public static final String TAG = CameraActivity.class.getSimpleName();
    private android.hardware.Camera mCamera;
    private CameraPreview mCameraPreview;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);

        mCamera = getCameraInstance();
        mCameraPreview = new CameraPreview(this, mCamera);
        FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
        preview.addView(mCameraPreview);

        Button captureButton = (Button) findViewById(R.id.button_capture);
        captureButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mCamera.takePicture(null, null, mPicture);
            }
        });
    }

    /**
     * Helper method to access the camera returns null if it cannot get the
     * camera or does not exist
     *
     * @return
     */
    private Camera getCameraInstance() {
        Camera camera = null;
        try {
            camera = Camera.open();
        } catch (Exception e) {
            // cannot get camera or does not exist
        }
        return camera;
    }


    PictureCallback mPicture = new PictureCallback() {
        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            File pictureFile = getOutputMediaFile();
            if (pictureFile == null) {
                return;
            }
            try {
                FileOutputStream fos = new FileOutputStream(pictureFile);
                fos.write(data);
                fos.close();
            } catch (FileNotFoundException e) {

            } catch (IOException e) {
            }
        }
    };

    private static File getOutputMediaFile() {
        File mediaStorageDir = new File(
                Environment
                        .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
                "MyCameraApp");
        if (!mediaStorageDir.exists()) {
            if (!mediaStorageDir.mkdirs()) {
                Log.d("MyCameraApp", "failed to create directory");
                return null;
            }
        }
        // Create a media file name
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
                .format(new Date());
        File mediaFile;
        mediaFile = new File(mediaStorageDir.getPath() + File.separator
                + "IMG_" + timeStamp + ".jpg");

        return mediaFile;
    }
}

CameraPreview.java:

public class CameraPreview extends SurfaceView implements
        SurfaceHolder.Callback {
    public static final String TAG = CameraPreview.class.getSimpleName();
    private SurfaceHolder mSurfaceHolder;
    private android.hardware.Camera mCamera;

    // Constructor that obtains context and camera
    @SuppressWarnings("deprecation")
    public CameraPreview(Context context, Camera camera) {
        super(context);
        this.mCamera = camera;
        this.mSurfaceHolder = this.getHolder();
        this.mSurfaceHolder.addCallback(this);
        this.mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    }

    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        try {
            mCamera.setPreviewDisplay(surfaceHolder);
            mCamera.startPreview();
            tryDrawing(surfaceHolder);

        } catch (IOException e) {
            // left blank for now
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        mCamera.stopPreview();
        mCamera.release();
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int format,
                               int width, int height) {
        // start preview with new settings
        try {
            mCamera.setPreviewDisplay(surfaceHolder);
            mCamera.startPreview();
            tryDrawing(surfaceHolder);
        } catch (Exception e) {
            // intentionally left blank for a test
        }
    }

    private void tryDrawing(SurfaceHolder surfaceHolder) {
        Log.i(TAG, "Trying to draw...");

        Canvas canvas = surfaceHolder.lockCanvas();
        if (canvas == null) {
            Log.e(TAG, "Cannot draw onto the canvas as it's null");
        } else {
            drawMyStuff(canvas);
            surfaceHolder.unlockCanvasAndPost(canvas);
        }
    }

    private void drawMyStuff(final Canvas canvas) {
        Random random = new Random();
        Log.i(TAG, "Drawing...");
        canvas.drawRGB(255, 128, 128);
    }
}

这非常适合拍照和预览,但是,当我尝试在画布上实际绘制时(也就是运行 CameraPreview.trydrawing() 方法),我收到此错误:

02-22 15:36:59.861    4261-4261/com.johncorser.selfiesnap I/CameraPreview﹕ Trying to draw...
02-22 15:36:59.861    4261-4261/com.johncorser.selfiesnap E/SurfaceHolder﹕ Exception locking surface
    java.lang.IllegalArgumentException
            at android.view.Surface.nativeLockCanvas(Native Method)
            at android.view.Surface.lockCanvas(Surface.java:243)
            at android.view.SurfaceView$4.internalLockCanvas(SurfaceView.java:814)
            at android.view.SurfaceView$4.lockCanvas(SurfaceView.java:782)
            at com.johncorser.selfiesnap.CameraPreview.tryDrawing(CameraPreview.java:63)
            at com.johncorser.selfiesnap.CameraPreview.surfaceChanged(CameraPreview.java:54)
            at android.view.SurfaceView.updateWindow(SurfaceView.java:583)
            at android.view.SurfaceView.access$000(SurfaceView.java:86)
            at android.view.SurfaceView$3.onPreDraw(SurfaceView.java:175)
            at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:847)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1867)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:996)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5600)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
            at android.view.Choreographer.doCallbacks(Choreographer.java:574)
            at android.view.Choreographer.doFrame(Choreographer.java:544)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5001)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
            at dalvik.system.NativeStart.main(Native Method)
02-22 15:36:59.961    4261-4261/com.johncorser.selfiesnap E/CameraPreview﹕ Cannot draw onto the canvas as it's null

我在这里查看了堆栈溢出,基本上发现问题与相机锁定表面视图的事实有关,因此我无法锁定画布(因此它为空)。所以我想知道的是,有什么方法可以克服这个问题并能够在图像上绘图吗?

理想情况下,在图像上绘制的任何内容也将保存在文件中,但我也可以添加一个“保存”按钮来处理最有可能的逻辑?

【问题讨论】:

    标签: android android-camera android-canvas surfaceview


    【解决方案1】:

    在上面再放一个CustomView并在上面画图,然后就可以通过这个方法合并那两张图片了

    public Bitmap mergeBitmaps(Bitmapclass bitmap1,RenderView Bitmapextends bitmap2)View {
    Bitmap mergedBitmap =private Bitmap.createBitmap(bitmap1.getWidth(), bitmap1.getHeight(), bitmap1.getConfig());
    Canvas canvas = newprivate Canvas(mergedBitmap);
    canvas.drawBitmap(bitmap1, 0, 0, null);
    canvas.drawBitmap(bitmap2, 0, 0, null);
    return mergedBitmap;
    

    }

    这是自定义视图的实现

    public class RenderView extends View {
    private Bitmap cachedBitmap;
    private Canvas cachedCanvas;
    private Paint linePaint;
    
    private boolean isClicked;
    private float lastX;
    private float lastY;
    
    public RenderView(Context context) {
        super(context);
    }
    
    public RenderView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    
    public RenderView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
        int width = getWidth();
        int height = getHeight();
        if(cachedBitmap == null && width > 0 && height > 0) {
            cachedBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
            cachedCanvas = new Canvas(cachedBitmap);
            linePaint = new Paint();
            linePaint.setStyle(Paint.Style.STROKE);
            linePaint.setStrokeWidth(2);
            linePaint.setColor(Color.parseColor("#000000"));
        }
        canvas.drawBitmap(cachedBitmap, 0, 0, null);
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                isClicked = true;
                break;
    
            case MotionEvent.ACTION_MOVE:
                if(isClicked && cachedCanvas != null) {
                    cachedCanvas.drawLine(lastX, lastY, event.getX(), event.getY(), linePaint);
                    invalidate();
                }
                break;
    
            case MotionEvent.ACTION_UP:
                isClicked = false;
                break;
        }
        lastX = event.getX();
        lastY = event.getY();
        return true;
    }
    

    }

    以这种方式通过XML添加这个视图

        <com.myapp.RenderView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/renderView"
        android:clickable="true"/>
    

    从摄像头捕获位图后,您可以从该类中获取cachedBitmap,并使用mergeBitmaps方法将这两个位图合并。

    【讨论】:

    • 您应该解释如何将一个 SurfaceView 放在另一个之上(或使用自定义 View 来解决问题)。简单地创建具有相同 Z 顺序的第二个 SurfaceView 通常是行不通的。
    • 我想要更多的解释,我尝试过这个例子,但它没有按我的预期工作。我可能会误解某些东西或忘记一步,我是 Android 的新手
    • 如何将 RenderView 连接到我现有的类?
    • 不知道你要画什么,比如你想画一条线,用canvas.drawLine方法,然后调用invalidate方法,调用ondraw方法,视图重绘
    • 您还应该覆盖 onTouchEvent ,您可以在其中绘制缓存的Canvas
    猜你喜欢
    • 2012-06-11
    • 1970-01-01
    • 1970-01-01
    • 2013-05-21
    • 1970-01-01
    • 1970-01-01
    • 2016-07-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多