前记: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. [CC-XXOR]Chef and Easy Problem

    [CC-XXOR]Chef and Easy Problem 题目大意: 给你一个长度为\(n(n\le10^5)\)的序列\(A(A_i<2^{31})\).\(m(m\le10^5)\)次询 ...

  2. HDU 5745 La Vie en rose 暴力

    La Vie en rose 题目连接: http://acm.hdu.edu.cn/showproblem.php?pid=5745 Description Professor Zhang woul ...

  3. 刚刚看到 PNaCl, 这才是我一直期待的跨平台的好东西!

    http://code.google.com/p/nativeclient/ https://developers.google.com/native-client/overview

  4. 使用CefSharp在.Net程序中嵌入Chrome浏览器(六)——调试

    chrome强大的调试功能令许多开发者爱不释手,在使用cef的时候,我们也可以继承这强大的开发者工具. 集成调试: 我们可以使用如下函数直接使用集成在chrome里的开发者工具 _chrome.Sho ...

  5. [坑] treap

    先来挖个坑,以后有时间了来补上. treap: 学习资料: fhq式treap    http://hi.baidu.com/wdxertqdtscnwze/item/7b6a9419be7c68cd ...

  6. nRF51 DK : nRF51822 Development Kit for Bluetooth Smart, ANT and 2.4GHz applications.

    KEY FEATURES • Affordable, Rapid prototyping and development solution for nRF51 Series SoCs • Kit su ...

  7. Linux/CentOS服务器 一个网卡绑定多IP地址(永久设置)

    有时我们在使用 Linux 服务器时需要配置多个IP地址.如果要配置多个IP地址是否需要多块网卡呢?答案是否定的.以 CentOS 系统为例,多个 IP 地址是可以共享一块物理网卡的. 如何永久为单网 ...

  8. asp.net core读取appsettings.json,如何读取多环境开发配置

    摘要 在读取appsettings.json文件中配置的时候,觉得最简单的方式就是使用asp.net core注入的方式进行读取了. 步骤 首先根据配置项的结构定义一个配置类,比如叫AppSettin ...

  9. 使用HttpClient对ASP.NET Web API服务实现增删改查

    本篇体验使用HttpClient对ASP.NET Web API服务实现增删改查. 创建ASP.NET Web API项目 新建项目,选择"ASP.NET MVC 4 Web应用程序&quo ...

  10. lufylegend:动画

    1.动画1 <script type="text/javascript"> var loader,anime,layer; //初始化画布 init(200, &quo ...