我们知道一般相机中都有点击时自动聚焦点击区域的效果,那么在我们的自定义相机中要怎么实现这个效果呢
一、相关API
在相机开发中,我们都应该了解到过Camera.Parameters这个类,很多相机的参数设置都是通过这个类来完成的。查看Camera的源码我们可以看到
1.setFocusAreas(List<Area> focusAreas)方法
/**
* Sets focus areas. See {@link #getFocusAreas()} for documentation.
*
* @param focusAreas the focus areas
* @see #getFocusAreas()
*/
public void setFocusAreas(List<Area> focusAreas) {
set(KEY_FOCUS_AREAS, focusAreas);
}
这个方法就是我们最终设置聚焦区域的方法,接下来我们应该了解一下这个方法的使用方法以及参数的含义。这里官方的注释告诉我们通过getFocusAreas()方法来查看文档,那么就让我们看下getFocusAreas()方法的文档吧
2.getFocusAreas()方法
/**
* <p>Gets the current focus areas. Camera driver uses the areas to decide
* focus.</p>
*
* <p>Before using this API or {@link #setFocusAreas(List)}, apps should
* call {@link #getMaxNumFocusAreas()} to know the maximum number of
* focus areas first. If the value is 0, focus area is not supported.</p>
*
* @return a list of current focus areas
*/
public List<Area> getFocusAreas() {
return splitArea(get(KEY_FOCUS_AREAS));
}
这里我删除了很多其它的描述注释,我们只需看到注释中说道在使用getFocusAreas()或者setFocusAreas(List<Area> focusAreas)方法前,应该先调用getMaxNumFocusAreas()方法获取设备支持的最大聚焦区域数量
3.getMaxNumFocusAreas()方法
/**
* Gets the maximum number of focus areas supported. This is the maximum
* length of the list in {@link #setFocusAreas(List)} and
* {@link #getFocusAreas()}.
*
* @return the maximum number of focus areas supported by the camera.
* @see #getFocusAreas()
*/
public int getMaxNumFocusAreas() {
return getInt(KEY_MAX_NUM_FOCUS_AREAS, 0);
}
看注释已经很明白了,目前市场大部分手机这个方法的返回值,前置摄像头都是0,后置摄像头都是1,说明前置摄像头一般不支持设置聚焦,而后置摄像头一般也只支持单个区域的聚焦。当然这个是和摄像头设备相关的,不同的设备返回值可能也不同。
4.Camera.Area静态内部类
/**
* <p>The Area class is used for choosing specific metering and focus areas for
* the camera to use when calculating auto-exposure, auto-white balance, and
* auto-focus.</p>
*/
public static class Area {}
官方注释中对这个类的解释就是指测光区域和聚焦区域,用于相机计算自动曝光、自动白平衡、自动聚焦。它有一个构造方法
/**
* Create an area with specified rectangle and weight.
*
* @param rect the bounds of the area.
* @param weight the weight of the area.
*/
public Area(Rect rect, int weight) {
this.rect = rect;
this.weight = weight;
}
其中Rect就是一个矩形区域,weight是一个权重值,代表其重要程度。官方注释中对这两个参数的解释为
/**
* <p>Each Area consists of a rectangle specifying its bounds, and a weight
* that determines its importance. The bounds are relative to the camera's
* current field of view. The coordinates are mapped so that (-1000, -1000)
* is always the top-left corner of the current field of view, and (1000,
* 1000) is always the bottom-right corner of the current field of
* view. Setting Areas with bounds outside that range is not allowed. Areas
* with zero or negative width or height are not allowed.</p>
*
* <p>The weight must range from 1 to 1000, and represents a weight for
* every pixel in the area. This means that a large metering area with
* the same weight as a smaller area will have more effect in the
* metering result. Metering areas can overlap and the driver
* will add the weights in the overlap region.</p>
*/
注释中解释到Camera.Area中的Rect字段,代表了一个被映射成2000x2000单元格的矩形。坐标(-1000,-1000)代表Camera图像的左上角,(1000,1000)代表Camera图像的右下角。
这里盗用一下别人的图,图中的红线说明了在Camera预览窗口中给Camera.Area指定的坐标系统。用Rect的值是(333,333,667,667)蓝色框显示了摄像区域的位置和形状。
二、实现思路(重点!)
了解这些API之后,实现思路其实就很简单了。
1、初始化相机,设置相应参数,并打开预览。这些东西都是类似的这里就不具体说明了。
2、通过点击屏幕,我们可以通过SurFaceView(我这里是使用的SurFaceView自定义相机的,别的也是同理)获取用户点击的坐标点信息
3、这里会有一个映射关系,通过上一步获取到的坐标信息是你设置的相机预览宽度和高度的坐标系中的坐标,我们需要把用户点击的这个坐标映射到Camera.Area中的那个坐标(-1000,-1000)到(1000,1000)的坐标系中
4、得到映射后的坐标后,我们可以以这个坐标点为中心点,根据个人喜好设置相应大小,计算获得一个Rect对象
5、最后我们只需要把这些相应的数据对象设置好给相机就行了
三、部分代码
这里假设我们已经得到映射后的Rect了,那么只需要简单的调用就行。
protected void focusOnRect(Rect rect) {
if (mCamera != null) {
Parameters parameters = mCamera.getParameters(); // 先获取当前相机的参数配置对象
parameters.setFocusMode(Parameters.FOCUS_MODE_AUTO); // 设置聚焦模式
Log.d(TAG, "parameters.getMaxNumFocusAreas() : " + parameters.getMaxNumFocusAreas());
if (parameters.getMaxNumFocusAreas() > 0) {
List<Camera.Area> focusAreas = new ArrayList<Camera.Area>();
focusAreas.add(new Camera.Area(rect, 1000));
parameters.setFocusAreas(focusAreas);
}
mCamera.cancelAutoFocus(); // 先要取消掉进程中所有的聚焦功能
mCamera.setParameters(parameters); // 一定要记得把相应参数设置给相机
mCamera.autoFocus(this);
}
}
可以通过打印这个返回值看聚焦成功了没有
@Override
public void onAutoFocus(boolean success, Camera camera) {
Log.d(TAG, "onAutoFocus : " + success);
}
一些后续的坐标计算以及相机设置后续看情况再贴出把,主要的是了解思路和熟练运用API。
还是再贴一点计算的代码吧:
获取点击坐标:
mSurfaceView.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
focusOnTouch((int) event.getX(), (int) event.getY());
return false;
}
});
触发点击聚焦
private void focusOnTouch(int x, int y) {
Rect rect = new Rect(x - 100, y - 100, x + 100, y + 100);
int left = rect.left * 2000 / mSurfaceview.getWidth() - 1000;
int top = rect.top * 2000 / mSurfaceview.getHeight() - 1000;
int right = rect.right * 2000 / mSurfaceview.getWidth() - 1000;
int bottom = rect.bottom * 2000 / mSurfaceview.getHeight() - 1000;
// 如果超出了(-1000,1000)到(1000, 1000)的范围,则会导致相机崩溃
left = left < -1000 ? -1000 : left;
top = top < -1000 ? -1000 : top;
right = right > 1000 ? 1000 : right;
bottom = bottom > 1000 ? 1000 : bottom;
focusOnRect(new Rect(left, top, right, bottom));
}