Android Api Demos登顶之路(七十)Graphics-->Camera Preview



/*
 * 这个demon演示了如何利用照相机取景,并将实现图像的预览功能。
 * 需要用到两个类,一个是Camera:照相机,用来管理和操作摄像头。
 * 一个是SurfaceView用来显示照相机取到的图像。SurfaceView允许在非UI线程中在其表面
 * 绘制图像,这样就避免了当绘制复杂的图像时会发生UI线程阻塞的问题。
 */
public class MainActivity extends Activity {
    public static final String TAG = "MainActivity";
    // 定义照相机
    private Camera mCamera;
    // 定义界面视图
    // 照相机的数量
    int numberOfCameras;
    // 当时锁定的照相机
    int cameraCurrentlyLocked;
    // 默认照相机的ID
    int defaultCameraId;
    private Preview mpreView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 设置显示模式为全屏、无标题
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
        // 1.设定应用的主界面,在这里我们自定义了一个视图容器,在这个容器中我们就放置了一个
        // SurfaceView组件用于显示在摄像头中取到的图像。
        mpreView=new Preview(this);
        setContentView(mpreView);

        //获取摄像头的数量
        numberOfCameras=Camera.getNumberOfCameras();
        //System.out.println(numberOfCameras);

        //获取默认摄像头的ID
        CameraInfo info=new CameraInfo();
        for(int i=0;i<numberOfCameras;i++){
            Camera.getCameraInfo(i, info);
            if(info.facing==CameraInfo.CAMERA_FACING_BACK){
                defaultCameraId=i;
            }
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        //设置为横屏显示否则图像方向不正确
        if(getRequestedOrientation()!=ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE){
              setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
             }
        //打开并设置摄像头
        mCamera=Camera.open();
        cameraCurrentlyLocked=defaultCameraId;
        mpreView.setCamera(mCamera);
    }

    @Override
    protected void onPause() {
        super.onPause();
        if(mCamera!=null){
            mpreView.setCamera(null);
            mCamera.release();
            mCamera=null;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater=getMenuInflater();
        inflater.inflate(R.menu.camera_menu, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.camera_menu:
            if(numberOfCameras==1){
                AlertDialog.Builder builder=new Builder(this);
                builder.setMessage("只有一个摄像头!")
                       .setNeutralButton("close", null)
                       .show();
                return true;
            }

            //释放掉当前的照相机
            if(mCamera!=null){
                mCamera.stopPreview();
                mpreView.setCamera(null);
                mCamera.release();
                mCamera=null;
            }

            //切换到一个新的相机
            mCamera=Camera.open((cameraCurrentlyLocked+1)%numberOfCameras);
            cameraCurrentlyLocked=(cameraCurrentlyLocked+1)%numberOfCameras;
            mpreView.switchCamera(mCamera);
            mCamera.startPreview();
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }

    }

    // 定义一个视图容器,作为应用的主界面。这也是这个Demo中最核心的部分。
    private class Preview extends ViewGroup implements Callback {
        // 定义用于显示图像的SurfaceView
        private SurfaceView mSurfaceView;
        // 定义一个用于控制和管理SurfaceView的句柄,照相机是通过使用SurfaceHolder句柄来
        // 使用SurfaceView的。SurfaceHolder通过回调接口实现对SurfaceView的操作和管理
        private SurfaceHolder mHolder;
        // 定义预览图像的尺寸,该类位于android.hardware.Camera.Size;是Camera的一个内部类
        // 是照相机用于控制和管理一些和图像的大小相关操作的类。
        private Size mPreviewSize;
        // 照相机支持的所有的预览尺寸
        private List<Size> mSupportedPreviewSizes;
        @SuppressWarnings("deprecation")
        /*
         * 使用照相机需要添加权限: <uses-permission
         * android:name="android.permission.CAMERA" /> <uses-feature
         * android:name="android.hardware.camera" /> <uses-feature
         * android:name="android.hardware.camera.autofocus" />
         */
        private Camera camera;

        public Preview(Context context) {
            super(context);
            // 创建一个SurfaceView并将其添加到容器当中
            mSurfaceView = new SurfaceView(context);
            addView(mSurfaceView);

            // 从SurfaceView当中取得控制句柄
            mHolder = mSurfaceView.getHolder();
            // 为SurfaceHolder添加回调方法,用于控制和管理SurfaceView
            mHolder.addCallback(this);
            // 设置SurfaceHolder的类型表示由Camera管理缓存中的图像,实现图像预览
            // 不过这个方法已经过时,当需要时应用会自动添加
            // mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        }

        @SuppressWarnings("deprecation")
        public void setCamera(Camera camera) {
            this.camera = camera;
            if (camera != null) {
                mSupportedPreviewSizes = camera.getParameters()
                        .getSupportedPreviewSizes();
                //System.out.println(mSupportedPreviewSizes.size());
                // 通过View布局被修改了,需要使修改立即生效。
                requestLayout();
            }
        }

        // 切换摄像头的方法
        public void switchCamera(Camera camera) {
            setCamera(camera);
            // 显示图像的预览
            try {
                camera.setPreviewDisplay(mHolder);
            } catch (IOException e) {
                Log.e(TAG, "IOException caused by setPreviewDisplay()", e);
            }

            // 修改照相机的参数设置
            Camera.Parameters parameters = camera.getParameters();
            parameters.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
            requestLayout();
            camera.setParameters(parameters);
        }

        /*
         * 重写onMeasure方法,用于测量容器的尺寸,这里我们并不关心子组件的大小,因为我们只需要居中显示照相机的预览图像即可
         */
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // 该方法将根据不同的模式,从两个参数中选择一个合适的尺寸返回
            int width = resolveSize(getSuggestedMinimumWidth(),
                    widthMeasureSpec);
            int height = resolveSize(getSuggestedMinimumHeight(),
                    heightMeasureSpec);
            /*System.out.println("width:"+width);
            System.out.println("height:"+height);*/
            // 设置容器的尺寸
            setMeasuredDimension(width, height);
            // 获取当前使用的预览尺寸(最合适的)
            if (mSupportedPreviewSizes != null) {
                mPreviewSize = getOptimalPreviewSize(mSupportedPreviewSizes,
                        width, height);
            }
        }

        private Size getOptimalPreviewSize(List<Size> sizes,
                int width, int height) {
            //设置精确度
            final double ASPECT_TOLERANCE=0.1;
            //定义容器的宽高比
            double targetRatio=width/height;
            if(sizes==null){
                return null;
            }
            Size optimalSize=null;
            double minDiff=Double.MAX_VALUE;
            int targetHeight=height;

            for(Size size:sizes){
                double ratio=size.width/size.height;
                //如果误差值大于设定的精度,则结束当前循环进入下一次循环
                if(Math.abs(ratio-targetRatio)>ASPECT_TOLERANCE){
                    continue;
                }
                if(Math.abs(size.height-targetHeight)<minDiff){
                    optimalSize=size;
                    minDiff=Math.abs(size.height-targetHeight);
                }
            }

            //如果找不到满足精度要求的,则忽视对精度的判断
            if(optimalSize==null){
                minDiff=Double.MAX_VALUE;
                for(Size size:sizes){
                    if(Math.abs(size.height-targetHeight)<minDiff){
                        optimalSize=size;
                        minDiff=Math.abs(size.height-targetHeight);
                    }
                }
            }
            return optimalSize;
        }

        /*
         * 重写onLayout方法设定窗口的布局,因为在该窗口中我们只有一个SurfaceView
         * 只需要让它在窗口中居中显示就可以了
         */
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            if(changed && getChildCount()>0){
                View child=getChildAt(0);
                //获取窗口的宽和高
                int width=right-left;
                int height=bottom-top;
                child.layout(left, top, right, bottom);

                int previewWidth=width;
                int previewHeight=height;
                if(mPreviewSize!=null){
                    previewWidth=mPreviewSize.width;
                    previewHeight=mPreviewSize.height;
                }
                /*System.out.println("width:"+width);
                System.out.println("height:"+height);
                System.out.println("previewWidth:"+previewWidth);
                System.out.println("previewHeight:"+previewHeight);*/
                //如果实际的预览视图的高度大于根据窗口的高宽比计算出的预览视图的高度
                //就需要缩小预览视图以适应容器的大小。此时将高度设置到容器的高度,宽度就会变小
                //就需要对宽度进行居中显示
                if(previewHeight>(height/width)*previewWidth){
                    int scaledChildWidth=previewWidth*height/previewHeight;
                    child.layout((width-scaledChildWidth)/2, 0, (width+scaledChildWidth)/2, height);
                    //System.out.println("scaledChildWidth:"+scaledChildWidth);
                }else{
                    //注意一定要把乘放在前面否则得到的结果为0
                    int scaledChildHeight=previewHeight*width/previewWidth;
                    System.out.println("scaledChildHeight:"+scaledChildHeight);
                    child.layout(0, (height-scaledChildHeight)/2, width, (height+scaledChildHeight)/2);
                }
            }
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // 设置照相机的预览视图
            if(camera!=null){
                try {
                    camera.setPreviewDisplay(holder);
                } catch (IOException e) {
                     Log.e(TAG, "IOException caused by setPreviewDisplay()", e);
                }
            }
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
            Camera.Parameters params=camera.getParameters();
            params.setPreviewSize(mPreviewSize.width, mPreviewSize.height);
            requestLayout();
            camera.setParameters(params);
            camera.startPreview();
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            if(camera!=null){
                camera.stopPreview();
            }
        }

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值