基础配置
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)
}
}
测试建议
-
测试设备覆盖

- 手机:多种尺寸和分辨率
- 平板:7寸、8寸、10寸、12寸
- 折叠屏设备
-
测试场景
- 旋转屏幕时数据保持
- 键盘弹出/收起
- 分屏模式
- 夜间模式切换
-
自动化测试
@Test fun testOrientationChange() { // 模拟方向变化 activityScenario.onActivity { activity -> activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE } // 验证UI状态 onView(withId(R.id.textView)) .check(matches(isDisplayed())) }
这个方案提供了从基础到高级的完整横竖屏适配策略,可根据OpenClaw项目的具体需求选择合适的方法。
版权声明:除非特别标注,否则均为本站原创文章,转载时请以链接形式注明文章出处。