基于 Compose With Material Design 3 的计算器
video_20220821_073634_edit.mp4
直接抄谷歌自家的计算器,
不过为了实现 “ 一套布局 ” 自适应横竖屏,横屏布局就没一样了。
- 基础四则运算 (%作为百分比处理)
- 算式优先度运算
- 函数 sin, abs 和 常量 pi, e
- 布局自适配不同屏幕大小 (益得Compose的优势) 和 添加异形屏沉浸
- 对于输入体验做了优化(例如自动处理左括号和右括号,以及运算符号)
个人觉得最有意思的是通过自定义布局实现一套布局自适应横竖屏。
注意:官方推荐使用 窗口大小类(WindowSizeClasses)实现自适应布局
多说无益,上代码。
@Preview(showBackground = true)
@Composable
fun TheLayoutPreview() {
ComposeCalculatorTheme {
// 自定义布局,自适应横竖屏
TheLayout(modifier = Modifier.fillMaxSize()) {
// 在横竖屏下不变的组件
Column(
modifier = Modifier
.weight(1f)
.fillMaxSize()
.background(Color.White)
) {
Text(text = "Column1")
}
// 根据屏幕方向切换不同内容的组件
Row(
modifier = Modifier
.weight(1f)
.fillMaxSize()
.background(Color.LightGray)
) {
when (isHorizontal()) {
true -> {
Text(text = "Row with Horizontal")
}
false -> {
Text(text = "Row with Vertical")
}
}
}
// 仅根据屏幕方向对布局配置做差异化的组件
Column(
modifier = Modifier
.isHorizontal {
weight(1f)
}
.isNotHorizontal {
weight(2f)
}
.fillMaxSize()
.background(Color.White)
) {
// 不在横屏透明度就会变 0.1 的组件
Text(
text = "Only Horizontal",
modifier = Modifier.isNotHorizontal { alpha(0.1f) })
}
}
}
}首先需要获取屏幕方向
@Composable
@Stable
fun isHorizontal() = when (LocalConfiguration.current.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> true
// Other wise
else -> false
}虽然 Compose 没直接提供屏幕数据的Api,但能通过 Activity 的 Configuration 判断方向
然后就是根据屏幕方向切换相应布局
@Composable
fun TheLayout(
content: @Composable TheLayoutScopeInstance.() -> Unit
) {
val scope by remember {
mutableStateOf(TheLayoutScopeInstance(null, null))
}
when (isHorizontal()) {
true -> {
Row(···) {
scope.apply {
rowScope = this@Row
content()
}
}
}
// Other wise
false -> {
Column(···) {
scope.apply {
columnScope = this@Column
content()
}
}
}
}
}
}但会发现 Column 和 Row 混用导致像是 Modifier.weight() 之类由 布局作用域 内提供的 Modifier 扩展函数没法用。
不过翻源码实现可以发现扩展函数是由 ColumnScope 和 RowScope 提供的,只要给自定义布局也实现作用域相应的 Modifier 扩展函数就行。
@LayoutScopeMarker
@Immutable
class TheLayoutScopeInstance(
var columnScope: ColumnScope?, var rowScope: RowScope?
) : TheLayoutScope {
···
@Stable
override fun Modifier.weight(weight: Float, fill: Boolean): Modifier {
val modifier = this
columnScope?.apply {
return modifier.weight(weight, fill)
}
rowScope?.apply {
return modifier.weight(weight, fill)
}
return modifier
}
···
}然后可以考虑再加上下面这两个自定义 Modifier 的扩展函数
非必要实现,但对于实际使用来说很方便
@Stable
fun Modifier.isHorizontal(doWork: Modifier.() -> Modifier) = composed {
if (isHorizontal()) doWork(this)
else this
}
@Stable
fun Modifier.isNotHorizontal(doWork: Modifier.() -> Modifier) = composed {
if (!isHorizontal()) doWork(this)
else this
}.webp?raw=true)
.webp?raw=true)


.webp?raw=true)
.webp?raw=true)