zoukankan      html  css  js  c++  java
  • 第 20 章 相机 II:拍摄并处理照片

    请参考教材,全面理解和完成本章节内容... ...

    本章将从相机预览里拍摄照片并保存为JPEG格式的本地文件。然后,将照片与Crime关联起来并显示在CrimeFragment的视图中。如果需要,用户也可以选择在DialogFragment中查看大尺寸版本的图片,如图20-1所示。

    image

    图20-1 Crime的缩略图以及大尺寸图片展示

    20.1 拍摄照片

    首先,我们来升级CrimeCameraFragment的布局,为其添加一个进度指示条组件。相机拍摄照片的过程可能比较耗时,有时需要用户等一会儿,为了不让用户失去耐心,添加进度指示条非常有必要。

    在fragment_crime_camera.xml布局文件中,参照图20-2,添加FrameLayoutProgressBar组件。

    image

    20-2 添加FrameLayoutProgressBar组件(fragment_crime_camera.xml

    代替默认的普通大小圆形进度环,@android:style/Widget.ProgressBar.Large样式将创建一个粗大的圆形旋转进度环,如图20-3所示。

    image

    20-3 旋转的进度环

    FrameLayout(包括它的ProgressBar子组件)的初始状态设置为不可见。只有在用户点击Take!按钮开始拍照时才可见。

    返回到CrimeCameraFragment.java中,为FrameLayout组件添加成员变量,然后通过资源ID引用它并设置为不可见状态,如代码清单20-1所示。

    代码清单20-1 配置使用FrameLayout视图(CrimeCameraFragment.java)

    image

    20.1.1 实现相机回调方法

    既然进度环的添加设置已完成,接下来实现从相机的实时预览中捕获一帧图像,然后将它保存为JPEG格式的文件。要拍摄一张照片,需调用以下见名知意的Camera方法:

    public final void takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg)

    在CriminalIntent应用中,实现ShutterCallback回调方法以及JPEG版本的PictureCallback回调方法。图20-4展示了这些对象之间的交互关系。

    image

    20-4 在CrimeCameraFragment中拍照

    下面是需要实现的两个接口,每个接口含有一个待实现的方法:

    public static interface Camera.ShutterCallback {

    public abstract void onShutter();

    }

    public static interface Camera.PictureCallback {

    public abstract void onPictureTaken (byte[] data, Camera camera);

    }

    在CrimeCameraFragment.java中,实现Camera.ShutterCallback接口显示进度环视图,实现Camera.PictureCallback接口命名并保存已拍摄的JPEG图片文件,如代码清单20-2所示。

    代码清单20-2 实现传入takePicture(...)方法的接口(CrimeCameraFragment.java)

    image

    onPictureTaken(...)方法中,创建了一个UUID字符串作为图片文件名。然后,使用Java I/O类打开一个输出流,将从Camera传入的JPEG数据写入文件。如果一切操作顺利,程序会输出一条文件保存成功的日志。

    完成了回调方法的处理,接下来就是修改Take!按钮的监听器方法,实现对takePicture(...)方法的调用。对于没有实现的接收处理原始图像数据的回调方法,记得传入null值,如代码清单20-3所示。

    代码清单20-3 实现takePicture(...)按钮单击事件方法 (CrimeCameraFragment.java)

    image

    20.1.2 设置图片尺寸大小

    相机需要知道创建多大尺寸的图片。设置图片尺寸与设置预览尺寸一样。可以调用以下Camera.Parameters方法获得可用的图片尺寸的列表:

    public List<Camera.Size> getSupportedPictureSizes()

    surfaceChanged(...)方法中,使用getBestSupportedSize(...)方法获得支持的适用于Surface的图片尺寸。最后将获得的尺寸设置为相机要创建的图片尺寸,如代码清单20-4所示。

    代码清单20-4 调用getBestSupportedSize(...)方法设置图片尺寸(CrimeCameraFragment.java)

    image

    运行CriminalIntent应用,然后点击Take!按钮。在LogCat中,创建一个以CrimeCameraFragment为标签的过滤器,查看图片文件的保存位置。

    目前为止,CrimeCameraFragment类完全具备了拍照及保存文件的功能。相机API的相关开发工作全部完成了。本章接下来的部分将重点介绍CrimeFragment类的开发完善,从而将图片与应用的其他部分进行整合。

    20.2 返回数据给 CrimeFragment

    为了让CrimeFragment类使用图片,需要将文件名从CrimeCameraFragment回传给它。图20-5展示了CrimeFragmentCrimeCameraFragment之间的交互过程。

    image

    20-5 使用CrimeCameraActivity设置回传信息

    首先,CrimeFragment以接收返回值的方式启动CrimeCameraActivity。图片拍摄完成后,CrimeCameraFragment会以图片文件名作为extra创建一个intent,并调用setResult(...)方法。然后,ActivityManager会调用onActivityResult(...)方法将intent转发给CrimePagerActivity。最后,CrimePagerActivityFragmentManager会调用CrimeFragment.onActivityResult(...)方法,将intent转发给CrimeFragment

    20.2.1 以接收返回值的方式启动CrimeCameraActivity

    当前,CrimeFragment只是直接启动CrimeCameraActivity。在CrimeFragment.java中,新增一个请求码常量,然后修改拍照按钮的监听器方法,以需要接收返回值的方式启动CrimeCameraActivity,如代码清单20-5所示。

    代码清单20-5 以接收返回值的方式启动CrimeCameraActivity(CrimeFragment.java)

    image

    20.2.2 在CrimeCameraFragment中设置返回值

    CrimeCameraFragment会将图片文件名放置在extra中并附加到intent上,然后传入CrimeCameraActivity.setResult(int, Intent)方法。在CrimeCameraFragment.java中,新增一个extra常量。然后,在onPictureTaken(...)方法中,判断照片处理状态,如果照片保存成功,就创建一个intent并设置结果代码为RESULT_OK,反之,则设置结果代码为RESULT_CANCELED,如代码清单20-6所示。

    代码清单20-6 新增照片文件名extra(CrimeCameraFragment.java)

    image

    20.2.3 在CrimeFragment中获取照片文件名

    最后,CrimeFragment会使用照片文件名更新CriminalIntent应用的模型层和视图层。在CrimeFragment.java中,覆盖onActivityResult(...)方法,检查结果并获取照片文件名。然后,为CrimeFragment类新增一个用于日志记录的TAG,如果照片文件名获取成功,就输出结果日志,如代码清单20-7所示。

    代码清单20-7 获取照片文件名(CrimeFragment.java)

    image

    运行CriminalIntent应用。在CrimeCameraActivity中拍摄一张照片。然后检查LogCat,确认CrimeFragment成功获取了照片文件名。

    有了CrimeFragment获取的照片文件名,接下来还有一些事情要做。

    更新模型层:首先需编写一个封装照片文件名的Photo类。还需给Crime类添加一个Photo类型的mPhoto属性。CrimeFragment将使用照片文件名创建一个Photo对象,然后使用它设置CrimemPhoto属性。

    更新CrimeFragment的视图:需要为CrimeFragment的布局增加一个ImageView组件,然后在ImageView视图上显示Crime的照片缩略图。

    显示全尺寸版的图片:需要创建一个名为ImageFragmentDialogFragment子类,然后使用它显示指定路径的照片。

    20.3 更新模型层

    图20-6展示了CrimeFragmentCrime以及Photo类三者之间的关系。

    image

    图20-6 模型层对象与CrimeFragment

    20.3.1 新增Photo

    以默认的java.lang.Object为超类,在com.bignerdranch.android.criminalintent包中创建一个名为Photo的新类。

    在Photo.java中,参照代码清单20-8添加需要的变量和方法。

    代码清单20-8 Photo新建类(Photo.java)

    image

    注意,Photo类有两个构造方法。第一个构造方法根据给定的文件名创建一个Photo对象。第二个构造方法是一个JSON序列化方法,在保存以及加载Photo类型的数据时,Crime会用到它。

    20.3.2 为Crime添加photo属性

    现在,我们来更新Crime类,包含一个Photo对象并将其序列化为JSON格式,如代码清单20-9所示。

    代码清单20-9 Crime照片(Crime.java)

    image

    20.3.3 设置photo属性

    在CrimeFragment.java中,修改onActivityResult(...)方法,在其中新建一个Photo对象并设置给当前的Crime,如代码清单20-10所示。

    代码清单20-10 处理新照片(CrimeFragment.java)

    image

    运行CriminalIntent应用并拍摄一张照片。然后查看LogCat,确认Crime拥有这张新拍的照片。

    可能有人会问,为什么要创建一个Photo类,而不是简单地添加一个文件名属性给Crime类。直接添加文件名属性虽然可行,但新建Photo类可以帮助处理更多任务,如显示照片名称或处理触摸事件。显然,要处理这些事情,我们需要一个单独的类。

    20.4 更新 CrimeFragment 的视图

    完成了模型层的更新,我们来着手更新CrimeFragment的视图层。特别要提到的是,CrimeFragment将会在ImageView上显示照片缩略图。

    image

    20-7 添加了ImageView组件的CrimeFragment

    20.4.1 添加ImageView组件

    打开layout/fragment_crime.xml布局文件,对照图20-8添加ImageView组件。

    image

    20-8 添加了ImageView组件的CrimeFragment布局

    我们还需要一个带有ImageView组件的水平模式布局,如图20-9所示。

    image

    20-9 带有ImageView组件的水平模式布局(layout-land/fragment_crime.xml

    在CrimeFragment.java中,创建一个成员变量,然后在onCreateView(...)方法中以资源ID引用ImageView视图,如代码清单20-11所示。

    代码清单20-11 配置ImageButtonCrimeFragment.java

    image

    预览修改后的布局,或者运行CriminalIntent应用,确保ImageView组件已正确添加。

    20.4.2 图像处理

    相机拍摄的照片尺寸通常都很大,需要预先处理,然后才能在ImageView视图上显示。手机制造商每年新推出的手机都带有越来越强大的相机。对用户来说,这是好事。但对于开发者来说,这很让人头痛。

    目前,主流Android手机都带有800万像素的照相机组件。大尺寸的图片很容易耗尽应用的内存。因此,加载图片前,需要编写代码缩小图片。图片使用完毕,也需要编写代码清理删除它。

    添加处理过的图片到imageview视图

    创建一个名为PictureUtils的新类,然后,在PictureUtils.java中,添加如代码清单20-12所示的方法,将图片缩放到设备默认的显示尺寸。

    代码清单20-12 添加PictureUtils类(PictureUtils.java)

    image

    注意,Display.getWidth()Display.getHeight()方法已被弃用。本章末尾将介绍更多有关代码弃用的知识。

    如果能将图片缩放至完美匹配ImageView视图的尺寸,那自然最好了。然而,我们通常无法及时获得用来显示图片的视图尺寸。例如,在onCreateView(...)方法中,就无法获得ImageView视图的尺寸。设备的默认屏幕大小是固定可知的,因此,稳妥起见,可以缩放图片至设备的默认显示屏大小。注意,用来显示图片的视图可能会小于默认的屏幕显示尺寸,但大于屏幕默认的显示尺寸则肯定不行。

    接下来,在CrimeFragment类中,新增一个私有方法,将缩放后的图片设置给ImageView视图,如代码清单20-13所示。

    代码清单20-13 添加showPhoto()方法(CrimeFragment.java)

    image

    在CrimeFragment.java中,新增onStart()实现方法,只要CrimeFragment的视图一出现在屏幕上,就调用showPhoto()方法显示图片,如代码清单20-14所示。

    代码清单20-14 加载图片(CrimeFragment.java)

    image

    CrimeFragment.onActivityResult(...)方法中,同样调用showPhoto()方法,以确保用户从CrimeCameraActivity返回后,ImageView视图可以显示用户所拍照片,如代码清单20-15所示。

    代码清单20-15 在onActivityResult(...)方法中调用showPhoto()方法(CrimeFragment.java)

    image

    卸载图片

    PictureUtils类中添加清理方法,清理ImageViewBitmapDrawable,如代码清单20-16所示。

    代码清单20-16 清理工作(PictureUtils.java)

    image

    Bitmap.recycle()方法的调用需要一些解释。Android开发文档暗示不需要调用Bitmap.recycle()方法,但实际上需要。因此,下面给出技术说明。

    Bitmap.recycle()方法释放了bitmap占用的原始存储空间。这也是bitmap对象最核心的部分。(取决于具体的Android系统版本,原始存储空间可大可小。Honeycomb以前,它存储了Java Bitmap的所有数据。)

    如果不主动调用recycle()方法释放内存,占用的内存也会被清理。但是,它是在将来某个时点在finalizer中清理,而不是在bitmap自身的垃圾回收时清理。这意味着很可能在finalizer调用之前,应用已经耗尽了内存资源。

    finalizer的执行有时不太靠谱,且这类bug很难跟踪或重现。因此,如果应用使用的图片文件很大,最好主动调用recycle()方法,以避免可能的内存耗尽问题。

    CrimeFragment类中,添加onStop()方法,并在其中调用cleanImageView(...)方法清理内存,如代码清单20-17所示。

    代码清单20-17 卸载图片(CrimeFragment.java)

    image

    onStart()方法中加载图片,然后在onStop()方法中卸载图片是一种好习惯。这些方法标志着用户可以看到activity的时间点。如果改在onResume()方法和onPause()方法中加载和卸载图片,用户体验可能会很糟糕。

    暂停的activity也可能部分可见,比如说,非全屏的activity视图显示在暂停的activity视图之上时。如果使用了onResume()方法和onPause()方法,那么图像消失后,因为没有被全部遮住,它又显示在了屏幕上。所以说,最佳实践就是,activity的视图一出现时就加载图片,然后等到activity再也不可见的情况下,再对它们进行卸载。

    运行CriminalIntent应用。拍摄一张照片并确认它显示在Imageview视图上。然后退出应用并重新启动它。确认进入同一Crime明细界面时,Imageview视图上的图片仍可正常显示。

    按照CrimeCameraActivity的初始显示方向,最好是以水平模式进行拍照。然而,如果不小心使用了竖直模式,拍照按钮上的图片可能无法按正确的方向显示。请通过本章第一个挑战练习修正该问题。

    20.5 在 DialogFragment 中显示大图片

    本章,CriminalIntent应用开发的最后环节是让用户查看Crime的大尺寸照片,如图20-10所示。

    image

    图20-10 显示较大图片的DialogFragment

    以support.v4.DialogFragment为父类,创建一个名为ImageFragment的新类。

    ImageFragment类需要知道Crime照片的文件路径。在ImageFragment.java中,新增一个newInstance(String)方法,该方法接受照片文件路径并放置到argument bundle中,如代码清单20-18所示。

    代码清单20-18 创建ImageFragment(ImageFragment.java)

    image

    通过设置fragment的样式为DialogFragment.STYLE_NO_TITLE,获得一个如图20-10所示的 简洁用户界面。

    ImageFragment不需要显示AlertDialog视图自带的标题和按钮。如果fragment不需要显示 标题和按钮,要实现显示大图片的对话框,采用覆盖onCreateView(...)方法并使用简单视图 的方式,要比覆盖onCreateDialog(...)方法并使用Dialog更简单、快捷且灵活。

    在ImageFragment.java中,覆盖onCreateView(...)方法创建ImageView并从argument获取文 件路径。然后获取缩小版的图片并设置给ImageView。最后,只要图片不再需要,就主动覆盖onDestroyView()方法以释放内存,如代码清单20-19所示。

    代码清单20-19 创建ImageFragment(ImageFragment.java)

    image

    最后,我们需要从CrimeFragment弹出显示图片的对话框。在CrimeFragment.java中,添加一个监听器方法给mPhotoView。在实现方法里,创建一个ImageFragment实例,然后通过调用ImageFragment的show(...)方法,将它添加给CrimePagerActivity的FragmentManager。另外,还需要一个字符串常量,用来唯一定位FragmentManager中的ImageFragment,如代码清单20-20所示。

    代码清单20-20 显示ImageFragment界面(CrimeFragment.java)

    image

    运行CriminalIntent应用。拍摄一张照片,确认可以清楚地看到那些令人震惊的案发现场照。

  • 相关阅读:
    springcloud 项目源码 微服务 分布式 Activiti6 工作流 vue.js html 跨域 前后分离
    springcloud 项目源码 微服务 分布式 Activiti6 工作流 vue.js html 跨域 前后分离
    OA办公系统 Springboot Activiti6 工作流 集成代码生成器 vue.js 前后分离 跨域
    java企业官网源码 自适应响应式 freemarker 静态引擎 SSM 框架
    java OA办公系统源码 Springboot Activiti工作流 vue.js 前后分离 集成代码生成器
    springcloud 项目源码 微服务 分布式 Activiti6 工作流 vue.js html 跨域 前后分离
    java 视频播放 弹幕技术 视频弹幕 视频截图 springmvc mybatis SSM
    最后阶段总结
    第二阶段学习总结
    第一阶段学习总结
  • 原文地址:https://www.cnblogs.com/jlxuqiang/p/4758907.html
Copyright © 2011-2022 走看看