本文已授权微信公众号《鸿洋》原创首发,转载请务必注明出处。

概述

相机差点儿是每一个APP都要用到的功能,万一老板让你定制相机方不方?反正我是有点方。

关于相机的两天奋斗总结免费送给你。

  Intent intent = new Intent();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
startActivity(intent);

或者指定返回图片的名称mCurrentPhotoFile

  Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT,Uri.fromFile(mCurrentPhotoFile));
startActivityForResult(intent, CAMERA_WITH_DATA);

2.自己定义启动相机。

今天以另外一种为例。

效果图例如以下

自己定义相机的一般步骤

  1. 创建显示相机画面的布局。Android已经为我们选定好SurfaceView
  2. 通过SurfaceView#getHolder()获得链接CameraSurfaceViewSurfaceHolder
  3. Camame.open()打开相机
  4. 通过SurfaceHolder链接CameraurfaceView

一般步骤的代码演示

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);
}
}
}

加入相机和自己主动聚焦限权

<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera.autofocus" />

CameraSurfaceView放在布局文件里,这里建议最外层为FrameLayout,后面会用到。如此。我们便有了一个没有照相功能的相机。初次之外,细致观察相机显示画面,图片是不是变形严重?那是由于我们还没有为相机设置各种參数。在预览前要设置摄像头的分辨率、预览分辨率和图片分辨率的宽高比保持一致。这样图片才不会变形。这是个比較难以理解的部分,想深刻理解还需读者自己动手去实践。

   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();//自己主动对焦。 // 设置PreviewDisplay的方向。效果就是将捕获的画面旋转多少度显示
// TODO 这里直接设置90°不严谨。详细见https://developer.android.com/reference/android/hardware/Camera.html#setPreviewDisplay%28android.view.SurfaceHolder%29
mCamera.setDisplayOrientation(90);
mCamera.setParameters(parameters);
} /**
* 从列表中选取合适的分辨率
* 默认w:h = 4:3
* <p>tip:这里的w相应屏幕的height
* h相应屏幕的width<p/>
*/
private Camera.Size getProperSize(List<Camera.Size> 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方法有三个參数,各自是ShutterCallbackPictureCallbackPictureCallback。这里我们仅仅用了PictureCallback

    // 拍照瞬间调用
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();
}
} }
};

jpeg#onPictureTaken()里。我们将存储照片信息的byte[] data解析成bitmap,然后转换成JPG格式的图片保存在SD卡中。注意finally中最后两句mCamera.stopPreview();// 关闭预览 mCamera.startPreview();// 开启预览 上文也提到:当调用camera.takePiture()方法后。camera关闭了预览。这时须要调用startPreview()来又一次开启预览。假设不再次开启预览。则会一直停留在拍摄照片画面。为了方便外部调用拍照。这里我暴露了一个方法供外部拍照。

    public void takePicture(){
//设置參数,并拍照
setCameraParams(mCamera, mScreenWidth, mScreenHeight);
// 当调用camera.takePiture方法后,camera关闭了预览,这时须要调用startPreview()来又一次开启预览
mCamera.takePicture(null, null, jpeg);
}

在布局文件里加入一个Button,点击Button运行takePicture()方法。

不要忘了加入写SD卡限权

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

至此。一个具有照相并保存拍摄图片功能的相机就做出来了。

But,我们就此满足了吗?要是为了这些简单的功能我也不会写这篇博客。这仅仅是个開始

真正的開始

别人APP在照相的时候。屏幕上竟然能够显示像效果图那样的框框啦、辅助点啦、图片bulabulabula~。

在网上搜索一番实现方式,再加上一些自己的理解,构成了这篇博客。

上文布局文件一直没有贴。如今贴出来大家先扫一眼,有些控件会在接下来展示

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"> <com.dyk.cameratest.view.CameraSurfaceView
android:id="@+id/cameraSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" /> <com.dyk.cameratest.view.RectOnCamera
android:layout_width="match_parent"
android:layout_height="match_parent" /> <RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"> <Button
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="20dp"
android:id="@+id/takePic"
android:layout_width="80dp"
android:layout_height="50dp"
android:background="#88427ac7"
android:text="拍照"
android:textColor="#aaa" />
</RelativeLayout>
</FrameLayout>

布局文件的最外层是个FrameLayout。我们知道FrameLayout是自带覆盖效果的。由来这个思路接下来就非常easy了。

编程重要的是思想,思想有了,其余的就剩详细的实现细节。

自己定义边边框框

为了和CameraSurfaceView区分开,再自己定义一个RectOnCamera专门用来画边边框框这些东西。这样做还一个优点是方便维护,不至于将所有东西都放在一个View中。

RectOnCamera

package com.dyk.cameratest.view;
...
/**
* Created by 一口仨馍 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); // 内圆
}
}

这里简单的画了一个相似二维码扫描的框框。另一个相似聚焦的内外圆。那么问题来了,聚焦的内外圆要随着手指滑而改变位置,并且要有聚焦的效果。可又和具有聚焦功能的CameraSurfaceView不是同一个类。不仅如此聚焦内外圆还全然覆盖了CameraSurfaceView。要处理这样的问题,须要接口回调。这就是思想以下的细节。如今尽管确定接口回调。但另一个问题,CameraSurfaceView类和RectOnCamera类中都没有对方的对象或者引用。没错,通过共同持有RectOnCameraCameraSurfaceViewActivity能够实现此功能。以下是详细的实现方法

动起来

首先。想要随着手指的滑动而改变RectOnCamera的位置肯定是要复写onTouchEvent()方法

    @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;
}

其次,定义回调接口

 private IAutoFocus mIAutoFocus;

    /** 聚焦的回调接口 */
public interface IAutoFocus{
void autoFocus();
} public void setIAutoFocus(IAutoFocus mIAutoFocus) {
this.mIAutoFocus = mIAutoFocus;
}

onTouchEvent()return前加入

  if (mIAutoFocus != null){
mIAutoFocus.autoFocus();
}

至此我们的回调接口已经定义好了。此时还须要CameraSurfaceView暴露一个聚焦方法。以便Activity调用

    public void setAutoFocus(){
mCamera.autoFocus(this);
}

准备工作已经所有完毕。以下请看Activity的详细实现:

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();
}
}

能够看到,MainActivity实现了IAutoFocus接口,并且在复写的IAutoFocus#autoFocus()方法中。调用了CameraSurfaceView暴露出来的方法setAutoFocus()。至此,在RectOnCamera每次的滑动过程中都会改变聚焦内外圆的位置,还会添加聚焦功能。

一心二用甚至一心多用岂不是更好。

结束语

在经历两次断电没保存和一次CSDNserver错误内容丢失之后终究还是完毕了这篇博客,实属不易。十分感谢能听我啰嗦到结尾~

PS:Demo界面并没有做的非常精致,仅仅是提供了一种思路。依照此思路能做出比較华丽的效果,授人以鱼不如授人以渔。

2016.10.12 在经历了上述种种磨难之后。最终发表了这篇博文。然而发表没几天,被我自己覆盖了。这下博文是真的丢了。心塞ing。今天没事百度下自己CSDN昵称“一口仨馍”,发现其它站点爬过这篇博文。随后我复制了自己原创的博文,再次发表。感谢那些爬我博文还不署名的站点。

谢谢你全家。

源代码下载

http://download.csdn.net/detail/qq_17250009/9484160

Android 手把手带你玩转自己定义相机的更多相关文章

  1. Java开发不懂Docker,学尽Java也枉然,阿里P8架构师手把手带你玩转Docker实战

    转: Java开发不懂Docker,学尽Java也枉然,阿里P8架构师手把手带你玩转Docker实战 Docker简介 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一 ...

  2. 手把手带你玩转 DialogFragment

    前言 本文已经收录到我的 Github 个人博客,欢迎大佬们光临寒舍: 我的 GIthub 博客 思维导图 一.为什么要学习 DialogFragment 你还在用 Dialog 吗? 你还在经常烦恼 ...

  3. 手把手教你玩转 CSS3 3D 技术

    css3的3d起步 要玩转css3的3d,就必须了解几个词汇,便是透视(perspective).旋转(rotate)和移动(translate).透视即是以现实的视角来看屏幕上的2D事物,从而展现3 ...

  4. 手把手教你玩转CSS3 3D技术

    手把手教你玩转 CSS3 3D 技术   要玩转css3的3d,就必须了解几个词汇,便是透视(perspective).旋转(rotate)和移动(translate).透视即是以现实的视角来看屏幕上 ...

  5. 【Android】自己定义相机的实现(支持连续拍照、前后摄像头切换、连续对焦)

    ~转载请注明http://blog.csdn.net/u013015161/article/details/46921257 介绍 这几天.写了一个自己定义照相机的demo.支持连续拍照和摄像头切换. ...

  6. Android性能优化:手把手带你全面实现内存优化

      前言 在 Android开发中,性能优化策略十分重要 本文主要讲解性能优化中的内存优化,希望你们会喜欢 目录   1. 定义 优化处理 应用程序的内存使用.空间占用 2. 作用 避免因不正确使用内 ...

  7. Android:手把手带你深入剖析 Retrofit 2.0 源码

    前言 在Andrroid开发中,网络请求十分常用 而在Android网络请求库中,Retrofit是当下最热的一个网络请求库 今天,我将手把手带你深入剖析Retrofit v2.0的源码,希望你们会喜 ...

  8. Android SurfaceView实战 带你玩转flabby bird (下)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/43063331,本文出自:[张鸿洋的博客] 1.概述 在Android Surfa ...

  9. 手把手带你做一个超炫酷loading成功动画view Android自定义view

    写在前面: 本篇可能是手把手自定义view系列最后一篇了,实际上我也是一周前才开始真正接触自定义view,通过这一周的练习,基本上已经熟练自定义view,能够应对一般的view需要,那么就以本篇来结尾 ...

随机推荐

  1. C#实现缩放字体

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  2. maven + sonar, gradle + sonar

    sonar installation and configuration Download sonar http://downloads.sonarsource.com/sonarqube/ Deco ...

  3. 便利的初始化view以及设置tag值

    便利的初始化view以及设置tag值 效果 源码 https://github.com/YouXianMing/iOS-Project-Examples 中的 SetRect // // Access ...

  4. End2endIT

    "C:\Program Files\Java\jdk1.8.0_112\bin\java" -ea -Didea.test.cyclic.buffer.size=1048576 & ...

  5. 在EditText中限制输入,自定义样式,监听输入的字符,自动换行

    自动获取焦点 <!-- 添加:<requestFocus /> 会自动获取焦点 --> <EditText android:layout_width="matc ...

  6. Plupload设置自定义参数

    在HTML 5比较流行的当下,Plupload是文件上传的不二之选,特别是Adobe宣布2020年将停止对Flash的更新支持.本文记录一下如何在上传文件的时候,传递自定义参数. 了解到两种方式,一种 ...

  7. SVG.js 元素操作整理(一)

    一.属性操作Attributes var draw = SVG('svg1').size(300, 300); //attr() 属性操作 //设置属性的值 var rect = draw.rect( ...

  8. 算法:快速排序实现 & 定制比较函数

    1. 快速排序基本算法 #include<stdio.h> ; int quick_sort(int *a, int start, int end){ if (start >= en ...

  9. [leetcode]Palindrome Partitioning @ Python

    原题地址:https://oj.leetcode.com/problems/palindrome-partitioning/ 题意: Given a string s, partition s suc ...

  10. WiFi十五岁了:感谢它能让我们在厕所上网

    无线俨然成为当下移动用户不可或缺的重要伴侣,在无线覆盖的区域意味着能够获取各种资讯或同好友进行即时通讯.无线带来了前所未有的技术变革,也颠覆了生活习惯,让用户可以访问Netflix或者在厕所浏览网页. ...