前记: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. SDOI2013 R1 Day1

    目录 2018.3.22 Test 总结 T1 BZOJ.3122.[SDOI2013]随机数生成器(BSGS 等比数列) T2 BZOJ.3123.[SDOI2013]森林(主席树 启发式合并) T ...

  2. BZOJ.4516.[SDOI2016]生成魔咒(后缀自动机 map)

    题目链接 后缀数组做法见这. 直接SAM+map.对于每个节点其产生的不同子串数为len[i]-len[fa[i]]. //15932kb 676ms #include <map> #in ...

  3. 简表-Java-Echart报表介绍

    Java后台报表尝试了很多,最终发现了一款,而且是开源的,简表地址:http://www.jatools.com/jor/.问题的引入:该报表支持嵌套,钻去,应对excel类似的报表,足够了.但是,报 ...

  4. 解决svn中文乱码的问题

    需要的工具:sqlitexiaz 工具下载: 链接:https://pan.baidu.com/s/1cz1Pvw 密码:yp64 1 首先在项目的根目录下,找到.svn(如果找不到,需要设置将隐藏文 ...

  5. MongoDB+MongoVUE安装及入门

    前言及概念 据说nodejs和mongoDB是一对好基友,于是就忍不住去学习了解了一下MongoDB相关的一些东西, 那么,MongoDB是什么?这里的五件事是每个开放人员应该知道的: MongoDB ...

  6. delphi代码实现窗口最小化,最大化,关闭消息发送

      分类: 代码实现窗口最小化,最大化,关闭 var hwnd: hwnd;//句柄 PostMessage(hwnd,WM_SYSCOMMAND, SC_MINIMIZE,0); //最小化Post ...

  7. 调用WScript.Shell时产生Automation 服务器不能创建对象的错误

    我们经常需要通过生成ActiveXObject("WScript.Shell");来调某一exe文件, 如 //设置网页打印的页眉页脚为空 var HKEY_Root,HKEY_P ...

  8. 基于设备树的controller学习(1)

    作者 彭东林pengdonglin137@163.com 平台 TQ2440Linux-4.10.17 概述 在设备树中我们经常见到诸如"#clock-cells"."# ...

  9. ISO 8601: Delphi way to convert XML date and time to TDateTime and back (via: Stack Overflow)

    Recently I needed a way of concerting back and forth ISO 8601 DateTime values used in XML from Delph ...

  10. firedac调用ORACLE的存储过程

    firedac调用ORACLE的存储过程 EMB官方原文地址:http://docwiki.embarcadero.com/RADStudio/Tokyo/en/Using_Oracle_with_F ...