实现双指张合缩放手势,以支持应用中的可缩放内容。这是提高无障碍性的标准且与平台一致的方法,可让用户直观地调整文本和界面元素的大小,以满足其需求。您的应用可以定义自定义缩放行为,通过精细控制和情境行为提供一种体验,用户通常可以比屏幕放大等系统级功能更快地发现这种体验。
选择扩缩策略
本指南中介绍的策略会使界面重排和重组,以适应屏幕宽度。这提供了显著的无障碍优势,因为用户无需进行水平平移和令人沮丧的“锯齿形”移动,否则阅读长行文本就需要这样做。
延伸阅读:研究证实,对于低视力用户,与需要二维平移的界面相比,重排内容的可读性明显更高,也更易于浏览。如需了解详情,请参阅移动设备上的平移和扫描内容与可自动调节内容的比较。
缩放所有元素或仅缩放文本元素
下表展示了每种缩放策略的视觉效果。
| 策略 | 密度缩放 | 字体缩放 |
|---|---|---|
行为 |
按比例缩放所有内容。内容会重排以适应其容器,因此用户无需水平平移即可查看所有内容。 |
仅影响文本元素。整体布局和非文本组件的大小保持不变。 |
哪些内容会扩缩 |
所有视觉元素:文本、组件(按钮、图标)、图片和布局间距(内边距、外边距) |
纯文字 |
演示 |
建议
现在,您已经了解了视觉差异,下表可帮助您权衡利弊,并为您的内容选择最佳策略。
界面类型 |
建议的出价策略 |
推理 |
阅读密集型布局 示例:新闻报道、即时通讯应用 |
密度或字体缩放 |
最好使用密度缩放来缩放整个内容区域,包括内嵌图片。 如果只需要缩放文本,字体缩放是一种简单的替代方案。 |
视觉结构化布局 示例:应用商店、社交媒体信息流 |
密度缩放 |
保留轮播界面或网格中图片与文字之间的视觉关系。重排特性可避免水平平移,因为水平平移会与嵌套滚动元素发生冲突。 |
在 Jetpack Compose 中检测缩放手势
如需支持用户可缩放的内容,您必须先检测多点触控手势。在 Jetpack Compose 中,您可以使用 Modifier.transformable 执行此操作。
transformable 修饰符是一个高级 API,可提供自上次手势事件以来的 zoomChange 增量。这会将状态更新逻辑简化为直接累加(例如 scale *= zoomChange),因此非常适合本指南中介绍的自适应扩缩策略。
实现示例
以下示例展示了如何实现密度缩放和字体缩放策略。
密度缩放
此方法会缩放界面区域的基本 density。因此,所有基于布局的测量值(包括内边距、间距和组件大小)都会缩放,就像屏幕尺寸或分辨率发生了变化一样。由于文字大小也取决于密度,因此也会按比例缩放。如果您想统一放大特定区域内的所有元素,同时保持界面的整体视觉节奏和比例,此策略非常有效。
private class DensityScalingState( // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x. private val minScale: Float = 0.75f, private val maxScale: Float = 3.5f, private val currentDensity: Density ) { val transformableState = TransformableState { zoomChange, _, _ -> scaleFactor.floatValue = (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale) } val scaleFactor = mutableFloatStateOf(1f) fun scaledDensity(): Density { return Density( currentDensity.density * scaleFactor.floatValue, currentDensity.fontScale ) } } @Composable fun DensityScalingSample() { val currentDensity = LocalDensity.current val scaleState = remember(currentDensity) { DensityScalingState(currentDensity = currentDensity) } Box( modifier = Modifier .fillMaxSize() .transformable(state = scaleState.transformableState), contentAlignment = Alignment.TopCenter ) { CompositionLocalProvider( LocalDensity provides scaleState.scaledDensity() ) { DemoCard() } } }
字体缩放
此策略更具针对性,仅修改 fontScale 因子。结果是,只有文本元素会增大或缩小,而所有其他布局组件(例如容器、内边距和图标)都会保持固定大小。此策略非常适合在阅读密集型应用中提高文本易读性。
class FontScaleState( // Note: For accessibility, typical min/max values are ~0.75x and ~3.5x. private val minScale: Float = 0.75f, private val maxScale: Float = 3.5f, private val currentDensity: Density ) { val transformableState = TransformableState { zoomChange, _, _ -> scaleFactor.floatValue = (scaleFactor.floatValue * zoomChange).coerceIn(minScale, maxScale) } val scaleFactor = mutableFloatStateOf(1f) fun scaledFont(): Density { return Density( currentDensity.density, currentDensity.fontScale * scaleFactor.floatValue ) } } @Composable fun FontScalingSample() { val currentDensity = LocalDensity.current val scaleState = remember { FontScaleState(currentDensity = currentDensity) } Box( modifier = Modifier .fillMaxSize() .transformable(state = scaleState.transformableState), contentAlignment = Alignment.TopCenter ) { CompositionLocalProvider( LocalDensity provides scaleState.scaledFont() ) { DemoCard() } } }
共享演示界面
这是上述两个示例使用的共享 DemoCard 可组合项,用于突出显示不同的缩放行为。
@Composable private fun DemoCard() { Card( modifier = Modifier .width(360.dp) .padding(16.dp), shape = RoundedCornerShape(12.dp) ) { Column( modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp) ) { Text("Demo Card", style = MaterialTheme.typography.headlineMedium) var isChecked by remember { mutableStateOf(true) } Row(verticalAlignment = Alignment.CenterVertically) { Text("Demo Switch", Modifier.weight(1f), style = MaterialTheme.typography.bodyLarge) Switch(checked = isChecked, onCheckedChange = { isChecked = it }) } Row(verticalAlignment = Alignment.CenterVertically) { Icon(Icons.Filled.Person, "Icon", Modifier.size(32.dp)) Spacer(Modifier.width(8.dp)) Text("Demo Icon", style = MaterialTheme.typography.bodyLarge) } Row( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { Box( Modifier .width(100.dp) .weight(1f) .height(80.dp) .background(Color.Blue) ) Box( Modifier .width(100.dp) .weight(1f) .height(80.dp) .background(Color.Red) ) } Text( "Demo Text: Lorem ipsum dolor sit amet, consectetur adipiscing elit," + " sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", style = MaterialTheme.typography.bodyMedium, textAlign = TextAlign.Justify ) } } }
提示和注意事项
为了打造更精致、更易于访问的体验,请考虑以下建议:
- 考虑提供非手势缩放控件:有些用户可能难以使用手势。为了支持这些用户,请考虑提供一种不依赖于手势的替代方法来调整或重置缩放比例。
- 针对所有缩放比例进行构建:针对应用内缩放以及系统级字体或显示设置测试界面。检查应用的布局是否能正确自适应,而不会破坏、重叠或隐藏内容。详细了解如何构建自适应布局。