这是工作第一份任务。任务要求是用GLSurfaceView做相机预览,笔者只有那么一点安卓常识,完全不知道GLSurfaceView的存在,故用SurfaceView做好就交工了,才发现,搬砖之前要搞清楚搬哪块砖啊!
进入正题。SurfaceView是一个类,类名是SurfaceView。它可以做很多事情,今天只拿来做视图!就是屏幕上你能看见的(这句话套用在Activity上也适用)。提醒:毫无基础从一看起,只要重点直接到四,括号内容可以忽略。
开始流程:
/AndroidMainfest.xml
注意:包名(package)!!!要用相机当然要加使用相机的权限(permission)!!!还有你的SDK版本不对就自行修改一下。
/src/org.kintai.cameratest/MainActivity.java
package org.kintai.cameratest;
import android.os.Bundle;
import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.util.Log;
import android.view.Window;
public class MainActivity extends Activity {
private static final String TAG = "Kintai";
private MySurfaceView mView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);// 横屏
requestWindowFeature(Window.FEATURE_NO_TITLE);// 去掉标题
initUI();
setContentView(R.layout.activity_main);
}
private void initUI() {
Log.d(TAG, "initUI...");
mView = (MySurfaceView) findViewById(R.id.mView);
}
}
再次说明:
1.这个MainActivity和C/C++程序中main起的名字都很像,所以作用也很像。进来之后首先会在onCreate方法内执行相应代码,之前呢?之前会加载一些static库,一般NDK才有。有些情况需要注意代码的顺序 (比如在口袋里的钱包取钱这个动作,一般顺序:手——口袋——钱包——钱;非一般顺序:手——钱,能确定拿到的钱 数额 就是你要的么?不够再拿~.~)
2.initUI方法。这个是笔者写的,各式各样都有,名字要直观,就是初始化界面,用于当前Activity上加载视图SurfaceView,即这里的mView。有些小地方需要在程序调试过程中慢慢体验(横屏、去标题、log.d是有点多余的,不写也行,就是这里放放。这个都不知道,LogCat就更不用说了)
3.findViewById方法。传的参数是 R.id.mView ,与R相关的是自动生成的东西,它一般在xml中,只要写的合法就会自动生成,为什么要等它生成在R里面才能拿来用?安全起见,用不合规则的砖会把程序搞蹦跶呢。(很不幸每一个安卓初学者都要接触这个东西,用它找TextView然后贴上“HelloWorld”...)
/res/layout/activity_main.xml
再次注意:
1.context对应的名字要写对,名字前面有个“.”这里是 .MainActivity 。
2.MySurfaceView在后面创建。(Button无关紧要,就是后面要拍照啊,要切换前后摄像头可能会用到,就先贴一个在这里)
/src/org.kintai.cameratest/MySurfaceView.java
package org.kintai.cameratest;
import java.io.IOException;
import android.content.Context;
import android.hardware.Camera;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class MySurfaceView extends SurfaceView implements
SurfaceHolder.Callback {
private static final String TAG = "Kintai";
private static SurfaceHolder holder;
private Camera mCamera;
public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.i(TAG, "new View ...");
holder = getHolder();//后面会用到!
holder.addCallback(this);
}
@Override
public void surfaceCreated(SurfaceHolder arg0) {
Log.i(TAG, "surfaceCreated...");
if (mCamera == null) {
mCamera = Camera.open();//开启相机,可以放参数 0 或 1,分别代表前置、后置摄像头,默认为 0
try {
mCamera.setPreviewDisplay(holder);//整个程序的核心,相机预览的内容放在 holder
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
Log.i(TAG, "surfaceChanged...");
mCamera.startPreview();//该方法只有相机开启后才能调用
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0) {
Log.i(TAG, "surfaceChanged...");
if (mCamera != null) {
mCamera.release();//释放相机资源
mCamera = null;
}
}
}
讲解:MySurfaceView继承至SurfaceView,实现了SurfaceHolder.Callback接口
1.从构造器(方法名和类名一样的)开始就有不一样的味道,一般只有context参数,这里多了个AttributeSet参数。这是因为 SurfaceView写在布局xml,而要从xml中通过findViewById方法生成视图则有此 特定的要求,如果不写则无法生成MySurfaceView的实例.(onCreate中setContentView直接加载SurfaceView则不用)
2.构造器里的holder。一直不理解为什么会有这样的东西,后来想想。一个视图是很大一块内存,如果不给它起个名字或者取个地址,要用到的时候怎么传啊?使用getHolder就找到它了,同时还要添加一个回调addCallback,代表holder和this (即这个类的本体) 的一种关联。
3.Callback接口。要用接口就要实现其带来的功能,即方法。这里有三个SurfaceCreated、SurfaceChanged和SurfaceDestoryed,参数可以不用管先,暂时并不用在视图上做特殊操作,只要求把相机的预览呈现。
SurfaceCreated:
View创建的时候调用,这里做了两个事情打开相机,设置预览显示;
预览显示当然需要界面,故有try~catch;
mCamera是相机实体,通过Camera.open指定该实体是后置摄像头,方便后续调用;
SurfaceChanged:
View有变化的时候调用,其实它一直在变化...
mCamera要用到了,startPreview开始预览;
SurfaceDestoryed:
View在销毁的时候调用,就是要随手关灯;
mCamera又要用到了,release释放资源,mCamera不指定;
预览效果图:
源码地址:
-----------------------------------------------------------------------------------Kintai原创,欢迎转载