欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义照相机详解

程序员文章站 2024-02-29 15:04:16
几乎每个app都会用的相机功能,下面小编把内容整理分享到萬仟网平台,供大家参考,感兴趣的朋友一起学习吧! 启动相机的两种方式 1.直接启动系统相机

几乎每个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自定义照相机详解

自定义相机的一般步骤

创建显示相机画面的布局,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自定义照相机倒计时拍照