知方号

知方号

Android相机开发(四): 旋转与纵横比

转载自:Penguin

Android Camera Develop: orientation/rotation and aspect ratio

概述

接上篇,在实现了相机的基础功能后,着眼于解决预览、拍照与录像时屏幕的旋转,以及预览时的纵横比等问题。上篇实现的相机APP只能以横屏方向预览、拍照和录像,在实际使用时会很不方便;本篇就会实现APP随设备的旋转而旋转,且可以在任意旋转角度上进行预览、拍照和录像。上篇实现的相机APP中,预览画面是充满整个屏幕的,即使调整预览分辨率后,预览画面所占尺寸也不变,这样会造成实际画面被拉长或压扁,十分不友好;本篇会实现预览画面纵横比与分辨率纵横比保持相同,即预览画面不会出现拉长或压扁的情况。

预览画面随设备旋转

如果只是简单地将AndroidManifest中横屏锁去掉,虽然可以实现APP随设备旋转,但预览画面不会随之旋转,如下图所示:

Screenshot Preview LandscapeScreenshot Preview Portrait Old

原因主要在于Camera的预览显示方向需要单独设置,不会随着设备的旋转而自动改变。而通过设置Camera的预览显示方向,可以自由指定旋转的角度。

解除旋转锁定

在AndroidManifest.xml中删去

XML

android:screenOrientation="landscape"

这样就解除了之前设定的APP的旋转锁定(注意与设备的旋转锁定不同),现在APP可以随设备旋转而旋转了。

计算旋转角度

相机预览的旋转角度需要根据相机预览目前的旋转角度,以及设备屏幕的旋转角度计算得到,不过还好Android官方给了示例代码。

在CameraPreview中加入

Java

public int getDisplayOrientation() { Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); int rotation = display.getRotation(); int degrees = 0; switch (rotation) { case Surface.ROTATION_0: degrees = 0; break; case Surface.ROTATION_90: degrees = 90; break; case Surface.ROTATION_180: degrees = 180; break; case Surface.ROTATION_270: degrees = 270; break; } android.hardware.Camera.CameraInfo camInfo = new android.hardware.Camera.CameraInfo(); android.hardware.Camera.getCameraInfo(Camera.CameraInfo.CAMERA_FACING_BACK, camInfo); int result = (camInfo.orientation - degrees + 360) % 360; return result;}

getDisplayOrientation()用来获取相机预览需要旋转的角度。前面一部分获得设备屏幕的旋转角度,即由重力传感器自动旋转屏幕的角度;然后得到相机原先的旋转角度camInfo.orientation,最后通过运算得到新的相机预览需要旋转的角度。

预览画面随设备旋转

设备可能经常在旋转,那什么时候将计算得到的旋转角度应用到相机呢?当然是要等待相应事件触发了。还记得之前一直没有派上用场的surfaceChanged()吗?现在就要用到它了,surfaceChanged()会在surface的大小或格式发生改变时调用,而屏幕的旋转恰恰会使得surface大小发生变化(横屏变为竖屏、竖屏变为横屏,surface的长宽都会发生变化),所以调整相机预览旋转角度就在这里了。

在surfaceChanged()中加入

Java

int rotation = getDisplayOrientation();mCamera.setDisplayOrientation(rotation);

即surfaceChanged()触发后,计算相机预览需要旋转的角度rotation,再通过setDisplayOrientation()将此角度应用到Camera。

运行试试

现在运行APP,试试旋转设备,相机预览也随之旋转了,不会再出现之前的怪异画面了。

Screenshot Preview Portrait New优化UI布局

有没有发现上图中在竖屏下控制按钮在屏幕右边很别扭?这是因为竖屏下APP仍然应用的横屏下的布局,本意是为了让布局的相对位置不随旋转而改动,但在这里明显不满足我们的需求了。我们考虑为APP配置横屏和竖屏两个布局,让Android根据屏幕方向自动选择。

新建横屏布局文件

双击打开activity_main.xml,点击面板左下角切换到Design界面,在如下图所示处点击Create Landscape Variation。

CreateLandscapeLayout

这样在项目文件列表中,activity_main.xml就会变成一个文件夹,其内含有两个activity_main.xml文件,其中有(land)的是横屏布局文件,另一个是竖屏布局文件。

加入竖屏布局

新建的横屏布局文件自动填充了之前的布局内容,所以横屏布局就不用修改了。

竖屏布局文件内容修改为:

XML

按照标准,不应该在android:text中直接写入文字,而应当在res/values/strings.xml中写入,在layout中引用。本APP实际也是这样写的,但此处为演示方便,还是直接在layout中写入

运行试试

运行APP,此时在横屏下还是之前的布局,但在竖屏下已经应用新的布局了,如下图所示

Screenshot Preview Portrait New Layout拍照与录像随预览旋转

现在预览和界面没问题了,但如果你拍张照,或者录像的话会发现照片或视频的方向没有随着预览方向而改变,也就是说不是“所见即所得”,现在来着手解决这个问题。

这个问题不是bug,而是Android故意的,我们也是需要手动指定拍照和录像的旋转角度。

修改拍照旋转角度

照片的旋转角度是在Camera的Parameters中指定的。这里有一个很困惑的地方,我们之前设置的setDisplayOrientation()是直接对Camera操作的,指定预览的旋转角度;而在Parameters中,有个方法setRotation()同样也是设置旋转角度,这两个有什么区别呢?这里我们不作深入追究,可以理解为setRotation()是设置预览帧数据,以及拍摄照片的方向,而setDisplayOrientation()仅设置预览显示的方向。

所以现在要做的就是在修改预览显示旋转角度时,同时设置拍照旋转角度。在surfaceChanged()中加入

Java

Camera.Parameters parameters = mCamera.getParameters();parameters.setRotation(rotation);mCamera.setParameters(parameters);

即首先获取Parameters,设置旋转角度,再将Parameters应用到Camera。

这样拍照得到的照片就和预览的方向一致了。

修改录像旋转角度

因为录像是交给MediaRecorder实际实现的,所以应当是给MediaRecorder设置旋转参数。

在prepareVideoRecorder()中,mMediaRecorder.prepare()之前加入

Java

int rotation = getDisplayOrientation();mMediaRecorder.setOrientationHint(rotation);

首先得到旋转角度,然后通过setOrientationHint()应用到MediaRecorder,只需要注意在prepare()前应用就好了。

这样录像得到的视频就和预览的方向一致了。

注意正如setOrientationHint()中的Hint所表示的,视频的旋转并不是编码层面的旋转,视频帧数据并没有发生旋转,而只是在视频中增加了参数,希望播放器按照指定的旋转角度旋转后播放,所以具体效果因播放器而异

实时调整预览纵横比Aspect Ratio

如上图所示,黑色矩形框为屏幕,灰色矩形框为SurfaceView的父级FrameLayout。我们的APP现在是SurfaceView充满整个FrameLayout即灰色矩形框,而这样会造成实际画面被拉长或压扁。解决方法是将SurfaceView的纵横比与预览分辨率的纵横比调整为相同,这样从屏幕上看到拍摄到的物体与实际情况就只是产生了缩放,而不会出现变形。为了达到最好的显示效果,我们希望SurfaceView能尽可能充满FrameLayout,如图中红色和绿色矩形框所示。通过观察可以很快发现,如果预览分辨率的纵横比大于FrameLayout的纵横比,则将SurfaceView的纵向长度设定为FrameLayout的纵向长度,而SurfaceView的横向长度由纵横比计算得到,如图中绿色矩形框所示;反之如红色矩形框所示。

计算尺寸

在CameraPreview中加入

Java

private void adjustDisplayRatio(int rotation) { ViewGroup parent = ((ViewGroup) getParent()); Rect rect = new Rect(); parent.getLocalVisibleRect(rect); int width = rect.width(); int height = rect.height(); Camera.Size previewSize = mCamera.getParameters().getPreviewSize(); int previewWidth; int previewHeight; if (rotation == 90 || rotation == 270) { previewWidth = previewSize.height; previewHeight = previewSize.width; } else { previewWidth = previewSize.width; previewHeight = previewSize.height; } if (width * previewHeight > height * previewWidth) { final int scaledChildWidth = previewWidth * height / previewHeight; layout((width - scaledChildWidth) / 2, 0, (width + scaledChildWidth) / 2, height); } else { final int scaledChildHeight = previewHeight * width / previewWidth; layout(0, (height - scaledChildHeight) / 2, width, (height + scaledChildHeight) / 2); }}

首先得到SurfaceView的父级parent(在这里就是FrameLayout),记录父级的长和宽;然后得到预览分辨率(需要注意处理横屏和竖屏的问题),通过比较两者的纵横比,确定SurfaceView的调整方法,计算出需要调整的长度,并使SurfaceView居中;最后通过layout()将新的SurfaceView的位置应用到布局中,完成纵横比的调整。

应用新的尺寸

预览分辨率的变化也会触发surfaceChanged(),所以调用adjustDisplayRatio()也是在surfaceChanged()中。在surfaceChanged()最后加入

Java

adjustDisplayRatio(rotation);运行试试

运行APP,马上就能发现相机预览不再是充满整个屏幕了,而是在边界有一定的空白,这样就保持了与预览分辨率相同的纵横比。另外,如果在预览的设置中修改了预览分辨率,新的纵横比也会立即应用到屏幕显示中。如下所示

Screenshot Aspect Ratio美化

以上完成了本篇需要实现的全部功能。这里提一点APP的美化,这里只是指出进行美化的地方,具体细节参见DEMO。

布局

首先可以将整个布局背景设置为黑色,使布局空白地方为黑色,像电影一样。在activity_main中,LinearLayout中加入

XML

android:background="@color/black"

其次将控制部分背景颜色区分开,同时将控制部分预留一部分边界,使布局结构明显。在activity_main中,RelativeLayout中加入

XML

android:background="@color/darkGray"android:padding="5dp"偏好设置文本颜色

偏好设置文本颜色默认为黑色,对于相机预览来说不容易分辨,将文本颜色设置为白色更好。这里我们给SettingsFragment设置一个主题。

在res/valuse/styles.xml中,resources下加入

XML

#FFF #FFF

创建一个名为PreferenceTheme的主题,主题只是修改文本颜色为纯白。

在SettingsFragment的onCreate()中,addPreferencesFromResource()之后加入

XML

getActivity().setTheme(R.style.PreferenceTheme);

即应用此主题。

这样偏好设置文本颜色就变为白色了,容易区分多了吧!

一点唠叨

本篇完成后,这个相机APP就离实用的APP更近了一步,甚至已经满足了大多数的需求。其实在旋转与纵横比的实现上,需要仔细研究诸如继承关系、生命周期等的问题,但

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至lizi9903@foxmail.com举报,一经查实,本站将立刻删除。