OpenClaw 横竖屏适配方案

openclaw OpenClaw手册 2

基础配置

1 AndroidManifest.xml 配置

<!-- 允许所有方向 -->
<activity
    android:name=".MainActivity"
    android:configChanges="orientation|screenSize|keyboardHidden|screenLayout"
    android:screenOrientation="unspecified">
</activity>
<!-- 或指定横屏/竖屏 -->
<activity
    android:screenOrientation="portrait"> <!-- 强制竖屏 -->
<activity
    android:screenOrientation="landscape"> <!-- 强制横屏 -->

2 iOS Info.plist 配置

<!-- 允许所有方向 -->
<key>UISupportedInterfaceOrientations</key>
<array>
    <string>UIInterfaceOrientationPortrait</string>
    <string>UIInterfaceOrientationLandscapeLeft</string>
    <string>UIInterfaceOrientationLandscapeRight</string>
    <string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<!-- 仅横屏 -->
<key>UISupportedInterfaceOrientations</key>
<array>
    <string>UIInterfaceOrientationLandscapeLeft</string>
    <string>UIInterfaceOrientationLandscapeRight</string>
</array>

布局适配方案

1 创建不同方向的布局文件夹

res/
├── layout/
│   └── activity_main.xml          # 默认布局
├── layout-land/                   # 横屏布局
│   └── activity_main.xml
└── layout-port/                   # 竖屏布局
    └── activity_main.xml

2 使用 ConstraintLayout 实现响应式布局

<!-- layout/activity_main.xml -->
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:id="@+id/imageView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintDimensionRatio="H,16:9"  <!-- 宽高比 -->
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"/>
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_margin="16dp"/>
</androidx.constraintlayout.widget.ConstraintLayout>

3 使用百分比布局

<androidx.percentlayout.widget.PercentFrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ImageView
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_widthPercent="100%"
        app:layout_heightPercent="60%"/>
</androidx.percentlayout.widget.PercentFrameLayout>

代码适配方案

1 检测屏幕方向变化

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 获取当前方向
        val orientation = resources.configuration.orientation
        when (orientation) {
            Configuration.ORIENTATION_LANDSCAPE -> {
                // 横屏逻辑
                setContentView(R.layout.activity_main_land)
            }
            Configuration.ORIENTATION_PORTRAIT -> {
                // 竖屏逻辑
                setContentView(R.layout.activity_main_port)
            }
        }
    }
    override fun onConfigurationChanged(newConfig: Configuration) {
        super.onConfigurationChanged(newConfig)
        // 方向变化时重新布局
        when (newConfig.orientation) {
            Configuration.ORIENTATION_LANDSCAPE -> {
                // 更新横屏UI
                updateLandscapeLayout()
            }
            Configuration.ORIENTATION_PORTRAIT -> {
                // 更新竖屏UI
                updatePortraitLayout()
            }
        }
    }
    private fun updateLandscapeLayout() {
        // 动态调整横屏布局
        val params = button.layoutParams as ViewGroup.LayoutParams
        params.width = ViewGroup.LayoutParams.MATCH_PARENT
        button.layoutParams = params
    }
    private fun updatePortraitLayout() {
        // 动态调整竖屏布局
        val params = button.layoutParams as ViewGroup.LayoutParams
        params.width = ViewGroup.LayoutParams.WRAP_CONTENT
        button.layoutParams = params
    }
}

2 使用 ViewModel 保持数据

class MainViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
    data class UiState(
        val isLandscape: Boolean = false,
        val screenWidth: Int = 0,
        val screenHeight: Int = 0
    )
    fun updateScreenInfo(width: Int, height: Int) {
        _uiState.update {
            it.copy(
                isLandscape = width > height,
                screenWidth = width,
                screenHeight = height
            )
        }
    }
}

响应式设计策略

1 尺寸限定符

res/
├── values/
├── values-sw600dp/          # 7寸平板
├── values-sw720dp/          # 10寸平板
├── values-land/             # 横屏通用
└── values-port/             # 竖屏通用

2 dimens.xml 配置

<!-- values/dimens.xml -->
<resources>
    <dimen name="margin_small">8dp</dimen>
    <dimen name="margin_medium">16dp</dimen>
    <dimen name="margin_large">24dp</dimen>
</resources>
<!-- values-sw600dp/dimens.xml -->
<resources>
    <dimen name="margin_small">16dp</dimen>
    <dimen name="margin_medium">24dp</dimen>
    <dimen name="margin_large">32dp</dimen>
</resources>

3 使用 Fragments 适配不同布局

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        if (resources.configuration.orientation == 
            Configuration.ORIENTATION_LANDSCAPE) {
            // 横屏使用双栏布局
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, LandscapeFragment())
                .commit()
        } else {
            // 竖屏使用单栏布局
            supportFragmentManager.beginTransaction()
                .replace(R.id.container, PortraitFragment())
                .commit()
        }
    }
}

高级适配技巧

1 使用 WindowMetrics 获取准确尺寸

class ScreenUtils {
    companion object {
        fun getScreenSize(context: Context): Size {
            val windowManager = context.getSystemService(Context.WINDOW_SERVICE) 
                as WindowManager
            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                val metrics = windowManager.currentWindowMetrics
                Size(metrics.bounds.width(), metrics.bounds.height())
            } else {
                @Suppress("DEPRECATION")
                val display = windowManager.defaultDisplay
                val size = Point()
                display.getSize(size)
                Size(size.x, size.y)
            }
        }
        fun isTablet(context: Context): Boolean {
            val screenWidth = getScreenSize(context).width
            val screenHeight = getScreenSize(context).height
            val density = context.resources.displayMetrics.density
            val widthInDp = screenWidth / density
            val heightInDp = screenHeight / density
            return min(widthInDp, heightInDp) >= 600
        }
    }
}

2 使用 MotionLayout 实现平滑过渡

<androidx.constraintlayout.motion.widget.MotionLayout
    android:id="@+id/motionLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layoutDescription="@xml/scene_portrait_to_landscape">
    <ImageView
        android:id="@+id/image"
        android:layout_width="100dp"
        android:layout_height="100dp"/>
</androidx.constraintlayout.motion.widget.MotionLayout>
<!-- res/xml/scene_portrait_to_landscape.xml -->
<MotionScene>
    <Transition
        motion:constraintSetStart="@id/portrait"
        motion:constraintSetEnd="@id/landscape"
        motion:duration="300">
        <OnSwipe
            motion:touchAnchorId="@id/image"
            motion:touchAnchorSide="right"/>
    </Transition>
    <ConstraintSet android:id="@+id/portrait">
        <Constraint
            android:id="@id/image"
            android:layout_width="100dp"
            android:layout_height="100dp"
            motion:layout_constraintTop_toTopOf="parent"
            motion:layout_constraintStart_toStartOf="parent"/>
    </ConstraintSet>
    <ConstraintSet android:id="@+id/landscape">
        <Constraint
            android:id="@id/image"
            android:layout_width="200dp"
            android:layout_height="200dp"
            motion:layout_constraintEnd_toEndOf="parent"
            motion:layout_constraintBottom_toBottomOf="parent"/>
    </ConstraintSet>
</MotionScene>

3 使用 Navigation Component 适配

class MainActivity : AppCompatActivity() {
    private lateinit var navController: NavController
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        navController = findNavController(R.id.nav_host_fragment)
        // 根据屏幕方向选择不同导航图
        val navGraph = if (isLandscape()) {
            R.navigation.nav_graph_landscape
        } else {
            R.navigation.nav_graph_portrait
        }
        navController.setGraph(navGraph)
    }
}

测试建议

  1. 测试设备覆盖

    OpenClaw 横竖屏适配方案-第1张图片-OpenClaw 开源免费 -中文免费安装

    • 手机:多种尺寸和分辨率
    • 平板:7寸、8寸、10寸、12寸
    • 折叠屏设备
  2. 测试场景

    • 旋转屏幕时数据保持
    • 键盘弹出/收起
    • 分屏模式
    • 夜间模式切换
  3. 自动化测试

    @Test
    fun testOrientationChange() {
     // 模拟方向变化
     activityScenario.onActivity { activity ->
         activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
     }
     // 验证UI状态
     onView(withId(R.id.textView))
         .check(matches(isDisplayed()))
    }

这个方案提供了从基础到高级的完整横竖屏适配策略,可根据OpenClaw项目的具体需求选择合适的方法。

标签: OpenClaw 横竖屏适配

抱歉,评论功能暂时关闭!