diff --git a/compose/src/main/java/org/zhiwei/compose/model/Screen.kt b/compose/src/main/java/org/zhiwei/compose/model/Screen.kt index f031b5e..b75ebec 100644 --- a/compose/src/main/java/org/zhiwei/compose/model/Screen.kt +++ b/compose/src/main/java/org/zhiwei/compose/model/Screen.kt @@ -16,15 +16,23 @@ import org.zhiwei.compose.screen.basic.material3.TextField_Screen import org.zhiwei.compose.screen.basic.material3.Text_Screen import org.zhiwei.compose.screen.basic.material3.TopAppbarTabs_Screen import org.zhiwei.compose.screen.basic.material3.Widget_Screen +import org.zhiwei.compose.screen.layout.UI_CustomModifier /** * 用于配置整个Compose模块内所有可跳转的页面UI,用于Navigation导航 */ +internal fun configPageRoute(modifier: Modifier, onBack: (() -> Unit) = {}): List { + val list = mutableListOf() + list.addAll(BasicScreenUIs.basicCourses(modifier, onBack)) + list.addAll(LayoutScreenUIs.layoutCourses(modifier)) + return list +} + //region basicScreen基础控件 internal object BasicScreenUIs { - //所有基础内容的list + //所有基础内容的list,⚠️todo 除了要用于填充页面,还要在上面添加到list中,注册页面导航route internal fun basicCourses(modifier: Modifier = Modifier, onBack: (() -> Unit) = {}) = listOf( CourseItemModel( "Column,Rom,Box,Modifiers", @@ -94,12 +102,12 @@ internal object BasicScreenUIs { //region LayoutScreen布局相关 internal object LayoutScreenUIs { - //所有基础内容的list - internal fun layoutCourses(modifier: Modifier = Modifier, onBack: (() -> Unit) = {}) = listOf( + //所有基础内容的list ⚠️todo 除了要用于填充页面,还要在上面添加到list中,注册页面导航route + internal fun layoutCourses(modifier: Modifier = Modifier) = listOf( CourseItemModel( - "Column,Rom,Box,Modifiers", - "列,行,箱,都是容器,顾名思义就是成列,成行和层叠摆放内部子控件;及修饰符Modifier内外边距等基本使用。" - ) { Box_Column_Row_Screen(modifier) }, + "Custom Modifier", + "创建自定义的modifier,来处理布局layout,测量measurable,约束constraint,占位等。" + ) { UI_CustomModifier(modifier) }, ) } //endregion \ No newline at end of file diff --git a/compose/src/main/java/org/zhiwei/compose/screen/HomeScreen.kt b/compose/src/main/java/org/zhiwei/compose/screen/HomeScreen.kt index 14c95d3..0947301 100644 --- a/compose/src/main/java/org/zhiwei/compose/screen/HomeScreen.kt +++ b/compose/src/main/java/org/zhiwei/compose/screen/HomeScreen.kt @@ -28,8 +28,8 @@ import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import kotlinx.coroutines.launch -import org.zhiwei.compose.model.BasicScreenUIs import org.zhiwei.compose.model.TabPagerModel +import org.zhiwei.compose.model.configPageRoute import org.zhiwei.compose.screen.basic.Basic_Screen import org.zhiwei.compose.screen.gesture.GestureScreen import org.zhiwei.compose.screen.graphics.GraphicsScreen @@ -54,9 +54,8 @@ internal fun Home_Screen(modifier: Modifier = Modifier) { composable(route = "HomeScreen") { HomeScreenContent(modifier, navController = navController) } - - //基础组件下的 每个可导航的页面,需要使用composable来设置 - BasicScreenUIs.basicCourses(modifier) { navController.navigateUp() }.forEach { model -> + //配置所有pager下的页面,每个item对应页面 可导航的页面,需要使用composable来设置 + configPageRoute(modifier) { navController.navigateUp() }.forEach { model -> composable(route = model.title) { //model中ui的属性字段是个函数,需要invoke来调用 model.ui() diff --git a/compose/src/main/java/org/zhiwei/compose/screen/layout/CustomModifier.kt b/compose/src/main/java/org/zhiwei/compose/screen/layout/CustomModifier.kt new file mode 100644 index 0000000..d04a85e --- /dev/null +++ b/compose/src/main/java/org/zhiwei/compose/screen/layout/CustomModifier.kt @@ -0,0 +1,167 @@ +package org.zhiwei.compose.screen.layout + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.AlignmentLine +import androidx.compose.ui.layout.FirstBaseline +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.layout +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import org.zhiwei.compose.ui.widget.Title_Desc_Text +import org.zhiwei.compose.ui.widget.Title_Sub_Text +import org.zhiwei.compose.ui.widget.Title_Text + +@Composable +internal fun UI_CustomModifier(modifier: Modifier = Modifier) { + LazyColumn(modifier.fillMaxSize()) { + item { + Title_Text("Custom Modifier") + Title_Sub_Text(title = "1、使用compose的layout库中的Modifier的扩展函数,来获取一个可用于测量尺寸,宽高及布局相关的Modifier对象") + Title_Desc_Text(desc = "custom Align modifier") + //下面使用了自定义的扩展函数customAlign,给内部的控件一个定植边距,以及添加对齐方式 + val modifier = Modifier + .padding(8.dp) + .fillMaxWidth() + .background(Color.LightGray) + Column(modifier.wrapContentHeight()) { + Text( + text = "Align Start with space", + modifier = Modifier + .background(Color(0xFF8BC34A)) + .customAlign(align = HorizontalAlign.START) + ) + + Text( + text = "Align Center with space", + modifier = Modifier + .background(Color(0xFF8BC34A)) + .customAlign(align = HorizontalAlign.CENTER) + ) + + Text( + text = "Align End with space", + modifier = Modifier + .background(Color(0xFF8BC34A)) + .customAlign(align = HorizontalAlign.END) + ) + } + Title_Desc_Text(desc = "firstBaselineToTop Modifier") + //对比两个text,因为属性不同,padding 32dp和baseLine top 32dp的效果; + //padding 是从Text文本的top开始计算。而baseLine是绘制基线 + Row(modifier.wrapContentHeight()) { + Text( + text = "Padding 32dp", + modifier = Modifier + .background(Color(0xFF8BC34A)) + .padding(top = 32.dp) + ) + Spacer(modifier = Modifier.width(20.dp)) + Text( + text = "Baseline 32dp", + modifier = Modifier + .background(Color(0xFF8BC34A)) + .firstBaselineToTop(32.dp) + ) + } + Title_Sub_Text(title = "2、LayoutModifier对可测量对象布局,添加内边距") + Title_Desc_Text(desc = "custom padding modifier") + //实现LayoutModifier来自定义内边距函数 + Text( + text = "Custom Padding", + modifier = Modifier + .background(Color(0xFF8BC34A)) + .paddingNoOffsetNoConstrain(all = 4.dp) + ) + } + } +} + + +//region 通过layout的自定义扩展函数 + + +//自定义的水平对其方式 +enum class HorizontalAlign { + START, CENTER, END +} + +/** + * 自定义的Modifier的扩展函数,基于原modifier对象,继续作用效果; + * 给内部可测量[Measurable]的对象两边都加上一定值的空白空间,并实现自定义布局对齐方式 + * 尤其注意,是作用于[Measurable]对象,Text是。 + */ +private fun Modifier.customAlign( + space: Int = 60, + align: HorizontalAlign = HorizontalAlign.CENTER, + //todo 注意⚠️这里的then函数,暂时未详细学习,简单理解就是字面意思,基于上个modifier对象的作用效果,继续后续操作 +) = this.then( + + layout { measurable: Measurable, constraints: Constraints -> + + val placeable = measurable.measure(constraints) + val width = placeable.measuredWidth + 2 * space + + layout(width, placeable.measuredHeight) { + when (align) { + HorizontalAlign.START -> { + placeable.placeRelative(0, 0) + } + + HorizontalAlign.CENTER -> { + placeable.placeRelative(space, 0) + } + + HorizontalAlign.END -> { + placeable.placeRelative(2 * space, 0) + } + } + } + } +) + + +/** + * 自定义的modifier扩展函数,实现对layout中的首行text的绘制基线baseline添加上边距 + */ +private fun Modifier.firstBaselineToTop(firstBaselineToTop: Dp) = this.then( + layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + //判断 composable 控件 是否有首行基线FirstBaseline + check(placeable[FirstBaseline] != AlignmentLine.Unspecified) + val firstBaseline = placeable[FirstBaseline] + // 高度计算 + val placeableY = firstBaselineToTop.roundToPx() - firstBaseline + val height = placeable.height + placeableY + layout(placeable.width, height) { + // 布局摆放 + placeable.placeRelative(0, placeableY) + } + } +) + +//endregion + +//region 预览效果 + +@Preview(showBackground = true, backgroundColor = 0XFFFFFFFF) +@Composable +private fun CustomModifierPreview() { + UI_CustomModifier() +} + +//endregion \ No newline at end of file diff --git a/compose/src/main/java/org/zhiwei/compose/screen/layout/CustomPaddingSamples.kt b/compose/src/main/java/org/zhiwei/compose/screen/layout/CustomPaddingSamples.kt new file mode 100644 index 0000000..146d309 --- /dev/null +++ b/compose/src/main/java/org/zhiwei/compose/screen/layout/CustomPaddingSamples.kt @@ -0,0 +1,246 @@ +package org.zhiwei.compose.screen.layout + +import androidx.compose.runtime.Stable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.LayoutModifier +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.unit.Constraints +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.constrainHeight +import androidx.compose.ui.unit.constrainWidth +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.offset + +/* + These padding samples are to show how Constraints.offset or + Constraints.constrainWidth/Height effect placeable placing when Composable dimensions are + bigger than parent Composable dimensions. + */ + +/** + * This modifier is similar to **padding** modifier. Uses Constraints.offset() to measure placeable + * without the area reserved for padding. When size of the Composable is bigger than parent + * Composable offset limits area to placeable width + horizontal padding when setting width + */ +@Stable +fun Modifier.paddingWithOffsetAndConstrain(all: Dp) = + this.then( + PaddingModifier(start = all, top = all, end = all, bottom = all, rtlAware = true) + ) + +// Implementation detail +private class PaddingModifier( + val start: Dp = 0.dp, + val top: Dp = 0.dp, + val end: Dp = 0.dp, + val bottom: Dp = 0.dp, + val rtlAware: Boolean, +) : LayoutModifier { + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + + val horizontal = start.roundToPx() + end.roundToPx() + val vertical = top.roundToPx() + bottom.roundToPx() + + val offsetConstraints = constraints.offset(-horizontal, -vertical) + val placeable = measurable.measure(offsetConstraints) + + val width = constraints.constrainWidth(placeable.width + horizontal) + val height = constraints.constrainHeight(placeable.height + vertical) + + println( + "😁 PaddingModifier()\n" + + "constraints: $constraints\n" + + "offsetConstraints: $offsetConstraints\n" + + "horizontal: $horizontal, " + + "placeable width: ${placeable.width}, " + + "constrainedWidth: $width" + ) + + return layout(width, height) { + if (rtlAware) { + placeable.placeRelative(start.roundToPx(), top.roundToPx()) + } else { + placeable.place(start.roundToPx(), top.roundToPx()) + } + } + } +} + +/** + * This modifier is similar to **padding** modifier but + * `measurable.measure(constraint)`used instead of + * `measurable.measure(constraints.offset(-horizontal, -vertical))`. + * offset only reserves area after padding is applied. With this modifier if parent dimensions + * are bigger we break padding. + * + * ## Note + * This is for demonstration only. Use offset when placing placeables with some rules requires + * offset to limit placeable size considering padding size + * + */ +@Stable +fun Modifier.paddingWithConstrainOnly(all: Dp) = + this.then( + PaddingModifierWitConstrain( + start = all, + top = all, + end = all, + bottom = all, + rtlAware = true + ) + ) + +// Implementation detail +private class PaddingModifierWitConstrain( + val start: Dp = 0.dp, + val top: Dp = 0.dp, + val end: Dp = 0.dp, + val bottom: Dp = 0.dp, + val rtlAware: Boolean, +) : LayoutModifier { + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + + val horizontal = start.roundToPx() + end.roundToPx() + val vertical = top.roundToPx() + bottom.roundToPx() + + val placeable = measurable.measure(constraints) + + val width = constraints.constrainWidth(placeable.width + horizontal) + val height = constraints.constrainHeight(placeable.height + vertical) + + println( + "🤡 PaddingModifierWitConstrainOnly()\n" + + "constraints: $constraints\n" + + "horizontal: $horizontal, " + + "placeable width: ${placeable.width}, " + + "constrainedWidth: $width" + ) + + return layout(width, height) { + if (rtlAware) { + placeable.placeRelative(start.roundToPx(), top.roundToPx()) + } else { + placeable.place(start.roundToPx(), top.roundToPx()) + } + } + } +} + + +/** + * 扩展函数,实现简单的padding效果。使用Constraints.offset()来测量布局,而没有计算反向padding的区域。 + * 如果布局大于容器尺寸,会有约束 + */ +@Stable +fun Modifier.paddingWithOffsetOnly(all: Dp) = + this.then( + PaddingModifierWitOffset(start = all, top = all, end = all, bottom = all, rtlAware = true) + ) + +// Implementation detail +private class PaddingModifierWitOffset( + val start: Dp = 0.dp, + val top: Dp = 0.dp, + val end: Dp = 0.dp, + val bottom: Dp = 0.dp, + val rtlAware: Boolean, +) : LayoutModifier { + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + + val horizontal = start.roundToPx() + end.roundToPx() + val vertical = top.roundToPx() + bottom.roundToPx() + + val offsetConstraints = constraints.offset(-horizontal, -vertical) + val placeable = measurable.measure(offsetConstraints) + + val width = (placeable.width + horizontal) + val height = (placeable.height + vertical) + + println( + "😱 PaddingModifierWitOffsetOnly()\n" + + "constraints: $constraints\n" + + "offsetConstraints: $offsetConstraints\n" + + "horizontal: $horizontal, " + + "placeable width: ${placeable.width}, " + + "width: $width" + ) + + return layout(width, height) { + if (rtlAware) { + placeable.placeRelative(start.roundToPx(), top.roundToPx()) + } else { + placeable.place(start.roundToPx(), top.roundToPx()) + } + } + } +} + +/** + * 扩展函数,实现简单的padding内边距效果,而不是使用offset或者constraint来从composable的控件移除对应区域; + * 因此内元素的modifier设定尺寸如果大于或等于容器尺寸,那么就会溢出布局外。 + */ +@Stable +fun Modifier.paddingNoOffsetNoConstrain(all: Dp) = + this.then( + PaddingModifierPlain( + start = all, + top = all, + end = all, + bottom = all, + rtlAware = true + ) + ) + +// 实现LayoutModifier接口的布局方式 +private class PaddingModifierPlain( + val start: Dp = 0.dp, + val top: Dp = 0.dp, + val end: Dp = 0.dp, + val bottom: Dp = 0.dp, + val rtlAware: Boolean, +) : LayoutModifier { + + override fun MeasureScope.measure( + measurable: Measurable, + constraints: Constraints, + ): MeasureResult { + + val horizontal = start.roundToPx() + end.roundToPx() + val vertical = top.roundToPx() + bottom.roundToPx() + + val placeable = measurable.measure(constraints) + + val width = (placeable.width + horizontal) + val height = (placeable.height + vertical) + + println( + "😍 PaddingModifierPlain()\n" + + "constraints: $constraints\n" + + "horizontal: $horizontal, " + + "placeable width: ${placeable.width}, " + + "width: $width" + ) + + return layout(width, height) { + if (rtlAware) { + placeable.placeRelative(start.roundToPx(), top.roundToPx()) + } else { + placeable.place(start.roundToPx(), top.roundToPx()) + } + } + } +} \ No newline at end of file