You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现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
}

二、关于横竖屏两套布局的选择

如果你的相机布局极度复杂,手动调整参数容易出错,可以考虑维护两套布局:

  1. 在res/layout-land目录下创建横屏布局文件,保证按钮在横屏时的相对位置和竖屏一致(比如竖屏底部居中→横屏右侧居中)
  2. 在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()
}

这种方式的优势是可视化布局更直观,缺点是需要同步维护两套布局的适配逻辑。


三、其他问题解答

  1. 能否复制并恢复视图位置?
    不推荐。旋转后屏幕尺寸、方向都发生变化,视图的绝对坐标没有复用价值,应该用相对位置(约束、比例边距)来保证位置固定。

  2. 能否以其他方向加载视图?
    通过requestedOrientation强制设置方向会影响所有Fragment,不符合你的需求,因此不建议使用。

  3. 无缝预览的补充优化
    配合你之前的代码设置系统旋转动画,让状态栏/导航栏切换更平滑:

private fun enableSeamlessRotation() {
    window.attributes.rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
    window.attributes = window.attributes
}

内容的提问来源于stack exchange,提问作者user16233948

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 14:35:26