1. 程式人生 > >Android學習筆記——簡單實現照相、錄音和錄影功能

Android學習筆記——簡單實現照相、錄音和錄影功能

       Android菜鳥第一次寫原創部落格,大神請輕噴,共同進步。

    最近剛接觸到如何實現一個簡單的照相機功能,然後又將錄音和錄影功能加了進去。ps:錄影功能相對複雜,自己實現起來比較困難,我就直接在程式裡呼叫系統的錄影功能了。以後在慢慢學習:)好,言歸正傳。

1、首先新建一個照相機專案;

2、我先新建了一個BaseActivity,用於設定相機螢幕全屏和獲取螢幕寬高畫素等功能;

程式碼:

package xw.phonemanager.activity;

import android.app.Activity;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.Window;
import android.view.WindowManager;

public class BaseActivity extends Activity {
	
	public static float screenWidth, screenHeigth;
//	private ProgressDialog mDialog;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setFullScreen(); // 設定全屏
		getScreen(); //獲取螢幕寬高值
	}
	
	// 設定全屏
	private void setFullScreen() {
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
	}
	//獲取螢幕寬高值
	private void getScreen() {

		DisplayMetrics metrics = new DisplayMetrics();
		getWindowManager().getDefaultDisplay().getMetrics(metrics);

		screenWidth =  metrics.widthPixels;
		screenHeigth =  metrics.heightPixels;
	}
	
//	public void showProgressDialog(String message){
//	}

}
3、新建一個照相的Activity(名叫PhotographActivity)繼承了BaseActivity,同時實現了OnClickListener, SurfaceHolder.Callback, PictureCallback三個介面。這時eclipse會自動提示必須實現onClick(View v)、surfaceCreated(SurfaceHolder holder)、surfaceChanged(SurfaceHolder holder, int format, int width, int height)、surfaceDestroyed(SurfaceHolder holder)、onPictureTaken(byte[] data, Camera camera)這四個方法,具體功能請看程式碼註釋;

4、新建一個佈局檔案photograph.xml,用於顯示開啟相機後預覽狀態螢幕上顯示的東西,和常見的相機介面差不多,如圖:


xml程式碼:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <SurfaceView
        android:id="@+id/camera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true" />

    <SeekBar
        android:id="@+id/seekbar_focal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="20dp"
        android:layout_alignParentRight="true"
        android:layout_marginRight="60dp"
        android:layout_alignParentTop="true"
        android:layout_marginTop="15dp" />

    <ImageView
        android:id="@+id/back_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="10dp"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:src="@drawable/back" />

    <ImageView
        android:id="@+id/flash_switch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_marginLeft="15dp"
         android:layout_alignParentBottom="true"
        android:layout_marginBottom="10dp"
        android:src="@drawable/light_auto" />

    <ImageView
        android:id="@+id/switch_camera"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/shot_btn"
        android:layout_alignTop="@+id/seekbar_focal"
        android:src="@drawable/showall" 
        android:visibility="invisible"/>

    <ImageView
        android:id="@+id/shot_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_marginRight="10dp"
        android:layout_centerVertical="true"
        android:src="@drawable/photo" />

    <Button
        android:id="@+id/record_start_btn"
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="10dp"
        android:layout_toRightOf="@id/flash_switch"
        android:layout_marginLeft="120dp"
        android:text="@string/record_start" />
    
    <Button
        android:id="@+id/record_stop_btn"
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="10dp"
        android:layout_toRightOf="@id/flash_switch"
        android:layout_marginLeft="120dp"
        android:text="@string/record_stop"
        android:visibility="invisible" />

    <Button
        android:id="@+id/video_btn"
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="10dp"
        android:layout_marginLeft="73dp"
        android:layout_toLeftOf="@id/back_btn"
        android:layout_marginRight="100dp"
        android:text="@string/video" />

</RelativeLayout>

5、開始實現相機、錄音和錄影功能,請看程式碼和註釋:

package xw.activity.photograph;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.Locale;

import xw.phonemanager.activity.BaseActivity;
import xw.phonemanager.activity.MenuActivity;
import xw.phonemanager.activity.R;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PixelFormat;
import android.hardware.Camera;
import android.hardware.Camera.Parameters;
import android.hardware.Camera.PictureCallback;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.format.DateFormat;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.Toast;

public class PhotographActivity extends BaseActivity implements
		OnClickListener, SurfaceHolder.Callback, PictureCallback {

	SurfaceView cameraPrew;// 預覽圖物件
	ImageView shot;
	ImageView flash;
	ImageView back;
	ImageView switchCamera;
	Button recordStart;
	Button recordStop;
	Button VCR;
	SeekBar focus;
	Camera camera;// 相機物件
	Camera.Parameters parameters;// 相機引數物件

	MediaRecorder mRecorder; // MediaRecorder物件,用於實現錄音和錄影

	private int cameraNum; // 攝像頭的數量
	private int cameraID;// 攝像頭的編號
	private boolean canSwitchCam = false;
	private boolean noSD;
	private int maxZoom; // 最大變焦
	private String recFileName; // 錄音音訊檔名稱
	// 設定了兩個判斷標識,當處於錄音狀態時不允許拍照或錄影,均置為false
	private boolean isPhoto = true; // 是否可以照相
	private boolean isVideo = true; // 是否可以錄影

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.photograph);

		Window window = getWindow();
		window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);// 螢幕高亮
		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);// 強制橫屏
		getWindow().setFormat(PixelFormat.TRANSPARENT);// 螢幕半透

		init();// 初始化
	}

	private void init() {
		checkSD();// 首先檢測SD卡是否存在或可用,不可用則提示使用者,避免浪費表情的尷尬:D
		cameraInit();
		prewInit();
		buttonInit();
		zoomInit();
		seekBarInit();
	}

	/**
	 * 檢查是否有SD卡
	 */
	private void checkSD() {
		String SDtatus = Environment.getExternalStorageState();
		if (SDtatus == Environment.MEDIA_MOUNTED) {
			noSD = true;
			Toast.makeText(PhotographActivity.this, "沒有SD卡或SD卡不可用",
					Toast.LENGTH_LONG).show();
		} else {
			noSD = false;
		}
	}

	/**
	 * 相機初始化
	 */
	private void cameraInit() {
		cameraNum = Camera.getNumberOfCameras();// 攝像頭的個數
		if (camera != null) { // 若不為空,釋放資源重新獲取
			camera.release();
		}
		camera = Camera.open(); // 開啟攝像頭

		// 檢測手機的攝像頭個數,若不止一個則顯示切換攝像頭的圖示,canSwitchCam置為true
		switchCamera = (ImageView) findViewById(R.id.switch_camera);
		if (cameraNum > 1) {
			switchCamera.setVisibility(View.VISIBLE);
			canSwitchCam = true;
			cameraID = 0;// 當前的攝像頭ID設為0
		}
		// 相機例項獲得相機引數集
		parameters = camera.getParameters();

	}

	/**
	 * 預覽初始化
	 */
	private void prewInit() {
		cameraPrew = (SurfaceView) findViewById(R.id.camera);// 繫結SurfaceView並例項化

		cameraPrew.getHolder().setFixedSize((int) BaseActivity.screenWidth,
				(int) BaseActivity.screenHeigth);
		cameraPrew.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
		cameraPrew.getHolder().addCallback(this);
		// 給整個螢幕的SurfaceView設定一個觸控監聽,實現全屏手動對焦
		cameraPrew.setOnTouchListener(new OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				camera.autoFocus(null);// 點選螢幕即可對焦
				return false;
			}
		});
	}

	/**
	 * 按鍵初始化,設定按鍵監聽
	 */
	private void buttonInit() {
		shot = (ImageView) findViewById(R.id.shot_btn);// 拍照鍵
		flash = (ImageView) findViewById(R.id.flash_switch);// 切換閃光燈鍵
		back = (ImageView) findViewById(R.id.back_btn);// 返回鍵
		recordStart = (Button) findViewById(R.id.record_start_btn);// 開始錄音鍵
		recordStop = (Button) findViewById(R.id.record_stop_btn);// 結束錄音鍵
		VCR = (Button) findViewById(R.id.video_btn);// 開始錄影鍵

		// 設定監聽
		shot.setOnClickListener(this);
		flash.setOnClickListener(this);
		back.setOnClickListener(this);
		switchCamera.setOnClickListener(this);
		recordStart.setOnClickListener(this);
		recordStop.setOnClickListener(this);
		VCR.setOnClickListener(this);
	}

	/**
	 * 滑動條實現變焦,初始化
	 */
	private void seekBarInit() {
		focus = (SeekBar) findViewById(R.id.seekbar_focal);
		focus.setProgress(0);
		focus.setMax(maxZoom);// 滑動條的最大值設為獲得的相機可變焦值的最大值
		focus.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {

			@Override
			public void onStopTrackingTouch(SeekBar seekBar) {
				// TODO Auto-generated method stub

			}

			@Override
			public void onStartTrackingTouch(SeekBar seekBar) {
				// TODO Auto-generated method stub

			}

			@Override
			public void onProgressChanged(SeekBar seekBar, int progress,
					boolean fromUser) {
				zoomChanged(progress);// 傳遞當前進度,變焦
			}
		});
	}

	private void zoomInit() {
		maxZoom = parameters.getMaxZoom();// 獲得相機最大變焦範圍
	}

	/**
	 * 按鍵監聽
	 */
	@Override
	public void onClick(View v) {
		int id = v.getId();
		switch (id) {
		case R.id.shot_btn:
			if (isPhoto == true) {
				takPicture();
			} else {
				Toast.makeText(this, "無響應,正在錄音", Toast.LENGTH_SHORT).show();
			}
			break;
		case R.id.flash_switch:
			flashSwitch();
			break;
		case R.id.back_btn:
			startActivity(new Intent(PhotographActivity.this,
					MenuActivity.class));
			finish();
			break;
		// 切換攝像頭
		case R.id.switch_camera:
			if (canSwitchCam == true) {// 至少有兩個才能切換
				if (camera != null) {
					camera.stopPreview();
					camera.release();
					Camera.open(++cameraID % cameraNum);// 在id:0~cameraNum之間切換
				}
			}
			break;

		case R.id.record_start_btn:
			if (noSD == true) { // 沒有SD卡
				Toast.makeText(PhotographActivity.this, "沒有SD卡或SD卡不可用",
						Toast.LENGTH_LONG).show();
				return;
			} else {
				startRec();// 開始錄音
			}
			break;

		case R.id.record_stop_btn:
			stopRec();// 結束錄音
			break;

		case R.id.video_btn:
			if (isVideo == true) {
				startVideo();// 開始錄影
			} else {
				Toast.makeText(this, "無響應,正在錄音", Toast.LENGTH_SHORT).show();
			}
			break;
		}
	}

	/**
	 * 切換閃光燈狀態,如果當前是開狀態,點選切換為關狀態, 再點選切換為自動狀態,再點選又到開狀態
	 */
	private void flashSwitch() {
		if (parameters.getFlashMode() == Parameters.FLASH_MODE_AUTO) {
			flash.setImageResource(R.drawable.light_on);
			parameters.setFlashMode(Parameters.FLASH_MODE_ON);
			camera.setParameters(parameters);
		} else if (parameters.getFlashMode() == Parameters.FLASH_MODE_ON) {
			flash.setImageResource(R.drawable.light_off);
			parameters.setFlashMode(Parameters.FLASH_MODE_OFF);
			camera.setParameters(parameters);
		} else {
			flash.setImageResource(R.drawable.light_auto);
			parameters.setFlashMode(Parameters.FLASH_MODE_AUTO);
			camera.setParameters(parameters);
		}
	}

	// 變焦
	private void zoomChanged(int zoom) {
		parameters.setZoom(zoom);
		camera.setParameters(parameters);
	}

	// 照像
	public void takPicture() {
		camera.autoFocus(null);
		camera.takePicture(null, null, this);
	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		try {
			camera.setPreviewDisplay(cameraPrew.getHolder());// 獲取相機預覽
		} catch (IOException e) {
			e.printStackTrace();
		}
		camera.getParameters();// 獲取相機引數例項
		camera.startPreview();
	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width,
			int height) {
		// TODO Auto-generated method stub

	}

	/**
	 * 退出相機介面後,釋放相機相關資源
	 */
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		if (camera != null) {
			camera.stopPreview();
			camera.release();
			camera = null;
		}
	}

	/**
	 * 拍照並儲存,自動跳轉到檢視介面
	 */
	@Override
	public void onPictureTaken(byte[] data, Camera camera) {
		if (noSD == true) { // 沒有SD卡
			Toast.makeText(PhotographActivity.this, "沒有SD卡或SD卡不可用",
					Toast.LENGTH_LONG).show();
			return;
		} else {
			Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
			String name = DateFormat.format("yyyyMMdd_hhmmss",
					Calendar.getInstance(Locale.CHINA))
					+ ".jpg";// 自定義照片名字和格式
			File file = new File("sdcard/TianTian/photo/");
			file.mkdirs(); // 建立目錄
			String fileName = "/mnt/sdcard/TianTian/photo/" + name;// 照片檔案的絕對路徑
			BufferedOutputStream bos = null;
			try {
				bos = new BufferedOutputStream(new FileOutputStream(fileName));
				bitmap.compress(Bitmap.CompressFormat.JPEG, 80, bos);// 通過緩衝流存入

				// 拍完之後跳轉到檢視剛拍的照片的一個Activity(ConfirmActivity),使用者可以選擇是否保留圖片
				ConfirmActivity.photoName = fileName;
				Uri imgUri = Uri.fromFile(new File(fileName));
				Intent intent = new Intent();
				intent.setClass(PhotographActivity.this, ConfirmActivity.class);
				intent.setData(imgUri);
				this.camera.stopPreview();// 停止預覽
				startActivity(intent);
			} catch (FileNotFoundException e) {
				e.printStackTrace();
			} finally {
				try {
					bos.close();// 關閉流
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	// 開始錄音
	private void startRec() {
		// 錄音時不能拍照和錄影
		isPhoto = false;
		isVideo = false;

		mRecorder = new MediaRecorder();
		mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);// 設定錄音員,從MIC獲得聲音
		mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);// 設定音訊輸出格式
		mRecorder.setAudioEncoder(MediaRecorder.VideoEncoder.DEFAULT);// 設定編碼格式

		String path = Environment.getExternalStorageDirectory().getPath();
		String name = DateFormat.format("yyyyMMdd_hhmmss",
				Calendar.getInstance(Locale.CHINA))
				+ ".3gp";
		File file = new File(path);
		file.mkdirs();
		recFileName = path + "/" + name;
		file = new File(recFileName);
		if (!file.exists()) {
			try {
				file.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		mRecorder.setOutputFile(recFileName);
		try {
			mRecorder.prepare();
		} catch (IllegalStateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		mRecorder.start(); // 錄音開始
		Toast.makeText(this, "錄音開始", Toast.LENGTH_SHORT).show();

		// “開始錄音”後,將按鈕換成“停止錄音”獲得焦點
		recordStart.setVisibility(View.GONE);
		recordStop.setVisibility(View.VISIBLE);
	}

	// 停止錄音
	private void stopRec() {

		isPhoto = true;
		isVideo = true;

		mRecorder.stop();
		mRecorder.release();
		mRecorder = null;
		Toast.makeText(this, "錄音結束", Toast.LENGTH_SHORT).show();

		// “停止錄音”後,將按鈕還原“開始錄音”獲得焦點
		recordStop.setVisibility(View.GONE);
		recordStart.setVisibility(View.VISIBLE);
	}

	/**
	 * 呼叫系統錄影機錄影
	 */
	private void startVideo() {
		camera.stopPreview();
		camera.release();
		camera = null;
		Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
		intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
		startActivityForResult(intent, RESULT_FIRST_USER);
		finish();
	}

	/**
	 * 重寫onKeyDown方法,當在相機介面按返回鍵退到主選單而不是直接退出程式
	 */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
			startActivity(new Intent(PhotographActivity.this,
					MenuActivity.class));
			finish();
		}
		return false;
	}

}

6、增加一個拍完照後可以檢視所拍的照片的功能,並且可以選擇保留或者刪除當前的照片,選擇後均會回到相機介面。新建一個ConfirmActivity,繼承BaseActivity。程式碼:
package xw.activity.photograph;

import java.io.File;

import xw.phonemanager.activity.BaseActivity;
import xw.phonemanager.activity.R;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.net.Uri;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageView;
import android.widget.Toast;

public class ConfirmActivity extends BaseActivity {

	public static String photoName;
	ImageView photo;// 顯示所拍的照
	ImageView save;// 保留
	ImageView delete;// 刪除

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		setContentView(R.layout.confirm);
		setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);// 強制橫屏

		photo = (ImageView) findViewById(R.id.photo_img);
		save = (ImageView) findViewById(R.id.photo_save);
		// 設定監聽,點選後會自動回到相機介面
		save.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				Toast.makeText(ConfirmActivity.this, "照片已儲存", Toast.LENGTH_LONG)
						.show();
				startActivity(new Intent(ConfirmActivity.this,
						PhotographActivity.class));
				finish();
			}
		});

		delete = (ImageView) findViewById(R.id.photo_delete);
		// 設定監聽,點選後會自動回到相機介面
		delete.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				File file = new File(photoName);
				if (file.exists()) {
					file.delete();// 刪除照片
					Toast.makeText(ConfirmActivity.this, "照片已刪除",
							Toast.LENGTH_LONG).show();
					startActivity(new Intent(ConfirmActivity.this,
							PhotographActivity.class));
					finish();
				}
			}
		});

		showPic();// 顯示所拍的照片
	}

	private void showPic() {
		Intent intent = getIntent();
		Uri originUri = intent.getData();
		photo.setImageURI(originUri);
	}

	/**
	 * 重寫onKeyDown方法,當在相機介面按返回鍵退到主選單而不是直接退出程式
	 */
	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event) {
		if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
			startActivity(new Intent(ConfirmActivity.this,
					PhotographActivity.class));
			finish();
		}
		return false;

	}
}

與之對應的xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
    <ImageView 
        android:id="@+id/photo_img"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

    <ImageView
        android:id="@+id/photo_delete"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignTop="@+id/photo_save"
        android:layout_marginRight="74dp"
        android:src="@drawable/photo_del" />

    <ImageView
        android:id="@+id/photo_save"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_marginBottom="32dp"
        android:layout_marginLeft="68dp"
        android:src="@drawable/photo_save" />

</RelativeLayout>

至此,簡單的照相、錄音和錄影功能基本實現,照相介面效果圖:




學識鄙陋,未免貽笑大方,望見諒:)

謝謝。