如何实现Pixel相机旋转时视图位置固定效果?(CameraX+ViewPager2)
实现Pixel 7相机式旋转行为:保持视图位置固定并适配系统状态栏/导航栏
核心需求回顾
- 竖屏转横屏时,状态栏、导航栏跟随系统切换到对应位置
- 相机按钮等UI元素保持相对预览区域的位置不变,仅旋转90度
- 可选:CameraX预览无中断
- 排除方案:锁定竖屏仅旋转图标(会影响其他Fragment的正常旋转)
一、解决布局重绘偏移:禁用系统重建+手动处理旋转
步骤1:配置Manifest避免系统重建
在对应Activity的Manifest节点中添加:
android:configChanges="orientation|screenSize|smallestScreenSize|layoutDirection"
这样系统不会因旋转重建Activity/Fragment,从根源避免布局重绘导致的位置错乱。
步骤2:手动处理旋转逻辑
在Activity或相机Fragment中重写onConfigurationChanged,分别处理CameraX预览方向、UI元素旋转与位置适配:
override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) // 1. 更新CameraX预览方向,保证预览画面正确旋转 val targetRotation = when(newConfig.orientation) { Configuration.ORIENTATION_LANDSCAPE -> Surface.ROTATION_90 Configuration.ORIENTATION_PORTRAIT -> Surface.ROTATION_0 else -> Surface.ROTATION_0 } preview?.targetRotation = targetRotation // 2. 旋转UI按钮,保持相对位置 val rotationDegree = if(newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) 90f else 0f captureBtn.rotation = rotationDegree galleryBtn.rotation = rotationDegree // 3. 调整按钮布局参数,保证相对预览区域的位置不变 adjustButtonLayoutParams(newConfig.orientation) } private fun adjustButtonLayoutParams(orientation: Int) { val captureParams = captureBtn.layoutParams as ConstraintLayout.LayoutParams if(orientation == Configuration.ORIENTATION_LANDSCAPE) { // 竖屏时的底部边距转为横屏时的右侧边距,保持距离预览边缘的一致 captureParams.rightMargin = captureParams.bottomMargin captureParams.bottomMargin = 0 // 切换约束:从底部对齐改为右侧对齐 captureParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID captureParams.bottomToBottom = ConstraintLayout.LayoutParams.UNSET } else { captureParams.bottomMargin = captureParams.rightMargin captureParams.rightMargin = 0 captureParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID captureParams.endToEnd = ConstraintLayout.LayoutParams.UNSET } captureBtn.layoutParams = captureParams }
二、关于横竖屏两套布局的选择
如果你的相机布局极度复杂,手动调整参数容易出错,可以考虑维护两套布局:
- 在
res/layout-land目录下创建横屏布局文件,保证按钮在横屏时的相对位置和竖屏一致(比如竖屏底部居中→横屏右侧居中) - 在
onConfigurationChanged中手动加载对应布局:
private fun reloadCameraLayout(orientation: Int) { val layoutRes = if(orientation == Configuration.ORIENTATION_LANDSCAPE) { R.layout.fragment_camera_land } else { R.layout.fragment_camera_port } binding.root.removeAllViews() binding = FragmentCameraBinding.inflate(layoutInflater, binding.root, true) // 重新绑定CameraX用例和按钮事件 bindCameraUseCases() }
这种方式的优势是可视化布局更直观,缺点是需要同步维护两套布局的适配逻辑。
三、其他问题解答
能否复制并恢复视图位置?
不推荐。旋转后屏幕尺寸、方向都发生变化,视图的绝对坐标没有复用价值,应该用相对位置(约束、比例边距)来保证位置固定。能否以其他方向加载视图?
通过requestedOrientation强制设置方向会影响所有Fragment,不符合你的需求,因此不建议使用。无缝预览的补充优化
配合你之前的代码设置系统旋转动画,让状态栏/导航栏切换更平滑:
private fun enableSeamlessRotation() { window.attributes.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS window.attributes = window.attributes }
内容的提问来源于stack exchange,提问作者user16233948
相关产品推荐
相关产品推荐

