前记:Android这个开源而自由的系统,为我们带来开发便利,同时也埋下太多的深坑。例如调用系统自带的相机就会出现照片丢失,或者其他各种各样的问题。因此,看来自定义一个相机十分的必要。

  要自定义相机我们无法要利用surfaceview与自带camera两把利器。

  首先了解surfaceview的基本含义:

  通常情况程序的View和用户响应都是在同一个线程中处理的,这也是为什么处理长时间事件(例如访问网络)需要放到另外的线程中去(防止阻塞当前UI线程的操作和绘制)。但是在其他线程中却不能修改UI元素,例如用后台线程更新自定义View(调用View的在自定义View中的onDraw函数)是不允许的。 如果需要在另外的线程绘制界面、需要迅速的更新界面或则渲染UI界面需要较长的时间,这种情况就要使用SurfaceView了。

  SurfaceView中包含一个Surface对象,而Surface是可以在后台线程中绘制的。Surface属于 OPhone底层显示系统。SurfaceView的性质决定了其比较适合一些场景:需要界面迅速更新、对帧率要求较高的情况。使用SurfaceView需要注意以下几点情况: SurfaceView和SurfaceHolder.Callback函数都从当前SurfaceView窗口线程中调用(一般而言就是程序的主线程)。有关资源状态要注意和绘制线程之间的同步。 在绘制线程中必须先合法的获取Surface才能开始绘制内容,在SurfaceHolder.Callback.surfaceCreated() 和SurfaceHolder.Callback.surfaceDestroyed()之间的状态为合法的,另外在Surface类型为SURFACE_TYPE_PUSH_BUFFERS时候是不合法的。 额外的绘制线程会消耗系统的资源,在使用SurfaceView的时候要注意这点。

  了解了这么多,我们来开始写我们的相机。首先,需要surfaceview来判断刷新界面。同样surefaceview三要素holder,callback,created.

  使用SurfaceView 只要继承SurfaceView类并实现(1)SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,(2)SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:

  (3)surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。

  有了这些理论的铺垫,我们所需做的就是在surfaceCreated对camera打开使其聚焦,做好拍照的操作:相应的源码如下:

  

private void initSurfaceView() {
try {
camera = Camera.open();// 打开硬件摄像头,这里导包得时候一定要注意是android.hardware.Camera
Camera.Parameters parameters = camera.getParameters();// 得到摄像头的参数
int PreviewWidth = 0;
int PreviewHeight = 0;
List<Camera.Size> size_list = parameters.getSupportedPreviewSizes(); if (size_list.size() > 0) {
Iterator<Camera.Size> itor = size_list.iterator();
while (itor.hasNext()) {
Camera.Size cur = itor.next();
if (cur.width >= PreviewWidth
&& cur.height >= PreviewHeight) {
PreviewWidth = cur.width;
PreviewHeight = cur.height;
// break;
}
}
} WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);// 得到窗口管理器
// Display display = wm.getDefaultDisplay();// 得到当前屏幕 parameters.setPreviewSize(PreviewWidth, PreviewHeight);// 设置预览照片的大小
parameters.setPictureFormat(PixelFormat.JPEG);// 设置照片的格式
parameters.setJpegQuality(100);// 设置照片的质量
System.out.println(camera.getParameters().flatten());
List<Size> picSizeValues = camera.getParameters()
.getSupportedPictureSizes();
if (picSizeValues.get(0).width > picSizeValues.get(picSizeValues
.size() - 1).width) {
for (Size size : picSizeValues) {
Log.v("show", size.width + "\n");
if (size.width <= 1024) {
parameters.setPictureSize(size.width, size.height);// 设置照片的大小,默认是和
break;
}
}
} else {
for (int i = picSizeValues.size() - 1; i >= 0; i -= 1) {
Log.v("show", picSizeValues.get(i).width + "\n");
if (picSizeValues.get(i).width <= 1024) {
parameters.setPictureSize(picSizeValues.get(i).width,
picSizeValues.get(i).height);
break;
}
}
}
parameters.setFlashMode(parameters.FLASH_MODE_AUTO);
// 屏幕一样大
camera.setParameters(parameters);
camera.setPreviewDisplay(surface_view.getHolder());// 通过SurfaceView显示取景画面
camera.startPreview();// 开始预览
is_preview = true;// 设置是否预览参数为真
} catch (IOException e) {
// Log.e(TAG, e.toString());
}
}

  通过代码,我们清晰看到了我们利用camera对象,他是何物了?他是Android提供的对摄像头操作的相应的API。我们看到上面的代码,我们设置camera拍摄的范围应该控制宽度在1024以下,并且预览的实在surefaceview范围内。

  这样应该简单相机就做出来了。但是,似乎不是那么完美,我们相机似乎差了什么样的,首先要有一个手动聚焦光圈。要完成这个功能,工作量不小啊:首先需要一个focusmanager来管理聚焦的操作:他的内部应该是这样的:

private Camera mCamera;

    private static final String TAG="FocusManager";

    private static final int FOCUS_WIDTH = 80;

    private static final int FOCUS_HEIGHT = 80;
public interface FocusListener {
public void onFocusStart(boolean smallAdjust); public void onFocusReturns(boolean smallAdjust, boolean success);
} private int mFocusKeepTimeMs = 3000;
private long mLastFocusTimestamp = 0; private Handler mHandler;
private FocusListener mListener;
private boolean mIsFocusing;
public final FocusManager mfocusManager;
private Object mParametersSync=new Object(); public FocusManager(Camera mCamera) {
mHandler = new Handler();
mIsFocusing = false;
this.mCamera = mCamera;
mfocusManager=this;
Camera.Parameters params = mCamera.getParameters();
if (params.getSupportedFocusModes().contains("auto")) {
params.setFocusMode("auto");
} // Do a first focus after 1 second
mHandler.postDelayed(new Runnable() {
public void run() {
checkFocus();
}
}, 1000);
} public void setListener(FocusListener listener) {
mListener = listener;
} public void checkFocus() {
long time = System.currentTimeMillis(); if (time - mLastFocusTimestamp > mFocusKeepTimeMs && !mIsFocusing) {
refocus();
} else if (time - mLastFocusTimestamp > mFocusKeepTimeMs * 2) {
// Force a refocus after 2 times focus failed
refocus();
}
} public void refocus() {
if (doAutofocus(this)) {
mIsFocusing = true; if (mListener != null) {
mListener.onFocusStart(false);
}
} } private boolean doAutofocus(FocusManager focusManager) {
if (mCamera != null) {
try { // Trigger af
mCamera.cancelAutoFocus(); mHandler.post(new Runnable() {
public void run() {
try {
mCamera.autoFocus(mfocusManager);
} catch (Exception e) {
// Do nothing here
}
}
}); } catch (Exception e) {
return false;
}
return true;
} else {
return false;
}
} @Override
public void onAutoFocus(boolean success, Camera camera) {
mLastFocusTimestamp = System.currentTimeMillis();
mIsFocusing = false; if (mListener != null) {
mListener.onFocusReturns(false, success);
} } /***
* 最大区域
* @return
*/
@SuppressLint("NewApi")
public boolean isFocusAreaSupported() {
if (mCamera != null) {
try {
return (getParameters().getMaxNumFocusAreas() > 0);
} catch (Exception e) {
return false;
}
} else {
return false;
}
} private Parameters getParameters() {
Parameters Parameters=null;
synchronized (mParametersSync) {
if (mCamera == null) {
Log.w("", "getParameters when camera is null");
return null;
} int tries = 0;
while (Parameters == null) {
try {
Parameters = mCamera.getParameters();
break;
} catch (RuntimeException e) {
Log.e(TAG, "Error while getting parameters: ", e);
if (tries < 3) {
tries++;
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
} else {
Log.e(TAG, "Failed to get parameters after 3 tries");
break;
}
}
}
} return Parameters;
} @SuppressLint("NewApi")
public void setFocusPoint(int x, int y) {
if (x < -1000 || x > 1000 || y < -1000 || y > 1000) {
Log.e(TAG, "setFocusPoint: values are not ideal " + "x= " + x + " y= " + y);
return;
}
Camera.Parameters params = getParameters(); if (params != null && params.getMaxNumFocusAreas() > 0) {
List<Camera.Area> focusArea = new ArrayList<Camera.Area>();
focusArea.add(new Camera.Area(new Rect(x, y, x + FOCUS_WIDTH, y + FOCUS_HEIGHT), 1000)); params.setFocusAreas(focusArea); try {
mCamera.setParameters(params);
} catch (Exception e) {
}
}
}

  首先,有一个camera对象,来通知我们来操作那个camera对象的聚焦。

  又上而下的来扫描此对象,我们观察操作最多的是对对焦调整。在checkFocus中,在其一定时间范围内使其强制对焦。在refocus这个方法中,用户的自动对焦开始,并且监听相应对焦状态的变化,用以调整对焦光圈的变化。而在isFocusAreaSupported方法中,我们要判断camera对象所支持的参数的聚焦的最大的范围是否大于0。经过这一系列的判断逻辑,我们对camera聚焦有了控制了。

  我们接下来要做的就是需要对其对焦的光圈来唠叨唠叨。对焦光圈就是相机聚焦区域来显示了一个光圈:他的类名叫做FocusHudRing。源码如下:

  

private FocusManager mFocusManager;
public FocusHudRing(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
} public FocusHudRing(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
} public FocusHudRing(Context context) {
super(context);
// TODO Auto-generated constructor stub
} @Override
@SuppressLint("ClickableViewAccessibility")
protected void onFinishInflate() {
// TODO Auto-generated method stub
super.onFinishInflate();
setImage(true);
} public void setImage(boolean success) {
// TODO Auto-generated method stub
if (success) {
setImageResource(R.drawable.hud_focus_ring_success);
} else {
setImageResource(R.drawable.hud_focus_ring_fail);
}
} public void setPosition(float x, float y) {
setX(x - getWidth() / 2.0f);
setY(y - getHeight() / 2.0f);
applyFocusPoint();
} private void applyFocusPoint() {
ViewGroup parent = (ViewGroup) getParent();
if (parent == null) return; // We swap X/Y as we have a landscape preview in portrait mode
float centerPointX = getY() + getHeight() / 2.0f;
float centerPointY = parent.getWidth() - (getX() + getWidth() / 2.0f); centerPointX *= 1000.0f / parent.getHeight();
centerPointY *= 1000.0f / parent.getWidth(); centerPointX = (centerPointX - 500.0f) * 2.0f;
centerPointY = (centerPointY - 500.0f) * 2.0f;
if (mFocusManager != null) {
mFocusManager.setFocusPoint((int) centerPointX, (int) centerPointY);
}
}
public boolean onTouch(View view, MotionEvent motionEvent) {
super.onTouch(view, motionEvent); if (motionEvent.getActionMasked() == MotionEvent.ACTION_UP) {
applyFocusPoint();
if (mFocusManager != null) {
mFocusManager.refocus();
}
}
return true;
} public FocusManager getFocusManager() {
return mFocusManager;
} public void setFocusManager(FocusManager mFocusManager) {
this.mFocusManager = mFocusManager;
}

  focushudring又不是简单存在,他又继承与hudring这个自定义控件,用户点击到了某个区域的话,这个控件就会出现在一定区域。就是依靠这个setposition控件,applyfocuspoint就是计算这个圆形,并且开启动画。

  有了这两个类以后,一个自定义聚焦控件就做好,你所做的就是监听下屏幕的手势事件,可能我说的不够清楚,大家请看代码吧?代码的位置是:

android动手写控件系列——老猪叫你写相机的更多相关文章

  1. Android自己定义控件系列五:自己定义绚丽水波纹效果

    尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自己定义控件实现一个比較有趣的效果 ...

  2. Android自己定义控件系列二:自己定义开关button(一)

    这一次我们将会实现一个完整纯粹的自己定义控件,而不是像之前的组合控件一样.拿系统的控件来实现.计划分为三部分:自己定义控件的基本部分,自己定义控件的触摸事件的处理和自己定义控件的自己定义属性: 以下就 ...

  3. android自己定义控件系列教程----视图

    理解android视图 对于android设备我们所示区域事实上和它在底层的绘制有着非常大的关系,非常多时候我们都仅仅关心我们所示,那么在底层一点它究竟是怎么样的一个东西呢?让我们先来看看这个图. w ...

  4. android自己定义控件系列教程-----仿新版优酷评论剧集卡片滑动控件

    我们先来看看优酷的控件是怎么回事? 仅仅响应最后也就是最顶部的卡片的点击事件,假设点击的不是最顶部的卡片那么就先把它放到最顶部.然后在移动到最前面来.重复如次. 知道了这几条那么我们就非常好做了. 里 ...

  5. Android自己定义控件系列三:自己定义开关button(二)

    接上一篇自己定义开关button(一)的内容继续.上一次实现了一个开关button的基本功能.即自己定义了一个控件.开关button,实现了点击切换开关状态的功能.今天我们想在此基础之上.进一步实现触 ...

  6. Android自己定义控件系列案例【五】

    案例效果: 案例分析: 在开发银行相关client的时候或者开发在线支付相关client的时候常常要求用户绑定银行卡,当中银行卡号一般须要空格分隔显示.最常见的就是每4位数以空格进行分隔.以方便用户实 ...

  7. Android自己定义控件系列一:Android怎样实现老版优酷client三级环形菜单

    转载请附上本文链接:http://blog.csdn.net/cyp331203/article/details/40423727 先来看看效果: 一眼看上去好像还挺炫的,感觉比較复杂...实际上并不 ...

  8. Android自己定义控件:老版优酷的三级菜单(效果图 + Demo)

    效果图: 制作思路: 1.先分析这个效果,事实上能够理解为把三级菜单分成level1,level2,level3,level1是始终显示的. 点击level1后,level2会出现:点击level2后 ...

  9. Android控件系列之CheckBox

    学习目的: 1.掌握在Android中如何建立CheckBox 2.掌握CheckBox的常用属性 3.掌握CheckBox选中状态变换的事件(监听器) CheckBox简介: CheckBox和Bu ...

随机推荐

  1. Android之Android WebView常见问题及解决方案汇总

    如有转载,请声明出处: 时之沙: http://blog.csdn.net/t12x3456 Android WebView常见问题解决方案汇总: 就目前而言,如何应对版本的频繁更新呢,又如何灵活多变 ...

  2. Lvs+Keepalived+Mysql

    环境 [root@node1 ~]# cat /etc/redhat-release CentOS Linux release (Core) [root@node1 ~]# uname -a Linu ...

  3. Tesseract ocr 3.02学习记录一

    光学字符识别(OCR,Optical Character Recognition)是指对文本资料进行扫描,然后对图像文件进行分析处理,获取文字及版面信息的过程.OCR技术非常专业,一般多是印刷.打印行 ...

  4. centos ssh终端下高亮显示git分支名

    #set git branch green=$'\e[1;32m' magenta=$'\e[1;35m' normal_colours=$'\e[m' function find_git_branc ...

  5. Sublime Text 2 快捷键(转)

    文件 File 新建文件 Ctrl + N 打开文件 Ctrl + O 打开最近关闭的文件 Ctrl + Shift + T 保存 Ctrl + S 另存为… Ctrl + Shift + S 关闭文 ...

  6. Implementation of Serial Wire JTAG flash programming in ARM Cortex M3 Processors

    Implementation of Serial Wire JTAG flash programming in ARM Cortex M3 Processors The goal of the pro ...

  7. WICED SDK 3.3.1

    7/20/2015 UPDATE: After installing the IDE you may not see all the APPs.  Press F5 in Eclipse to ref ...

  8. Temporary ASP.Net Files探究

    了解.net平台的兄弟都知道,.net也是采用动态编译的也就是说我们常说的build生成的dll只是中间代码而在web第一次请求的时候才是真正意义上的编译生成二进制代码这也就是为什么刚编译完第一次打开 ...

  9. 「GIT SourceTree冲突」解决方案

    现在程序猿标配GIT作为代码管理,但是从SVN到GIT学习中,其中GIT的冲突是一个难点,常常会导致Push不上去,Pull不下来,很尴尬的地步,还不知道自己写的代码被覆盖没,废话不多说,直接上干货! ...

  10. [Go] panic 和 recover

    通常情况下,函数向其调用方报告错误的方式都是返回一个 error 类型的值.但是,当遇到致命错误的时候,很可能会使程序无法继续运行.这时,上述错误处理方式就太不适合了,Go 推荐通过调用 panic ...