第 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应用。拍摄一张照片,确认可以清楚地看到那些令人震惊的案发现场照。

原文地址:https://www.cnblogs.com/jlxuqiang/p/4758907.html