Android雙SurfaceView實現底部拍照,頂部繪圖

當SurfaceHolder對象的類型設置為SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS時就隻能拍照不能繪制瞭。
為瞭既能通過SurfaceView拍照又能在上面繪制圖形,可以通過雙SurfaceView層疊的變通方式如下:
用於繪制的SurfaceView,使其透明並位於頂部:
[java]
package com.test; 
 
import android.content.Context; 
import android.graphics.Bitmap; 
import android.graphics.BitmapFactory; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Paint; 
import android.graphics.PixelFormat; 
import android.graphics.Rect; 
import android.graphics.Paint.Style; 
import android.util.AttributeSet; 
import android.view.SurfaceHolder; 
import android.view.SurfaceView; 
 
public class SVDraw extends SurfaceView implements SurfaceHolder.Callback { 
 
    private Bitmap bmp; 
    private String imgPath = ""; 
    protected SurfaceHolder sh; // 專門用於控制surfaceView的 
    private int width; 
    private int height; 
 
    // XML文件解析需要調用View的構造函數View(Context , AttributeSet) 
    // 因此自定義SurfaceView中也需要該構造函數 
    public SVDraw(Context context, AttributeSet attrs) { 
        super(context, attrs); 
        // TODO Auto-generated constructor stub 
        sh = getHolder(); 
        sh.addCallback(this); 
        sh.setFormat(PixelFormat.TRANSPARENT); // 設置為透明 
        setZOrderOnTop(true);// 設置為頂端 
    } 
 
    @Override 
    public void surfaceChanged(SurfaceHolder arg0, int arg1, int w, int h) { 
        // TODO Auto-generated method stub 
        width = w; 
        height = h; 
    } 
 
    @Override 
    public void surfaceCreated(SurfaceHolder arg0) { 
        // TODO Auto-generated method stub 
 
    } 
 
    @Override 
    public void surfaceDestroyed(SurfaceHolder arg0) { 
        // TODO Auto-generated method stub 
 
    } 
 
    void clearDraw() { 
 
        Canvas canvas = sh.lockCanvas(); 
        canvas.drawColor(Color.BLUE);// 清除畫佈 
        sh.unlockCanvasAndPost(canvas); 
    } 
 
    /**
     * 繪制
     */ 
    public void doDraw() { 
        if (bmp != null) { 
            Canvas canvas = sh.lockCanvas(); 
            canvas.drawColor(Color.TRANSPARENT);// 這裡是繪制背景 
            Paint p = new Paint(); // 筆觸 
            p.setAntiAlias(true); // 反鋸齒 
            p.setColor(Color.RED); 
            p.setStyle(Style.STROKE); 
            canvas.drawBitmap(bmp, 0, 0, p); 
            canvas.drawLine(width / 2 – 100, 0, width / 2 – 100, height, p); 
            canvas.drawLine(width / 2 + 100, 0, width / 2 + 100, height, p); 
            // ———————— 畫邊框——————— 
            Rect rec = canvas.getClipBounds(); 
            rec.bottom–; 
            rec.right–; 
            p.setColor(Color.GRAY); // 顏色 
            p.setStrokeWidth(5); 
            canvas.drawRect(rec, p); 
            // 提交繪制 
            sh.unlockCanvasAndPost(canvas); 
        } 
 
    } 
 
    public void drawLine() { 
 
        Canvas canvas = sh.lockCanvas(); 
 
        canvas.drawColor(Color.TRANSPARENT);// 這裡是繪制背景 
        Paint p = new Paint(); // 筆觸 
        p.setAntiAlias(true); // 反鋸齒 
        p.setColor(Color.RED); 
        p.setStyle(Style.STROKE); 
        canvas.drawLine(width / 2 – 100, 0, width / 2 – 100, height, p); 
        canvas.drawLine(width / 2 + 100, 0, width / 2 + 100, height, p); 
 
        // 提交繪制 
        sh.unlockCanvasAndPost(canvas); 
    } 
 
    public String getImgPath() { 
        return imgPath; 
    } 
 
    public void setImgPath(String imgPath) { 
        this.imgPath = imgPath; 
        // 根據路徑載入目標圖像 
        bmp = BitmapFactory.decodeFile(imgPath); 
    } 
 

用於在SurfaceView(使其位於繪制SurfaceView底部)上拍照及預覽的Activity:
[java] 
package com.test; 
 
import java.io.File; 
import java.io.FileNotFoundException; 
import java.io.FileOutputStream; 
import java.io.IOException; 
import java.io.OutputStream; 
import java.text.SimpleDateFormat; 
import java.util.Date; 
import java.util.Iterator; 
import java.util.List; 
 
import android.app.Activity; 
import android.content.ContentValues; 
import android.content.pm.ActivityInfo; 
import android.graphics.Bitmap; 
import android.graphics.Bitmap.Config; 
import android.graphics.BitmapFactory; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Paint; 
import android.graphics.Typeface; 
 
import android.graphics.PixelFormat; 
import android.hardware.Camera; 
import android.hardware.Camera.PictureCallback; 
import android.hardware.Camera.ShutterCallback; 
import android.hardware.Camera.Size; 
 
import android.media.AudioManager; 
import android.media.ToneGenerator; 
import android.net.Uri; 
import android.os.Bundle; 
import android.os.Environment; 
import android.provider.MediaStore; 
import android.provider.SyncStateContract.Constants; 
import android.util.Log; 
import android.view.SurfaceHolder; 
import android.view.SurfaceView; 
import android.view.View; 
import android.view.Window; 
import android.view.WindowManager; 
import android.widget.Button; 
 
public class SurfaceViewDraw extends Activity implements 
        SurfaceHolder.Callback, Camera.PictureCallback { 
    /** Called when the activity is first created. */ 
    private SVDraw svDraw = null; 
    private SurfaceView svCamera = null; 
    protected SurfaceHolder mSurfaceHolder; 
 
    private Button btnClear; 
    private Button btnOpen; 
    private Button btnClose; 
    private Button btnTakePic; 
    private Button btnDraw; 
 
    private Camera mCamera; // 這個是hardware的Camera對象 
    private boolean isOpen = false;// 相機是否打開 
    private ToneGenerator tone; 
    private String imgPath; 
 
    private int width; 
    private int height; 
 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        hideStatusBar(); 
        setContentView(R.layout.main); 
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);// 強制為橫屏 
 
        svDraw = (com.test.SVDraw) findViewById(R.id.svDraw); 
        svCamera = (SurfaceView) findViewById(R.id.svCamera); 
 
        btnClear = (Button) findViewById(R.id.btnClear); 
        btnOpen = (Button) findViewById(R.id.btnOpen); 
        btnClose = (Button) findViewById(R.id.btnClose); 
        btnTakePic = (Button) findViewById(R.id.btnTakePic); 
        btnDraw = (Button) findViewById(R.id.btnDraw); 
 
        btnClear.setOnClickListener(new ClickEvent()); 
        btnOpen.setOnClickListener(new ClickEvent()); 
        btnClose.setOnClickListener(new ClickEvent()); 
        btnTakePic.setOnClickListener(new ClickEvent()); 
        btnDraw.setOnClickListener(new ClickEvent()); 
 
        mSurfaceHolder = svCamera.getHolder(); 
        mSurfaceHolder.addCallback(this); 
        // 當設置為SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS後就不能繪圖瞭 
        mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); 
 
    } 
 
    class ClickEvent implements View.OnClickListener { 
 
        @Override 
        public void onClick(View v) { 
            // TODO Auto-generated method stub 
            if (v == btnClear) { 
 
                stopPreview(); // 停止預覽後清屏速度會快一點 
                svDraw.setVisibility(View.INVISIBLE); 
                startPreview();// 清屏後啟動預覽 
 
            } else if (v == btnOpen) { 
 
                initCamera(); 
 
            } else if (v == btnClose) { 
 
                closeCamera(); 
 
            } else if (v == btnTakePic) { 
                if (isOpen) { 
                    startPreview();// 防止異常 
                    mCamera.takePicture(mShutterCallback, null, null, 
                            mjpegCallback); 
                    svDraw.setVisibility(View.VISIBLE); 
                    svDraw.drawLine();// 拍照後繪制測線 
                } 
            } else if (v == btnDraw) { 
 
                svDraw.setVisibility(View.VISIBLE); 
                svDraw.doDraw(); 
            } 
        } 
 
    } 
 
    ShutterCallback mShutterCallback = new ShutterCallback() { 
        @Override 
        public void onShutter() { 
            // TODO Auto-generated method stub 
            if (tone == null) 
                // 發出提示用戶的聲音 
                tone = new ToneGenerator(AudioManager.STREAM_MUSIC, 
                        ToneGenerator.MAX_VOLUME); 
            tone.startTone(ToneGenerator.TONE_PROP_BEEP); 
 
        } 
    }; 
    /**
     * Jpeg格式壓縮
     */ 
    PictureCallback mjpegCallback = new PictureCallback() { 
        @Override 
        // 取得拍照圖片 
        public void onPictureTaken(byte[] data, Camera camera) { 
            // TODO Auto-generated method stub 
            // 拍照前關閉預覽 
            mCamera.stopPreview(); 
            // 取得圖像路徑 
            imgPath = saveFile2(data); 
            svDraw.setImgPath(imgPath); 
 
        } 
 
    }; 
 
    /**
     * draw information on the picture
     * 
     * @param imgPath
     */ 
    public void drawInfo(String imgPath) { 
 
        Bitmap bmp = BitmapFactory.decodeFile(imgPath); 
 
        if (bmp != null) { 
            Bitmap drawBmp = Bitmap.createBitmap(640, 480, Config.ARGB_8888); 
            Canvas c = new Canvas(drawBmp); 
            Paint p = new Paint(); 
            c.drawBitmap(bmp, 0, 0, p); 
            String familyName = "Arial"; 
            Typeface font = Typeface.create(familyName, Typeface.NORMAL); 
            p.setColor(Color.RED); 
            p.setTypeface(font); 
            p.setTextSize(20); 
            p.setStyle(Paint.Style.STROKE); 
            SimpleDateFormat dateFormat = new SimpleDateFormat( 
                    "yyyy-MM-dd hh:mm:ss"); 
            String strDate = dateFormat.format(new Date()); 
            c.drawText(strDate, 10, 30, p); 
 
            try { 
                saveBmp(drawBmp, imgPath); 
            } catch (IOException e) { 
                // TODO Auto-generated catch block 
                e.printStackTrace(); 
            } 
        } 
 
    } 
 
    /**
     * save bmp as jpg by path
     * 
     * @param bmpPath
     * @param bmp
     * @throws IOException
     */ 
    public void saveBmp(Bitmap bmp, String fileName) throws IOException { 
 
        File f = new File(fileName); 
        f.createNewFile(); 
        FileOutputStream fOut = null; 
        try { 
            fOut = new FileOutputStream(f); 
        } catch (FileNotFoundException e) { 
            e.printStackTrace(); 
        } 
 
        bmp.compress(Bitmap.CompressFormat.JPEG, 100, fOut); 
        try { 
            fOut.flush(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
        try { 
            fOut.close(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
 
    /**
     * return imgFilePath
     * 
     * @param data
     * @return
     */ 
    private String saveFile2(byte[] data) { 
        File imgFileDir = getDir(); 
        if (!imgFileDir.exists() && !imgFileDir.mkdirs()) { 
            Log.v("directory", "Can't create directory to save image."); 
            return null; 
        } 
        // 圖像名稱 
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddhhmmss"); 
        String strDate = dateFormat.format(new Date()); 
        String imgFileName = "img_" + strDate + ".jpg"; 
        // 圖像路徑 
        String imgFilePath = imgFileDir.getPath() + File.separator 
                + imgFileName; 
        File imgFile = new File(imgFilePath); 
        try { 
            FileOutputStream fos = new FileOutputStream(imgFile); 
            fos.write(data); 
            fos.close(); 
            Log.v("directory", "New Image saved:" + imgFile); 
 
        } catch (Exception error) { 
            Log.d(Constants.ACCOUNT_NAME, 
                    imgFileName + " not saved: " + error.getMessage()); 
 
        } 
        //繪制拍照日期等 
        drawInfo(imgFilePath); 
        return imgFilePath; 
    } 
 
    /**
     * 
     * @return
     */ 
    private File getDir() { 
        File sdDir = Environment 
                .getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); 
        // 創建圖像需要保存的文件夾 
        return new File(sdDir, "Photo"); 
 
    } 
 
    @Override 
    public void onPictureTaken(byte[] data, Camera camera) { 
        // TODO Auto-generated method stub 
        // data是一個原始的JPEG圖像數據, 
        // 在這裡我們可以存儲圖片,很顯然可以采用MediaStore 
        // 註意保存圖片後,再次調用stopPreview()停止預覽,等待測量 
        Uri imageUri = this.getContentResolver().insert( 
                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 
                new ContentValues()); 
        try { 
            OutputStream os = this.getContentResolver().openOutputStream( 
                    imageUri); 
            os.write(data); 
            os.flush(); 
            os.close(); 
        } catch (Exception e) { 
            // TODO: handle exception 
            e.printStackTrace(); 
        } 
        // 拍照後停止預覽 
        mCamera.stopPreview(); 
    } 
 
    @Override 
    public void surfaceCreated(SurfaceHolder holder) { 
        // TODO Auto-generated method stub 
 
    } 
 
    @Override 
    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { 
        // TODO Auto-generated method stub 
        width = w; 
        height = h; 
    } 
 
    @Override 
    public void surfaceDestroyed(SurfaceHolder holder) { 
        // TODO Auto-generated method stub 
 
    } 
 
    /**
     * 關閉相機
     */ 
    public void closeCamera() { 
        if (isOpen) { 
            mCamera.stopPreview(); 
            mCamera.release(); 
            mCamera = null; 
            isOpen = false; 
        } 
    } 
 
    /**
     * 停止拍照預覽
     */ 
    public void stopPreview() { 
        if (isOpen) { 
            mCamera.stopPreview(); 
        } 
    } 
 
    /**
     * 啟動拍照預覽
     */ 
    public void startPreview() { 
        if (isOpen) { 
            mCamera.startPreview(); 
        } 
    } 
 
    /**
     * 初始化相機
     */ 
    public void initCamera() { 
        if (!isOpen) { 
            mCamera = Camera.open(); 
        } 
        if (mCamera != null && !isOpen) { 
            try { 
                Camera.Parameters mParameters = mCamera.getParameters(); 
                mParameters.setPictureFormat(PixelFormat.JPEG); // 設置照片格式 
                List<Size> sizes = mParameters.getSupportedPreviewSizes(); 
                Size optimalSize = getOptimalPreviewSize(sizes, width, height); 
                mParameters.setPreviewSize(optimalSize.width, 
                        optimalSize.height); // 大小 
                mParameters.setPictureSize(optimalSize.width, 
                        optimalSize.height); 
 
                mParameters.set("jpeg-quality", 100);// 照片質量 
                // 首先獲取系統設備支持的所有顏色特效,有復合我們的,則設置;否則不設置 
                List<String> colorEffects = mParameters 
                        .getSupportedColorEffects(); 
                Iterator<String> colorItor = colorEffects.iterator(); 
                while (colorItor.hasNext()) { 
                    String currColor = colorItor.next(); 
                    if (currColor.equals(Camera.Parameters.EFFECT_SOLARIZE)) { 
                        mParameters 
                                .setColorEffect(Camera.Parameters.EFFECT_SOLARIZE); 
                        break; 
                    } 
                } 
                mCamera.setParameters(mParameters); 
                mCamera.setPreviewDisplay(mSurfaceHolder); 
                mCamera.startPreview(); 
            } catch (IOException e) { 
                // TODO Auto-generated catch block 
                e.printStackTrace(); 
            } 
            isOpen = true; 
        } 
    } 
 
    private Size getOptimalPreviewSize(List<Size> sizes, int w, int h) { 
        final double ASPECT_TOLERANCE = 0.05; 
        double targetRatio = (double) w / h; 
        if (sizes == null) 
            return null; 
 
        Size optimalSize = null; 
        double minDiff = Double.MAX_VALUE; 
 
        int targetHeight = h; 
 
        // Try to find an size match aspect ratio and size 
        for (Size size : sizes) { 
            double ratio = (double) size.width / size.height; 
            if (Math.abs(ratio – targetRatio) > ASPECT_TOLERANCE) 
                continue; 
            if (Math.abs(size.height – targetHeight) < minDiff) { 
                optimalSize = size; 
                minDiff = Math.abs(size.height – targetHeight); 
            } 
        } 
 
        // Cannot find the one match the aspect ratio, ignore the requirement 
        if (optimalSize == null) { 
            minDiff = Double.MAX_VALUE; 
            for (Size size : sizes) { 
                if (Math.abs(size.height – targetHeight) < minDiff) { 
                    optimalSize = size; 
                    minDiff = Math.abs(size.height – targetHeight); 
                } 
            } 
        } 
        return optimalSize; 
    } 
 
    @Override 
    protected void onDestroy() { 
        // TODO Auto-generated method stub 
        super.onDestroy(); 
        if (isOpen) { 
            closeCamera(); 
        } 
    } 
 
    // 在 Activity.setCurrentView()之前調用 
    public void hideStatusBar() { 
        // 隱藏標題 
        requestWindowFeature(Window.FEATURE_NO_TITLE); 
        // 定義全屏參數 
        int flag = WindowManager.LayoutParams.FLAG_FULLSCREEN; 
        // 獲得窗口對象 
        Window curWindow = this.getWindow(); 
        // 設置Flag標示 
        curWindow.setFlags(flag, flag); 
    } 

主界面main.xml:
[html] 
<?xml version="1.0" encoding="utf-8"?> 
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:layout_width="fill_parent" 
    android:layout_height="fill_parent" 
    android:orientation="horizontal" > 
   <!– 對於自定義控件要指明的控件的包名與空間名,系統自帶的控件不需要指定包名 –> 
   <FrameLayout 
       android:layout_width="640dip"  
       android:layout_height="480dip"  
       android:orientation="vertical" > 
    <SurfaceView 
      android:id="@+id/svCamera" 
      android:layout_width="fill_parent"  
      android:layout_height="fill_parent"/> 
     
    <com.test.SVDraw 
     android:id="@+id/svDraw" 
     android:layout_width="fill_parent"  
     android:layout_height="fill_parent"/> 
     
   </FrameLayout> 
     
    <LinearLayout  
      android:id="@+id/LinearLayout01" 
      android:layout_width="158dip"  
      android:layout_height="fill_parent" 
      android:layout_marginLeft="1dip" 
      android:layout_marginRight="1dip" 
      android:orientation="vertical" 
      android:background="@drawable/main_right_bg"> 
        <Button  
          android:id="@+id/btnOpen"  
          android:layout_width="fill_parent" 
          android:layout_height="wrap_content"  
          android:layout_marginTop="10dip" 
          android:text="打開相機"/> 
         <Button  
          android:id="@+id/btnClose"  
          android:layout_width="fill_parent" 
          android:layout_height="wrap_content"  
          android:text="關閉相機"/> 
         <Button  
            android:id="@+id/btnTakePic"  
         android:layout_width="fill_parent" 
         android:layout_height="wrap_content"  
         android:text="拍照"   /> 
        <Button  
         android:id="@+id/btnClear"  
         android:layout_width="fill_parent" 
         android:layout_height="wrap_content"  
         android:text="預覽"/>    
        <Button  
         android:id="@+id/btnDraw"  
         android:layout_width="fill_parent" 
         android:layout_height="wrap_content"  
         android:text="繪制"/>       
      
    </LinearLayout> 
</LinearLayout> 
在res下新建文件夾drawable,並在其下面新建面板背景main_right_bg.xml:
[html]
<?xml version="1.0" encoding="utf-8"?> 
<shape xmlns:android="http://schemas.android.com/apk/res/android" 
    android:shape="rectangle"> 
  <gradient 
      android:startColor = "#666666" 
      android:centerColor="#000FFF" 
      android:endColor = "#666666"   
      android:angle = "270"/>  
  <corners  
      android:radius="4dip"/> 
   
</shape> 
AndroidManifest.xml:
[html] view plaincopy
<?xml version="1.0" encoding="utf-8"?> 
<manifest xmlns:android="http://schemas.android.com/apk/res/android" 
    package="com.test" 
    android:versionCode="1" 
    android:versionName="1.0" > 
 
    <uses-sdk android:minSdkVersion="8" /> 
     
<!– 照相機權限 –> 
<uses-permission android:name="android.permission.CAMERA" /> 
<uses-feature android:name="android.hardware.camera" /> 
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" /> 
<!– 在SDCard中創建與刪除文件權限 –> 
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/> 
<!– 往SDCard寫入數據權限 –> 
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  
 
    <application 
        android:icon="@drawable/ic_launcher" 
        android:label="@string/app_name" > 
        <activity 
            android:name=".SurfaceViewDraw" 
            android:label="@string/app_name" > 
            <intent-filter> 
                <action android:name="android.intent.action.MAIN" /> 
 
                <category android:name="android.intent.category.LAUNCHER" /> 
            </intent-filter> 
        </activity> 
    </application> 
 
</manifest> 
作者:xinzheng_wang

發佈留言