这是一个用surfaceview来捕捉摄像头画面并拍照存储图片到sdcard的demo。众所周知,在一个应用中,我们可以通过intent来调用系统自带的相机功能进行拍
照,但,这样做不如自己写一个拍照界面来的酷!用surfaceview的方式来做,你可以随心所欲的设计自己的界面。
在这个例子中,我用代码制作了一个拍摄界面,里面只有三个控件,一个是自己封装的CameraView,它继承了SurfaceView,一个是悬浮在CameraView上的按
钮,点击它可以捕捉画面并把图像存储到sdCard的根目录下,还有一个是悬浮在CameraView上的TextView,它不过显示一行文字而已。
程序运行截图如下:
存储在sdcard根目录下的图片如下:
你可以看到,在surfaceview中所呈现的图像和保存的图像是一样的,这样做保证了所见即所得。
代码如下:
这是MainActivity,程序一运行就打开的Activity:
public class MainActivity extends Activity {
private CameraView mCameraView;
private Button takePictureBtn;
private Camera mCamera;
private Bitmap mBitmap;
private int bitmapWidth;
private int bitmapHeight;
private RelativeLayout rl;
// 准备一个保存图片的pictureCallback对象
public Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
// TODO Auto-generated method stub
if(camera != null){
Toast.makeText(getApplicationContext(), "正在保存...", Toast.LENGTH_LONG).show();
// 用BitmapFactory.decodeByteArray()方法可以把相机传回的裸数据转换成Bitmap对象
// 这里通过BitmapFactory.Options类指定解码方法
BitmapFactory.Options options = new BitmapFactory.Options();
// 在解码图片的时候设置inJustDecodeBounds属性为true,可以避免内存分配
// options.inJustDecodeBounds = true; 这句话已开启就会死机
mBitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options);
bitmapWidth = options.outWidth;
bitmapHeight = options.outHeight;
// 把bitmap保存成一个存储卡中的文件
File file = new File("/sdcard/YY"+ new DateFormat().format("yyyyMMdd_hhmmss", Calendar.getInstance(Locale.CHINA)) + ".png");
try {
file.createNewFile();
BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file));
mBitmap.compress(Bitmap.CompressFormat.PNG, 100, os);
os.flush();
os.close();
Toast.makeText(getApplicationContext(), "图片 " + bitmapWidth + "X" + bitmapHeight +" 保存完毕,在存储卡的根目录", Toast.LENGTH_LONG).show();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//窗口去掉标题
requestWindowFeature(Window.FEATURE_NO_TITLE);
//窗口设置为全屏
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
//设置窗口为半透明
getWindow().setFormat(PixelFormat.TRANSLUCENT);
// 创建布局
rl = new RelativeLayout(this);
// 添加CameraView
addCameraView(rl);
// 添加拍摄按钮
addTakePictureBtn(rl);
// 添加提示文字
addTextView(rl);
// 设置布局
setContentView(rl);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
/**
* 添加CamearView
* @param rl 所在的布局
*/
private void addCameraView(RelativeLayout rl){
// 打开相机
this.mCamera = Camera.open();
// 设置CameraView的大小和位置
// 1、首先得到保存在sdcard的图片大小
Camera.Parameters parameters = this.mCamera.getParameters();
Size sdCardPictureSize = CameraView.getOptimalPictureSize(parameters.getSupportedPictureSizes(), 640, 480);
// 2、得到设备的屏幕分辨率
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
// 3、根据以上两个数据计算出CameraView的大小
float scale = (float)dm.heightPixels/(float)sdCardPictureSize.height;
int cameraViewWidth = (int) (sdCardPictureSize.width * scale);
int cameraViewHeight = (int) (sdCardPictureSize.height * scale);
System.out.println("scale: " + scale);
System.out.println("cameraView: " + cameraViewWidth + ", " + cameraViewHeight);
// 4、把CameraView居中布局
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(cameraViewWidth, cameraViewHeight);
rlp.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE);
// 5、把cameraView添加到rl上
this.mCameraView = new CameraView(this);
this.mCameraView.setCamera(mCamera);
rl.addView(this.mCameraView, rlp);
}
/**
* 添加拍摄按钮
* @param rl
*/
private void addTakePictureBtn(RelativeLayout rl){
this.takePictureBtn = new Button(this);
this.takePictureBtn.setText("拍摄");
this.takePictureBtn.setOnClickListener(new TakePictureBtnOnClickListener());
// 把按钮放置在屏幕右下角
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
rlp.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
rlp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT);
rlp.rightMargin = 15;
rlp.bottomMargin = 15;
rl.addView(takePictureBtn, rlp);
}
/**
* 添加提示文字
* @param rl
*/
private void addTextView(RelativeLayout rl){
TextView textView = new TextView(this);
textView.setText("请点击拍摄按钮拍摄:");
// 把文字放在屏幕左上角
RelativeLayout.LayoutParams rlp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
rlp.addRule(RelativeLayout.ALIGN_PARENT_LEFT);
rlp.addRule(RelativeLayout.ALIGN_TOP);
rl.addView(textView, rlp);
}
/**
* 拍摄按钮的OnClickListener,在这里执行拍照。
* @author haozi
*
*/
class TakePictureBtnOnClickListener implements View.OnClickListener{
@Override
public void onClick(View v) {
if(mCamera != null){
mCamera.takePicture(null, null, mPictureCallback);
}else{
Toast.makeText(getApplicationContext(), "Camera对象为空!", Toast.LENGTH_LONG).show();
}
}
}
}
这是封装好的CameraView控件,它继承了SurfaceView:
/**
* 摄像的View
* @author haozi
*
*/
public class CameraView extends SurfaceView {
public Context mContext;
private SurfaceHolder mSurfaceHolder;
public CameraView(Context context, AttributeSet attrs) {
super(context, attrs);
this.mContext = context;
}
public CameraView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.mContext = context;
}
public CameraView(Context context) {
super(context);
this.mContext = context;
}
/**
* 把照相机对象传入
* @param mCamera
*/
public void setCamera(Camera mCamera){
// 操作surface的holder
mSurfaceHolder = this.getHolder();
// 创建surfaceholder对象
mSurfaceHolder.addCallback(new SurfaceHolderCallback(mCamera));
// 设置push缓冲类型,说明surface数据由其他来源提供。而不是用自己的Canvas来绘图,在这里由摄像头来提供数据。
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
/**
* 得到最适合的预览大小
* @param sizes
* @param w
* @param h
* @return
*/
public static Size getOptimalPreviewSize(List<Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
if (sizes == null) return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
/**
* 得到最合适的PictureSize
* @param sizes
* @param w
* @param h
* @return
*/
public static Size getOptimalPictureSize(List<Size> sizes, int w, int h) {
final double ASPECT_TOLERANCE = 0.1;
double targetRatio = (double) w / h;
if (sizes == null) return null;
Size optimalSize = null;
double minDiff = Double.MAX_VALUE;
int targetHeight = h;
// Try to find an size match aspect ratio and size
for (Size size : sizes) {
double ratio = (double) size.width / size.height;
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
// Cannot find the one match the aspect ratio, ignore the requirement
if (optimalSize == null) {
minDiff = Double.MAX_VALUE;
for (Size size : sizes) {
if (Math.abs(size.height - targetHeight) < minDiff) {
optimalSize = size;
minDiff = Math.abs(size.height - targetHeight);
}
}
}
return optimalSize;
}
/**
* 摄像头捕捉到的画面都会在这里被处理
* @author haozi
*
*/
class SurfaceHolderCallback implements SurfaceHolder.Callback{
private Camera mCamera;
public SurfaceHolderCallback(Camera mCamera){
this.mCamera = mCamera;
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
// 停止预览
mCamera.stopPreview();
// 释放相机资源并置空
mCamera.release();
mCamera = null;
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
// 设置预览
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
// 如果出现异常,释放相机资源并置空
mCamera.release();
mCamera = null;
}
}
// 当surface视图数据发生变化时,处理预览信息
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// TODO Auto-generated method stub
// 如果相机资源并不为空
if(mCamera != null){
// 获得相机参数对象
Camera.Parameters parameters = mCamera.getParameters();
// 获取最合适的参数,为了做到拍摄的时候所见即所得,我让previewSize和pictureSize相等
Size previewSize = getOptimalPictureSize(parameters.getSupportedPictureSizes(), 640, 480);
Size pictureSize = getOptimalPictureSize(parameters.getSupportedPictureSizes(), 640, 480);
System.out.println("---------------------------");
System.out.println("previewSize: " + previewSize.width + ", " + previewSize.height);
System.out.println("pictureSize: " + pictureSize.width + ", " + pictureSize.height);
// 设置照片格式
parameters.setPictureFormat(PixelFormat.JPEG);
// 设置预览大小
parameters.setPreviewSize(previewSize.width, previewSize.height);
// 设置自动对焦,先进行判断
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) {
parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO);
}
// 设置图片保存时候的分辨率大小
parameters.setPictureSize(pictureSize.width, pictureSize.height);
// 给相机对象设置刚才设置的参数
mCamera.setParameters(parameters);
// 开始预览
mCamera.startPreview();
}
}
}
}要使用摄像机,别忘了权限,这是AndroidManifest.xml的代码:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.haozi.demo.screenshot4"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="4"
android:targetSdkVersion="10" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.haozi.demo.screenshot4.MainActivity"
android:label="@string/app_name"
android:screenOrientation="landscape" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera"/>
<uses-feature android:name="android.hardware.autofocus"/>
</manifest>因为是代码布局,所以就没有用到xml布局方式。ok,大功告成!
http://hi.baidu.com/justtmiss/item/f3f6d50ce395f1d872e67630--感谢这边文章的作者,我从里面得到了启发
http://hi.baidu.com/justtmiss/item/f3f6d50ce395f1d872e67630
NSString *CellIdentifier = [NSString stringWithFormat:@"Cell%d%d", [indexPath section], [indexPath row]];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier: CellIdentifier] autorelease];
}
这是我程序里面的正确代码,由于目前项目紧张。我打算过一段时间在好好描述一下问题和。
1、先难后易、玩家会接受 如果先易后难 玩家很难接受
2、数值在运营之前一定要设计好 坚决不能改 玩家玩着玩着就习惯了、要不然重要数值改来改去 玩家很容易流失
3、数值要做精细 不能做那种让人爽的数值