【Android 開發】SufaceView自定義相機拍照
阿新 • • 發佈:2019-02-01
前段時間寫了關於一篇關於呼叫系統相機的部落格,如果需要呼叫系統相機和截圖可以看一看這篇部落格:Android學習之呼叫系統相機拍照、截圖並儲存最近發現不同手機,呼叫系統相機效果不太好,,所以學習Android 的相機原理,自定義了一個Android相機。看了這篇部落格,相信大家都會寫一個自己的相機。
我對比了一下小猿搜題、學霸君、作業幫、阿凡題這四款app的拍照功能,個人感覺小猿搜題的體驗要好一些,因為從主介面進入拍照介面,連個介面沒有一個旋轉的過渡,而學霸君就有一個過渡,有一絲絲的影響體驗。也就是說學霸君的拍照介面是橫屏的,在activity的onCreate方法裡面呼叫了setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)來設定全屏,而切換介面的時候又從豎屏切換為橫屏,就會有個過渡的效果,影響了體驗。
專案地址:https://github.com/Terrybthvi/MyCamera
這個專案仿照小猿搜題app,首先我們將相機佈局最低層是顯示攝像頭獲取到的影象,上層是網格線,聚焦圖示等。
效果圖如下所示:
1、繪製網格參考線
繪製網格參考線就是繪製拍照時螢幕中的豎線和直線,程式碼如下所示:
2、自定義相機程式碼
這裡我們要建立一個SurfaceView,將攝像頭獲取到的影象放到SurfsaceView中顯示。主要方法如下:
1、繼承SurfaceView,定義變數,初始化類。
2、實現三個方法對sufarceView監聽。
- surfaceCreated();
- surfaceChanged();
- surfaceDestroyed();
3、獲取相機例項
4、設定相機監聽
5、增加自動聚焦 和點選螢幕聚焦
6、拍照以及設定圖片格式
3、為相機新增聚焦功能
4、Activity呼叫相機
actiity中註冊了SensorEventListener,也就是使用感測器監聽使用者手機的移動,如果有一定距離的移動,則自動聚焦,這樣體驗好一點。<span style="font-family:SimHei;font-size:18px;">package com.example.terry.mycamera; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.text.format.DateFormat; import android.util.Log; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.animation.LinearInterpolator; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; import com.example.terry.mycamera.Crop.CropImageView; import com.example.terry.mycamera.Crop.CropperImage; import com.example.terry.mycamera.camare.CameraPreview; import com.example.terry.mycamera.camare.FocusView; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; /** * @Class: TakePhotoActivity * @Description: 拍照介面 * @author: leiqi(http://blog.csdn.net/u013132758) * @Date: 2016/3/15 */ public class TakePhotoActivity extends Activity implements CameraPreview.OnCameraStatusListener, SensorEventListener { private static final String TAG = "TakePhoteActivity"; public static final Uri IMAGE_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; public static final String PATH = Environment.getExternalStorageDirectory() .toString() + "/AndroidMedia/"; CameraPreview mCameraPreview; CropImageView mCropImageView; RelativeLayout mTakePhotoLayout; LinearLayout mCropperLayout; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // 設定橫屏 // setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); // 設定全屏 requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_take_photo); // Initialize components of the app mCropImageView = (CropImageView) findViewById(R.id.CropImageView); mCameraPreview = (CameraPreview) findViewById(R.id.cameraPreview); FocusView focusView = (FocusView) findViewById(R.id.view_focus); mTakePhotoLayout = (RelativeLayout) findViewById(R.id.take_photo_layout); mCropperLayout = (LinearLayout) findViewById(R.id.cropper_layout); mCameraPreview.setFocusView(focusView); mCameraPreview.setOnCameraStatusListener(this); mCropImageView.setGuidelines(2); mSensorManager = (SensorManager) getSystemService(Context. SENSOR_SERVICE); mAccel = mSensorManager.getDefaultSensor(Sensor. TYPE_ACCELEROMETER); } boolean isRotated = false; @TargetApi(Build.VERSION_CODES.HONEYCOMB) @Override protected void onResume() { super.onResume(); if(!isRotated) { TextView hint_tv = (TextView) findViewById(R.id.hint); ObjectAnimator animator = ObjectAnimator.ofFloat(hint_tv, "rotation", 0f, 90f); animator.setStartDelay(800); animator.setDuration(1000); animator.setInterpolator(new LinearInterpolator()); animator.start(); View view = findViewById(R.id.crop_hint); AnimatorSet animSet = new AnimatorSet(); ObjectAnimator animator1 = ObjectAnimator.ofFloat(view, "rotation", 0f, 90f); ObjectAnimator moveIn = ObjectAnimator.ofFloat(view, "translationX", 0f, -50f); animSet.play(animator1).before(moveIn); animSet.setDuration(10); animSet.start(); isRotated = true; } mSensorManager.registerListener(this, mAccel, SensorManager.SENSOR_DELAY_UI); } @Override protected void onPause() { super.onPause(); mSensorManager.unregisterListener(this); } @Override public void onConfigurationChanged(Configuration newConfig) { Log.e(TAG, "onConfigurationChanged"); super.onConfigurationChanged(newConfig); } public void takePhoto(View view) { if(mCameraPreview != null) { mCameraPreview.takePicture(); } } public void openlight(View view) { if (mCameraPreview != null) { mCameraPreview.openLight(); view.setVisibility(View.GONE); View v = findViewById(R.id.nolight); v.setVisibility(View.VISIBLE); } } public void offlight(View v) { if (mCameraPreview != null) { mCameraPreview.offLight(); v.setVisibility(View.GONE); View view = findViewById(R.id.light); view.setVisibility(View.VISIBLE); } } public void close(View view) { finish(); } /** * 關閉截圖介面 * @param view */ public void closeCropper(View view) { showTakePhotoLayout(); } /** * 開始截圖,並儲存圖片 * @param view */ public void startCropper(View view) { //獲取截圖並旋轉90度 CropperImage cropperImage = mCropImageView.getCroppedImage(); Log.e(TAG, cropperImage.getX() + "," + cropperImage.getY()); Log.e(TAG, cropperImage.getWidth() + "," + cropperImage.getHeight()); Bitmap bitmap = Utils.rotate(cropperImage.getBitmap(), -90); // Bitmap bitmap = mCropImageView.getCroppedImage(); // 系統時間 long dateTaken = System.currentTimeMillis(); // 影象名稱 String filename = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken) .toString() + ".jpg"; Uri uri = insertImage(getContentResolver(), filename, dateTaken, PATH, filename, bitmap, null); cropperImage.getBitmap().recycle(); cropperImage.setBitmap(null); Intent intent = new Intent(this, PreviewActivity.class); intent.setData(uri); intent.putExtra("path", PATH + filename); intent.putExtra("width", bitmap.getWidth()); intent.putExtra("height", bitmap.getHeight()); intent.putExtra("cropperImage", cropperImage); startActivity(intent); bitmap.recycle(); finish(); super.overridePendingTransition(R.anim.fade_in, R.anim.fade_out); // doAnimation(cropperImage); } /** * 拍照成功後回撥 * 儲存圖片並顯示截圖介面 * @param data */ @Override public void onCameraStopped(byte[] data) { Log.i("TAG", "==onCameraStopped=="); // 建立影象 Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); // 系統時間 long dateTaken = System.currentTimeMillis(); // 影象名稱 String filename = DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken) .toString() + ".jpg"; // 儲存影象(PATH目錄) Uri source = insertImage(getContentResolver(), filename, dateTaken, PATH, filename, bitmap, data); //準備截圖 try { mCropImageView.setImageBitmap(MediaStore.Images.Media.getBitmap(this.getContentResolver(), source)); // mCropImageView.rotateImage(90); } catch (IOException e) { Log.e(TAG, e.getMessage()); } showCropperLayout(); } /** * 儲存影象並將資訊新增入媒體資料庫 */ private Uri insertImage(ContentResolver cr, String name, long dateTaken, String directory, String filename, Bitmap source, byte[] jpegData) { OutputStream outputStream = null; String filePath = directory + filename; try { File dir = new File(directory); if (!dir.exists()) { dir.mkdirs(); } File file = new File(directory, filename); if (file.createNewFile()) { outputStream = new FileOutputStream(file); if (source != null) { source.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); } else { outputStream.write(jpegData); } } } catch (FileNotFoundException e) { Log.e(TAG, e.getMessage()); return null; } catch (IOException e) { Log.e(TAG, e.getMessage()); return null; } finally { if (outputStream != null) { try { outputStream.close(); } catch (Throwable t) { } } } ContentValues values = new ContentValues(7); values.put(MediaStore.Images.Media.TITLE, name); values.put(MediaStore.Images.Media.DISPLAY_NAME, filename); values.put(MediaStore.Images.Media.DATE_TAKEN, dateTaken); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.DATA, filePath); return cr.insert(IMAGE_URI, values); } private void showTakePhotoLayout() { mTakePhotoLayout.setVisibility(View.VISIBLE); mCropperLayout.setVisibility(View.GONE); } private void showCropperLayout() { mTakePhotoLayout.setVisibility(View.GONE); mCropperLayout.setVisibility(View.VISIBLE); mCameraPreview.start(); //繼續啟動攝像頭 } private float mLastX = 0; private float mLastY = 0; private float mLastZ = 0; private boolean mInitialized = false; private SensorManager mSensorManager; private Sensor mAccel; @Override public void onSensorChanged(SensorEvent event) { float x = event.values[0]; float y = event.values[1]; float z = event.values[2]; if (!mInitialized){ mLastX = x; mLastY = y; mLastZ = z; mInitialized = true; } float deltaX = Math.abs(mLastX - x); float deltaY = Math.abs(mLastY - y); float deltaZ = Math.abs(mLastZ - z); if(deltaX > 0.8 || deltaY > 0.8 || deltaZ > 0.8){ mCameraPreview.setFocus(); } mLastX = x; mLastY = y; mLastZ = z; } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } }</span>
5、新增計算焦點及測光區域、拍照後圖片旋轉以及檢測攝像頭是否可用
<span style="font-family:SimHei;font-size:18px;">package com.example.terry.mycamera;
import android.content.Context;
/**
* Created by leiqi on 15/9/29.
*/
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.os.Environment;
import android.util.DisplayMetrics;
import java.io.File;
/**
* @Class:
* @Description:
* @author: leiqi(http://blog.csdn.net/u013132758)
* @Date: 2016/3/15
*/
public class Utils {
public static DisplayMetrics getScreenWH(Context context) {
DisplayMetrics dMetrics = new DisplayMetrics();
dMetrics = context.getResources().getDisplayMetrics();
return dMetrics;
}
/**
* 計算焦點及測光區域
*
* @param focusWidth
* @param focusHeight
* @param areaMultiple
* @param x
* @param y
* @param previewleft
* @param previewRight
* @param previewTop
* @param previewBottom
* @return Rect(left,top,right,bottom) : left、top、right、bottom是以顯示區域中心為原點的座標
*/
public static Rect calculateTapArea(int focusWidth, int focusHeight,
float areaMultiple, float x, float y, int previewleft,
int previewRight, int previewTop, int previewBottom) {
int areaWidth = (int) (focusWidth * areaMultiple);
int areaHeight = (int) (focusHeight * areaMultiple);
int centerX = (previewleft + previewRight) / 2;
int centerY = (previewTop + previewBottom) / 2;
double unitx = ((double) previewRight - (double) previewleft) / 2000;
double unity = ((double) previewBottom - (double) previewTop) / 2000;
int left = clamp((int) (((x - areaWidth / 2) - centerX) / unitx),
-1000, 1000);
int top = clamp((int) (((y - areaHeight / 2) - centerY) / unity),
-1000, 1000);
int right = clamp((int) (left + areaWidth / unitx), -1000, 1000);
int bottom = clamp((int) (top + areaHeight / unity), -1000, 1000);
return new Rect(left, top, right, bottom);
}
public static int clamp(int x, int min, int max) {
if (x > max)
return max;
if (x < min)
return min;
return x;
}
/**
* 檢測攝像頭裝置是否可用
* Check if this device has a camera
* @param context
* @return
*/
public static boolean checkCameraHardware(Context context) {
if (context != null && context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
// this device has a camera
return true;
} else {
// no camera on this device
return false;
}
}
/**
* @param context
* @return app_cache_path/dirName
*/
public static String getDBDir(Context context) {
String path = null;
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
path = Environment.getExternalStorageDirectory().getAbsolutePath() +
File.separator + "bbk" + File.separator + "cloudteacher" + File.separator + "db";
File externalCacheDir = context.getExternalCacheDir();
if (externalCacheDir != null) {
path = externalCacheDir.getPath();
}
}
if (path == null) {
File cacheDir = context.getCacheDir();
if (cacheDir != null && cacheDir.exists()) {
path = cacheDir.getPath();
}
}
return path;
}
/**
* bitmap旋轉
* @param b
* @param degrees
* @return
*/
public static Bitmap rotate(Bitmap b, int degrees) {
if (degrees != 0 && b != null) {
Matrix m = new Matrix();
m.setRotate(degrees, (float) b.getWidth() / 2, (float) b.getHeight() / 2);
try {
Bitmap b2 = Bitmap.createBitmap(
b, 0, 0, b.getWidth(), b.getHeight(), m, true);
if (b != b2) {
b.recycle(); //Android開發再次提示Bitmap操作完應該顯示的釋放
b = b2;
}
} catch (OutOfMemoryError ex) {
// Android123建議大家如何出現了記憶體不足異常,最好return 原始的bitmap物件。.
}
}
return b;
}
public static final int getHeightInPx(Context context) {
final int height = context.getResources().getDisplayMetrics().heightPixels;
return height;
}
public static final int getWidthInPx(Context context) {
final int width = context.getResources().getDisplayMetrics().widthPixels;
return width;
}
}</span>
原始碼下載地址:https://github.com/Terrybthvi/MyCamera