編輯:關於Android編程
幾乎每個APP都會用的相機功能,下面小編把內容整理分享到本站平台,供大家參考,感興趣的朋友一起學習吧!
啟動相機的兩種方式
1.直接啟動系統相機
<code class="hljs avrasm"> Intent intent = new Intent(); intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); startActivity(intent);</code>
或者指定返回圖片的名稱mCurrentPhotoFile
<code class="hljs avrasm"> Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(mCurrentPhotoFile)); startActivityForResult(intent, CAMERA_WITH_DATA);</code>
2.自定義啟動相機
今天以第二種為例。效果圖如下
自定義相機的一般步驟
創建顯示相機畫面的布局,Android已經為我們選定好SurfaceView 通過SurfaceView#getHolder()獲得鏈接Camera和SurfaceView的SurfaceHolder Camame.open()打開相機 通過SurfaceHolder鏈接Camera和SurfaceView
一般步驟的代碼演示
<code class="hljs java">public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback, Camera.AutoFocusCallback { private static final String TAG = "CameraSurfaceView"; private Context mContext; private SurfaceHolder holder; private Camera mCamera; private int mScreenWidth; private int mScreenHeight; public CameraSurfaceView(Context context) { this(context, null); } public CameraSurfaceView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CameraSurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mContext = context; getScreenMetrix(context); initView(); } private void getScreenMetrix(Context context) { WindowManager WM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); WM.getDefaultDisplay().getMetrics(outMetrics); mScreenWidth = outMetrics.widthPixels; mScreenHeight = outMetrics.heightPixels; } private void initView() { holder = getHolder();//獲得surfaceHolder引用 holder.addCallback(this); holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);//設置類型 } @Override public void surfaceCreated(SurfaceHolder holder) { Log.i(TAG, "surfaceCreated"); if (mCamera == null) { mCamera = Camera.open();//開啟相機 try { mCamera.setPreviewDisplay(holder);//攝像頭畫面顯示在Surface上 } catch (IOException e) { e.printStackTrace(); } } } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.i(TAG, "surfaceChanged"); mCamera.startPreview(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.i(TAG, "surfaceDestroyed"); mCamera.stopPreview();//停止預覽 mCamera.release();//釋放相機資源 mCamera = null; holder = null; } @Override public void onAutoFocus(boolean success, Camera Camera) { if (success) { Log.i(TAG, "onAutoFocus success="+success); } } }</code>
添加相機和自動聚焦限權
<code class="hljs xml"><uses-permission android:name="android.permission.CAMERA"> <uses-feature android:name="android.hardware.camera.autofocus"></uses-feature></uses-permission></code>
將CameraSurfaceView放在布局文件中,這裡建議最外層為FrameLayout,後面會用到。如此,我們便有了一個沒有照相功能的相機。初次之外,仔細觀察相機顯示畫面,圖片是不是變形嚴重?那是因為我們還沒有為相機設置各種參數。在預覽前要設置攝像頭的分辨率、預覽分辨率和圖片分辨率的寬高比保持一致。這樣圖片才不會變形。這是個比較難以理解的部分,想深刻理解還需讀者自己動手去實踐。
<code class="hljs java"> private void setCameraParams(Camera camera, int width, int height) { Log.i(TAG,"setCameraParams width="+width+" height="+height); Camera.Parameters parameters = mCamera.getParameters(); // 獲取攝像頭支持的PictureSize列表 List<camera.size> pictureSizeList = parameters.getSupportedPictureSizes(); for (Camera.Size size : pictureSizeList) { Log.i(TAG, "pictureSizeList size.width=" + size.width + " size.height=" + size.height); } /**從列表中選取合適的分辨率*/ Camera.Size picSize = getProperSize(pictureSizeList, ((float) height / width)); if (null == picSize) { Log.i(TAG, "null == picSize"); picSize = parameters.getPictureSize(); } Log.i(TAG, "picSize.width=" + picSize.width + " picSize.height=" + picSize.height); // 根據選出的PictureSize重新設置SurfaceView大小 float w = picSize.width; float h = picSize.height; parameters.setPictureSize(picSize.width,picSize.height); this.setLayoutParams(new FrameLayout.LayoutParams((int) (height*(h/w)), height)); // 獲取攝像頭支持的PreviewSize列表 List<camera.size> previewSizeList = parameters.getSupportedPreviewSizes(); for (Camera.Size size : previewSizeList) { Log.i(TAG, "previewSizeList size.width=" + size.width + " size.height=" + size.height); } Camera.Size preSize = getProperSize(previewSizeList, ((float) height) / width); if (null != preSize) { Log.i(TAG, "preSize.width=" + preSize.width + " preSize.height=" + preSize.height); parameters.setPreviewSize(preSize.width, preSize.height); } parameters.setJpegQuality(100); // 設置照片質量 if (parameters.getSupportedFocusModes().contains(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { parameters.setFocusMode(android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);// 連續對焦模式 } mCamera.cancelAutoFocus();//自動對焦。 mCamera.setDisplayOrientation(90);// 設置PreviewDisplay的方向,效果就是將捕獲的畫面旋轉多少度顯示 mCamera.setParameters(parameters); } /** * 從列表中選取合適的分辨率 * 默認w:h = 4:3 *<p>注意:這裡的w對應屏幕的height * h對應屏幕的width</p></camera.size></camera.size></code> */ private Camera.Size getProperSize(List pictureSizeList, float screenRatio) { Log.i(TAG, "screenRatio=" + screenRatio); Camera.Size result = null; for (Camera.Size size : pictureSizeList) { float currentRatio = ((float) size.width) / size.height; if (currentRatio - screenRatio == 0) { result = size; break; } } if (null == result) { for (Camera.Size size : pictureSizeList) { float curRatio = ((float) size.width) / size.height; if (curRatio == 4f / 3) {// 默認w:h = 4:3 result = size; break; } } } return result; }
進去的是屏幕寬高,出來的是調整好了的參數。在surfaceChanged方法中執行mCamera.startPreview(); 前調用setCameraParams(mCamera, mScreenWidth, mScreenHeight); 就可以了。最後要在AndroidManifest.xml裡設置activity的方向android:screenOrientation="portrait"代碼裡有很多注釋,其中也有我自己調試時候的Log,大家可以自己調試下,看看不同參數的效果。昨天調參數搞到一點多,都在折騰這個函數。唉,一把辛酸淚。
身為一個相機,居然不能照相?真是太丟臉了!下面給我們的相機添加上照相的功能。照相核心代碼就一句:mCamera.takePicture(null, null, jpeg);
可以看到takePicture方法有三個參數,分別是ShutterCallback、PictureCallback和PictureCallback。這裡我們只用了PictureCallback
<code class="hljs java"> // 拍照瞬間調用 private Camera.ShutterCallback shutter = new Camera.ShutterCallback() { @Override public void onShutter() { Log.i(TAG,"shutter"); } }; // 獲得沒有壓縮過的圖片數據 private Camera.PictureCallback raw = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera Camera) { Log.i(TAG, "raw"); } }; //創建jpeg圖片回調數據對象 private Camera.PictureCallback jpeg = new Camera.PictureCallback() { @Override public void onPictureTaken(byte[] data, Camera Camera) { BufferedOutputStream bos = null; Bitmap bm = null; try { // 獲得圖片 bm = BitmapFactory.decodeByteArray(data, 0, data.length); if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { Log.i(TAG, "Environment.getExternalStorageDirectory()="+Environment.getExternalStorageDirectory()); String filePath = "/sdcard/dyk"+System.currentTimeMillis()+".jpg";//照片保存路徑 File file = new File(filePath); if (!file.exists()){ file.createNewFile(); } bos = new BufferedOutputStream(new FileOutputStream(file)); bm.compress(Bitmap.CompressFormat.JPEG, 100, bos);//將圖片壓縮到流中 }else{ Toast.makeText(mContext,"沒有檢測到內存卡", Toast.LENGTH_SHORT).show(); } } catch (Exception e) { e.printStackTrace(); } finally { try { bos.flush();//輸出 bos.close();//關閉 bm.recycle();// 回收bitmap空間 mCamera.stopPreview();// 關閉預覽 mCamera.startPreview();// 開啟預覽 } catch (IOException e) { e.printStackTrace(); } } } };</code>
在jpeg的onPictureTaken裡。我們將存儲照片信息的byte[] data解析成bitmap,然後轉換成JPG格式的圖片保存在SD卡中。注意finally中最後兩句mCamera.stopPreview();// 關閉預覽 mCamera.startPreview();// 開啟預覽 上文也提到:當調用camera.takePiture方法後,camera關閉了預覽,這時需要調用startPreview()來重新開啟預覽。如果不再次開啟預覽,則會一直停留在拍攝照片畫面。為了方便外部調用拍照。這裡我暴露了一個方法供外部拍照。
<code class="hljs cs"> public void takePicture(){ //設置參數,並拍照 setCameraParams(mCamera, mScreenWidth, mScreenHeight); // 當調用camera.takePiture方法後,camera關閉了預覽,這時需要調用startPreview()來重新開啟預覽 mCamera.takePicture(null, null, jpeg); }</code>
在布局文件中添加一個Button,點擊Button執行takePicture()方法。不要忘了添加寫SD卡限權
<code class="hljs xml"><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission></code>
至此,一個具有照相並保存拍攝圖片功能的相機就做出來了。But,我們就此滿足了嗎?要是為了這些簡單的功能我也不會寫這篇博客。這只是個開始
真正的開始
昨天看見別的APP在照相的時候,屏幕上居然可以顯示像效果圖那樣的框框啦、輔助點啦、圖片bulabulabula~。在網上搜索一番實現方式,再加上一些自己的理解,構成了這篇博客。
上文布局文件一直沒有貼,現在貼出來大家先掃一眼,有些控件會在接下來展示
<code class="hljs xml"><!--?xml version="1.0" encoding="utf-8"?--> <framelayout android:layout_height="match_parent" android:layout_width="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <com.dyk.cameratest.view.camerasurfaceview android:id="@+id/cameraSurfaceView" android:layout_height="match_parent" android:layout_width="match_parent"> <com.dyk.cameratest.view.rectoncamera android:layout_height="match_parent" android:layout_width="match_parent"> <relativelayout android:layout_height="match_parent" android:layout_width="match_parent"> </relativelayout></com.dyk.cameratest.view.rectoncamera></com.dyk.cameratest.view.camerasurfaceview></framelayout></code><button android:background="#88427ac7" android:id="@+id/takePic" android:layout_alignparentbottom="true" android:layout_centerhorizontal="true" android:layout_height="50dp" android:layout_marginbottom="20dp" android:layout_width="80dp" android:text="拍照" android:textcolor="#aaa"><code class="hljs xml"> </code></button>
布局文件的最外層是個FrameLayout,我們知道FrameLayout是自帶覆蓋效果的。由來這個思路接下來就很簡單了。編程重要的是思想,思想有了,其余的就剩具體的實現細節。
自定義邊邊框框
為了和CameraSurfaceView區分開,再自定義一個RectOnCamera專門用來畫邊邊框框這些東西。這樣做還一個好處是方便維護,不至於將所有東西都放在一個View中。
RectOnCamera
<code class="hljs java">package com.dyk.cameratest.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.RectF; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; /** * Created by dyk on 2016/4/7. */ public class RectOnCamera extends View { private static final String TAG = "CameraSurfaceView"; private int mScreenWidth; private int mScreenHeight; private Paint mPaint; private RectF mRectF; // 圓 private Point centerPoint; private int radio; public RectOnCamera(Context context) { this(context, null); } public RectOnCamera(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RectOnCamera(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); getScreenMetrix(context); initView(context); } private void getScreenMetrix(Context context) { WindowManager WM = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); WM.getDefaultDisplay().getMetrics(outMetrics); mScreenWidth = outMetrics.widthPixels; mScreenHeight = outMetrics.heightPixels; } private void initView(Context context) { mPaint = new Paint(); mPaint.setAntiAlias(true);// 抗鋸齒 mPaint.setDither(true);// 防抖動 mPaint.setColor(Color.RED); mPaint.setStrokeWidth(5); mPaint.setStyle(Paint.Style.STROKE);// 空心 int marginLeft = (int) (mScreenWidth*0.15); int marginTop = (int) (mScreenHeight * 0.25); mRectF = new RectF(marginLeft, marginTop, mScreenWidth - marginLeft, mScreenHeight - marginTop); centerPoint = new Point(mScreenWidth/2, mScreenHeight/2); radio = (int) (mScreenWidth*0.1); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); mPaint.setColor(Color.RED); canvas.drawRect(mRectF, mPaint); mPaint.setColor(Color.WHITE); Log.i(TAG, "onDraw"); canvas.drawCircle(centerPoint.x,centerPoint.y, radio,mPaint);// 外圓 canvas.drawCircle(centerPoint.x,centerPoint.y, radio - 20,mPaint); // 內圓 } } </code>
這裡簡單的畫了一個類似二維碼掃描的框框,還有一個類似聚焦的內外圓。那麼問題來了,聚焦的內外圓要隨著手指滑而改變位置,而且要有聚焦的效果。可又和具有聚焦功能的CameraSurfaceView不是同一個類,不僅如此聚焦內外圓還完全覆蓋了CameraSurfaceView。要處理這種問題,需要接口回調。這就是思想下面的細節。現在雖然確定接口回調,但還有一個問題,CameraSurfaceView類和RectOnCamera類中都沒有對方的對象或者引用。沒錯,通過共同持有RectOnCamera和CameraSurfaceView的Activity可以實現此功能。下面是具體的實現方法。
動起來
首先,想要隨著手指的滑動而改變RectOnCamera的位置肯定是要復寫onTouchEvent()方法
<code class="hljs cs"> @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_UP: int x = (int) event.getX(); int y = (int) event.getY(); centerPoint = new Point(x, y); invalidate(); return true; } return true; }</code>
其次,定義回調接口
<code class="hljs java"> private IAutoFocus mIAutoFocus; /** 聚焦的回調接口 */ public interface IAutoFocus{ void autoFocus(); } public void setIAutoFocus(IAutoFocus mIAutoFocus) { this.mIAutoFocus = mIAutoFocus; }</code>
在onTouchEvent()中return前加入
<code class="hljs cs"> if (mIAutoFocus != null){ mIAutoFocus.autoFocus(); }</code>
至此我們的回調接口已經定義好了,此時還需要CameraSurfaceView暴露一個聚焦方法,以便Activity調用
<code class="hljs cs"> public void setAutoFocus(){ mCamera.autoFocus(this); }</code>
准備工作已經全部完成,下面請看Activity的具體實現:
<code class="hljs java">public class MainActivity extends Activity implements View.OnClickListener,RectOnCamera.IAutoFocus{ private CameraSurfaceView mCameraSurfaceView; private RectOnCamera mRectOnCamera; private Button takePicBtn; private boolean isClicked; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_NO_TITLE); // 全屏顯示 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_main); mCameraSurfaceView = (CameraSurfaceView) findViewById(R.id.cameraSurfaceView); mRectOnCamera = (RectOnCamera) findViewById(R.id.rectOnCamera); takePicBtn= (Button) findViewById(R.id.takePic); mRectOnCamera.setIAutoFocus(this); takePicBtn.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()){ case R.id.takePic: mCameraSurfaceView.takePicture(); break; default: break; } } @Override public void autoFocus() { mCameraSurfaceView.setAutoFocus(); } } </code>
可以看到,MainActivity實現了IAutoFocus接口,並且在復寫的IAutoFocus#autoFocus()方法中,調用了CameraSurfaceView暴露出來的方法setAutoFocus()。至此,在RectOnCamera每次的滑動過程中都會改變聚焦內外圓的位置,還會增加聚焦功能。一心二用甚至一心多用豈不是更好。
好了,Android自定義照相機教程到此結束,希望對大家有所幫助!
本站推薦閱讀:
Android開發從相機或相冊獲取圖片裁剪
Android自定義照相機倒計時拍照
最近看到新浪微博頂部欄的微博分組效果很炫,從網上查了一些資料明白原來是用PopupWindow實現的,今天自己也寫了一個例子實現了這種效果,希望對大家有幫助。PopupW
有的時候用多了Android的一些開源框架用多了,就會把最基礎的一些東西給忘了,今天來重溫一下Android的網絡基礎。包括這個Http協議的網絡請求,JSON解析。使用
Prime Land Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 2972
最近准備研究一下android雙進程守護,由於此前用eclipse 寫jni習慣了,現在主要用as 工具。在此也試著寫個demo 然後在對雙進程守護進行研究1、所需工具