編輯:關於Android編程
在Api21中Camera類被廢棄,取而代之的是camera2包。相對來說,camera2比Camera使用起來看似復雜了好多,但是在靈活性方面增加了很多。不過出於兼容性的考慮,加上當前Android設備5.0以下的占比還是比較大的,所以在相機開發的過程中,Camera類還是不得不掌握的。在本文中,講解的是Camera的基本使用。而關於camera2的使用,在以後的文章中會單獨再講。
Camera.open(int)獲得一個相機實例 利用camera.getParameters()得到相機實例的默認設置Camera.Parameters 如果需要的話,修改Camera.Parameters並調用camera.setParameters(Camera.Parameters)來修改相機設置 調用camera.setDisplayOrientation(int)來設置正確的預覽方向 調用camera.setPreviewDisplay(SurfaceHolder)來設置預覽,如果沒有這一步,相機是無法開始預覽的 調用camera.startPreview()來開啟預覽,對於拍照,這一步是必須的 在需要的時候調用camera.takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)來拍攝照片 拍攝照片後,相機會停止預覽,如果需要再次拍攝多張照片,需要再次調用camera.startPreview()來重新開始預覽 調用camera.stopPreview()來停止預覽 一定要在onPause()的時候調用camera.release()來釋放camera,在onResume中重新開始camera
相機權限
使用相機進行拍照,需要添加以下權限:
拍攝的照片需要存儲到內存卡的話,還需要內存卡讀寫的權限:
拍照注意事項
根據拍照步驟就可以使用相機進行拍照,但是在使用過程中,有諸多需要注意的地方。
開啟相機
開啟相機直接調用Camera.open(),理論上就可以得到一個相機實例。但是在開啟相機前,最好check一下。雖然目前基本上所有的手機都是前後攝像頭,但是也不排斥有些奇葩的手機,只有後攝像頭,或者干脆沒有攝像頭的。所有在open一個camera前,先檢查是否存在該id的camera。
Camera.getNumberOfCameras()可以獲得當前設備的Camera的個數N,N為0表示不支持攝像頭,否則對應的Camera的ID就是0—(N-1)。
相機設置
Camera的Parameters提供了諸多屬性,可以對相機進行多種設置,包括預覽大小及格式、照片大小及格式、預覽頻率、拍攝場景、顏色效果、對焦方式等等,具體設置可參考官方手冊。
拍照尤其需要注意的是對預覽大小、照片大小以及對焦方式的設置。
在對預覽大小、照片大小及對焦方式設置時,設置的值必須是當前設備所支持的。否則,預覽大小和照片大小設置會無效,對焦方式設置會導致崩潰。它們都有相應的方法,獲取相應的支持的列表。對應的依次為getSupportedPictureSizes(),
getSupportedPictureSizes(),
getSupportedFocusModes()。
相機預覽方向
不對相機預覽方向和應用方向設置,通常情況下得到的預覽結果是無法接受的。一般應用設置的方向為固定豎向,預覽設置旋轉90度即可。嚴謹點來說,預覽方向的設置是根據當前window的rotation來設置的,即
((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation()的值。在
Surface.ROTATION_0和
Surface.ROTATION_180時,Camera設置displayOrientation為90,否則設置為0。
預覽View的設置
相機預覽前,必須調用
camera.setPreviewDisplay(SurfaceHolder)來設置預覽的承載。SurfaceHolder一般取自SurfaceView、SurfaceTexture、TextureView等。一般情況下,如果不對顯示的View大小做合理的設置,預覽中的場景都會被變形。
如何保證預覽不變形呢?預覽效果變形是因為設置的previewSize和預覽View的長寬比例不同造成的,將預覽View的長寬比設置的與Camera的previewSize相同(需要注意的是,豎屏下是預覽View的長寬比,要設置的與Camera的previewSize的寬長比相同)即可解決變形的問題。
以全屏預覽為例,假如手機屏幕分辨率為1280*720,相機支持的預覽大小沒有1280*720的,也沒有這個比例的,但是有1280*960的,相機預覽大小選的也是這個。這時候,將預覽View的大小設置為1280*960即可,超出屏幕的部分不予理會。值得注意的是,如果預覽View的父布局是RelativeLayout,設置寬度大於父布局是無效的。可以重寫RelativeLayout的onMeasure來實現,或者將父布局改為FrameLayout。
拍照監聽及圖片處理
相機拍照時在預覽時,調用
takePicture(Camera.ShutterCallback, Camera.PictureCallback, Camera.PictureCallback, Camera.PictureCallback)(或其重載方法)來實現拍照的,其中第一個參數表示圖像捕獲時刻的回調。可以看到此方法的後三個參數類型是一樣的,都是圖像回調。分別表示原始圖數據回調、展示圖像數據的回調、JPEG圖像數據的回調。圖像回調中得到byte數組decode為image後,圖片的方向都是攝像頭原始的圖像方向。
可以通過parameters.setRotation(int)來改變最後一個回調中圖像數據的方向。個人推薦不設置,直接在回調中利用矩陣變換統一處理。因為利用
parameters.setRotation(int)來旋轉圖像,在不同手機上會有差異。
與預覽View設置類似,pictureSize設置的值,影響了最後的拍照結果,處理時需要對拍照的結果進行裁剪,使圖片結果和在可視區域預覽的結果相同。前攝像頭拍攝的結果還需要做對稱變換,以保證“所見即所得”。
拍照示例
首先需要一個相機的控制類CameraKitKat,繼承自ACamera,ACamera是一個抽象類,具有open(int),close()方法,控制相機的開啟和關閉。這樣做主要是為了後面擴展使用Camera2。CameraKitKat源碼如下:
public class CameraKitKat extends ACamera{
private Camera camera;
private SurfaceHolder holder;
private float displayScale;
public CameraKitKat(SurfaceView surfaceView) {
super(surfaceView);
init();
}
private void init(){
holder=displayView.getHolder();
}
@Override
public void open(int type){
int rotation=((WindowManager)displayView.getContext().getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay().getRotation();
if(!openCamera(type))return;
setParameters(camera,rotation);
setDisplayOrientation(camera,rotation);
setPreviewDisplay(camera,holder);
camera.startPreview();
}
@Override
public void close(){
camera.stopPreview();
camera.release();
}
//調整SurfaceView的大小
private void resizeDisplayView(){
Camera.Parameters parameters=camera.getParameters();
Camera.Size size=parameters.getPreviewSize();
FrameLayout.LayoutParams p= (FrameLayout.LayoutParams) displayView.getLayoutParams();
float scale=size.width/(float)size.height;
displayScale=displayView.getHeight()/(float)displayView.getWidth();
if(scale>displayScale){
p.height= (int) (scale*displayView.getWidth());
p.width=displayView.getWidth();
}else{
p.width= (int) (displayView.getHeight()/scale);
p.height=displayView.getHeight();
}
Log.e("wuwang","-->"+size.width+"/"+size.height);
Log.e("wuwang","--<"+p.height+"/"+p.width);
displayView.setLayoutParams(p);
displayView.invalidate();
}
private boolean checkCameraId(int cameraId){
return cameraId>=0&&cameraId() {
@Override
public int compare(Camera.Size lhs, Camera.Size rhs) {
return lhs.width*lhs.height-rhs.width*rhs.height;
}
});
parameters.setPreviewSize(size.width,size.height);
//PictureSize設置為和預覽大小最近的
Camera.Size picSize=Collections.max(parameters.getSupportedPictureSizes(), new Comparator() {
@Override
public int compare(Camera.Size lhs, Camera.Size rhs) {
return (int) (Math.sqrt(Math.pow(size.width-rhs.width,2)+Math.pow(size.height-rhs.height,2))-
Math.sqrt(Math.pow(size.width-lhs.width,2)+Math.pow(size.height-lhs.height,2)));
}
});
parameters.setPictureSize(picSize.width,picSize.height);
//如果相機支持自動聚焦,則設置相機自動聚焦,否則不設置
if(parameters.getSupportedFocusModes().contains(Camera.Parameters.FOCUS_MODE_AUTO)){
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
}
//設置顏色效果
// parameters.setColorEffect(Camera.Parameters.EFFECT_MONO);
camera.setParameters(parameters);
resizeDisplayView();
}
//相機使用第三步,設置相機預覽方向
private void setDisplayOrientation(Camera camera,int rotation){
if(rotation== Surface.ROTATION_0||rotation==Surface.ROTATION_180){
camera.setDisplayOrientation(90);
}else{
camera.setDisplayOrientation(0);
}
}
//相機使用第四步,設置相機預覽載體SurfaceHolder
private void setPreviewDisplay(Camera camera,SurfaceHolder holder){
try {
camera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void measureSize(int width, int height) {
super.measureSize(width, height);
}
@Override
public void takePicture() {
super.takePicture();
camera.takePicture(new Camera.ShutterCallback() {
@Override
public void onShutter() {
}
}, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
}
}, new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
if(pictureCallback!=null){
pictureCallback.onPictureTaken(data,displayScale);
}
}
});
}
}
本例中,使用SurfaceView作為預覽View,提供SurfaceHolder給Camera。考慮到預覽變形問題,使用FrameView作為其父布局。CameraPreview源碼如下:
public class CameraPreview extends FrameLayout implements SurfaceHolder.Callback{
private SurfaceView surfaceView;
private ACamera camera;
private boolean isCameraBack=false;
public CameraPreview(Context context) {
this(context,null);
}
public CameraPreview(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public CameraPreview(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init(){
addPreview();
//後續增加CameraLollipop,根據系統版本使用Camera或者Camera2
camera=new CameraKitKat(surfaceView);
setKeepScreenOn(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
camera.measureSize(MeasureSpec.getSize(widthMeasureSpec),MeasureSpec.getSize(heightMeasureSpec));
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
private void addPreview(){
surfaceView=new SurfaceView(getContext());
surfaceView.getHolder().addCallback(this);
this.addView(surfaceView);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
camera.open(isCameraBack?0:1);
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
setKeepScreenOn(false);
camera.close();
}
public boolean isCameraBack(){
return isCameraBack;
}
public void setOnPictureCallback(PictureCallback pictureCallback){
camera.setOnPictureCallback(pictureCallback);
}
public void takePicture(){
camera.takePicture();
}
}
在拍照的Activity中,置入CameraPreview,長寬都設置為match_parent,然後增加一個拍照的按鈕。增加拍照監聽,並在監聽中對圖像數據進行處理即可。Activity的源碼如下:
public class MainActivity extends Activity implements View.OnClickListener{
private View btnTake;
private ImageView ivShower;
private CameraPreview cameraPreview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btnTake).setOnClickListener(this);
ivShower= (ImageView) findViewById(R.id.ivShower);
cameraPreview= (CameraPreview) findViewById(R.id.cameraView);
cameraPreview.setOnPictureCallback(new PictureCallback() {
@Override
public void onPictureTaken(byte[] data,float scale) {
ivShower.setVisibility(View.VISIBLE);
Bitmap bitmap=BitmapFactory.decodeByteArray(data,0,data.length);
Bitmap bitmap2=rotateAndCropBitmap(bitmap,cameraPreview.isCameraBack()?90:-90,scale);
saveBitmapToPath(bitmap2,Environment.getExternalStorageDirectory()+"/temp.jpeg");
ivShower.setImageBitmap(bitmap2);
bitmap.recycle();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
File file=new File(Environment.getExternalStorageDirectory()+"/temp.jpeg");
if(file.exists()){
file.delete();
}
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btnTake:
cameraPreview.takePicture();
break;
default:
break;
}
}
private Bitmap rotateAndCropBitmap(Bitmap bm,int orientationDegree,float rate){
//TODO : 貌似有些問題
int width,height;
float bmScale=bm.getHeight()/(float)bm.getWidth();
if(rate==bmScale)return bm;
else if(rate>bmScale){
width=bm.getWidth();
height= (int) (width/rate);
}else{
height= bm.getHeight();
width= (int) (height*rate);
}
Matrix m = new Matrix();
if(orientationDegree==-90){ //前攝像頭,則左右鏡像
m.postScale(1,-1);
}
m.postRotate(orientationDegree);
return Bitmap.createBitmap(bm,0,bm.getHeight()-height,width,height,m,true);
}
private void saveBitmapToPath(Bitmap bitmap,String path){
File file=new File(path);
try {
FileOutputStream fos=new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG,90,fos);
fos.flush();
fos.close();
Log.e("wuwang","filePath-->"+file.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
}
}
}
前邊4篇文章,成功將 u-boot2012 移植到了 2440 開發板上,但是它僅僅支持 norflash 啟動並不夠完善,下面我們設法讓它支持兩種啟動方式。首先,我們得
前不久看到鴻洋大大的圓形菜單,就想開始模仿,因為實在是太酷了,然後自己根據別人(zw哥)給我講的一些思路、一些分析,就開始改造自己的圓形菜單了。文章結構:1.功能介紹以及
Prime Land Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 2972
GridView基礎新建一個HelloGridView的工程修改main.xml代碼如下:<?xml version=1.0 encoding=utf-8&