├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── kotlinc.xml
├── misc.xml
└── vcs.xml
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── customlayout
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── customlayout
│ │ │ ├── AutoWidthSortColumnActivity.kt
│ │ │ ├── BoxAsDividerNegativePaddingBehaviorActivity.kt
│ │ │ ├── DividerColumnExperimentActivity.kt
│ │ │ ├── DividerLazyColumnExperimentActivity.kt
│ │ │ ├── DividerLikeColumnActivity.kt
│ │ │ ├── DividerLikeLazyColumnActivity.kt
│ │ │ ├── DividerLikeOnlyColumnActivity.kt
│ │ │ ├── LayoutModifierSingleExperimentActivity.kt
│ │ │ ├── LayoutModifierSizeExperimentActivity.kt
│ │ │ ├── LayoutSizeExperimentActivity.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── NegativePaddingExperimentActivity.kt
│ │ │ ├── Util.kt
│ │ │ └── ui
│ │ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ ├── backup_rules.xml
│ │ └── data_extraction_rules.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── customlayout
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
18 |
19 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'com.example.customlayout'
8 | compileSdk 33
9 |
10 | defaultConfig {
11 | applicationId "com.example.customlayout"
12 | minSdk 24
13 | targetSdk 33
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables {
19 | useSupportLibrary true
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | kotlinOptions {
34 | jvmTarget = '1.8'
35 | }
36 | buildFeatures {
37 | compose true
38 | }
39 | composeOptions {
40 | kotlinCompilerExtensionVersion '1.3.2'
41 | }
42 | packagingOptions {
43 | resources {
44 | excludes += '/META-INF/{AL2.0,LGPL2.1}'
45 | }
46 | }
47 | }
48 |
49 | dependencies {
50 |
51 | implementation 'androidx.core:core-ktx:1.8.0'
52 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
53 | implementation 'androidx.activity:activity-compose:1.5.1'
54 | implementation platform('androidx.compose:compose-bom:2022.10.00')
55 | implementation 'androidx.compose.ui:ui'
56 | implementation 'androidx.compose.ui:ui-graphics'
57 | implementation 'androidx.compose.ui:ui-tooling-preview'
58 | implementation 'androidx.compose.material3:material3'
59 | testImplementation 'junit:junit:4.13.2'
60 | androidTestImplementation 'androidx.test.ext:junit:1.1.5'
61 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
62 | androidTestImplementation platform('androidx.compose:compose-bom:2022.10.00')
63 | androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
64 | debugImplementation 'androidx.compose.ui:ui-tooling'
65 | debugImplementation 'androidx.compose.ui:ui-test-manifest'
66 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/customlayout/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("com.example.customlayout", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/AutoWidthSortColumnActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.Column
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.height
13 | import androidx.compose.foundation.layout.size
14 | import androidx.compose.foundation.layout.width
15 | import androidx.compose.material3.MaterialTheme
16 | import androidx.compose.material3.Surface
17 | import androidx.compose.material3.Text
18 | import androidx.compose.runtime.Composable
19 | import androidx.compose.ui.Alignment
20 | import androidx.compose.ui.Modifier
21 | import androidx.compose.ui.graphics.Color
22 | import androidx.compose.ui.layout.Layout
23 | import androidx.compose.ui.unit.dp
24 | import com.example.customlayout.ui.theme.CustomLayoutTheme
25 |
26 | class AutoWidthSortColumnActivity : ComponentActivity() {
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 | setContent {
30 | CustomLayoutTheme {
31 | // A surface container using the 'background' color from the theme
32 | Surface(
33 | modifier = Modifier.fillMaxSize(),
34 | color = MaterialTheme.colorScheme.background
35 | ) {
36 | Greeting()
37 | }
38 | }
39 | }
40 | }
41 |
42 | @Composable
43 | fun Greeting(modifier: Modifier = Modifier) {
44 | Column(
45 | horizontalAlignment = Alignment.CenterHorizontally,
46 | verticalArrangement = Arrangement.Center
47 | ) {
48 | CustomLayout(Modifier.size(150.dp).border(1.dp, Color.Green)) {
49 | Text("Hello There")
50 | Text("Good")
51 | Text("Longer please")
52 | Text("More Text")
53 | Box(Modifier.height(10.dp).width(100.dp).background(Color.Cyan))
54 | Box(Modifier.height(10.dp).width(10.dp).background(Color.Red))
55 | Box(Modifier.height(10.dp).width(50.dp).background(Color.Magenta))
56 | }
57 | }
58 | }
59 |
60 | @Composable
61 | fun CustomLayout(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
62 | Layout(
63 | modifier = modifier,
64 | content = content
65 | ) { measurables, constraints ->
66 | val looseConstraints = constraints.copy(
67 | minWidth = 0,
68 | minHeight = 0,
69 | )
70 | val placaebles = measurables.map { measurable ->
71 | measurable.measure(constraints = looseConstraints)
72 | }.sortedBy { it.width }
73 |
74 | layout(constraints.maxWidth, constraints.maxHeight) {
75 | var y = 0
76 | placaebles.forEach { placeable ->
77 | placeable.place(0, y)
78 | y += placeable.height
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/BoxAsDividerNegativePaddingBehaviorActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.BoxScope
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.fillMaxSize
14 | import androidx.compose.foundation.layout.size
15 | import androidx.compose.foundation.layout.width
16 | import androidx.compose.material3.Checkbox
17 | import androidx.compose.material3.ExperimentalMaterial3Api
18 | import androidx.compose.material3.MaterialTheme
19 | import androidx.compose.material3.Slider
20 | import androidx.compose.material3.Surface
21 | import androidx.compose.material3.Text
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.runtime.getValue
24 | import androidx.compose.runtime.mutableStateOf
25 | import androidx.compose.runtime.remember
26 | import androidx.compose.runtime.setValue
27 | import androidx.compose.ui.Alignment
28 | import androidx.compose.ui.Modifier
29 | import androidx.compose.ui.graphics.Color
30 | import androidx.compose.ui.layout.layout
31 | import androidx.compose.ui.unit.Dp
32 | import androidx.compose.ui.unit.dp
33 | import androidx.compose.ui.unit.offset
34 | import com.example.customlayout.ui.theme.CustomLayoutTheme
35 |
36 | class BoxAsDividerNegativePaddingBehaviorActivity :ComponentActivity() {
37 | override fun onCreate(savedInstanceState: Bundle?) {
38 | super.onCreate(savedInstanceState)
39 | setContent {
40 | CustomLayoutTheme {
41 | // A surface container using the 'background' color from the theme
42 | Surface(
43 | modifier = Modifier.fillMaxSize(),
44 | color = MaterialTheme.colorScheme.background
45 | ) {
46 | Greeting()
47 | }
48 | }
49 | }
50 | }
51 |
52 | @Composable
53 | fun Greeting() {
54 | Column(
55 | horizontalAlignment = Alignment.CenterHorizontally,
56 | ) {
57 | val textWidth = 180.dp
58 | var containerSize by remember { mutableStateOf(150) }
59 | Row(verticalAlignment = Alignment.CenterVertically) {
60 | Text(text = "Container Size: $containerSize", modifier = Modifier.width(textWidth))
61 | Slider(
62 | value = containerSize.toFloat(),
63 | onValueChange = { containerSize = it.toInt() },
64 | valueRange = 0f..300f
65 | )
66 | }
67 | var boxSize by remember { mutableStateOf(150) }
68 | var useBoxSize by remember { mutableStateOf(false) }
69 | Row(verticalAlignment = Alignment.CenterVertically) {
70 | Row(modifier = Modifier.width(textWidth),
71 | verticalAlignment = Alignment.CenterVertically
72 | ) {
73 | Checkbox(
74 | checked = useBoxSize,
75 | onCheckedChange = { useBoxSize = it }
76 | )
77 | Text(text = "Box Size: $boxSize")
78 | }
79 | Slider(
80 | value = boxSize.toFloat(),
81 | onValueChange = { boxSize = it.toInt() },
82 | valueRange = 0f..300f,
83 | enabled = useBoxSize
84 | )
85 | }
86 | var constraintOffSet by remember { mutableStateOf(0) }
87 |
88 | Row(verticalAlignment = Alignment.CenterVertically) {
89 | Text(
90 | text = "Constraint Offset: $constraintOffSet",
91 | modifier = Modifier.width(textWidth)
92 | )
93 | Slider(
94 | value = constraintOffSet.toFloat(),
95 | onValueChange = { constraintOffSet = it.toInt() },
96 | valueRange = -150f..150f
97 | )
98 | }
99 | var layoutSizeChange by remember { mutableStateOf(0) }
100 | Row(verticalAlignment = Alignment.CenterVertically) {
101 | Text(
102 | text = "Layout's Change: $layoutSizeChange",
103 | modifier = Modifier.width(textWidth)
104 | )
105 | Slider(
106 | value = layoutSizeChange.toFloat(),
107 | onValueChange = { layoutSizeChange = it.toInt() },
108 | valueRange = -200f..200f
109 | )
110 | }
111 | var placement by remember { mutableStateOf(0) }
112 | Row(verticalAlignment = Alignment.CenterVertically) {
113 | Text(text = "Placement: $placement", modifier = Modifier.width(textWidth))
114 | Slider(
115 | value = placement.toFloat(),
116 | onValueChange = { placement = it.toInt() },
117 | valueRange = -200f..200f
118 | )
119 | }
120 | var verticalCentered by remember { mutableStateOf(false) }
121 | var horizontalCentered by remember { mutableStateOf(false) }
122 | Row {
123 | Row(modifier = Modifier.weight(1f),
124 | horizontalArrangement = Arrangement.Center,
125 | verticalAlignment = Alignment.CenterVertically
126 | ) {
127 | Checkbox(
128 | checked = verticalCentered,
129 | onCheckedChange = { verticalCentered = it }
130 | )
131 | Text("Vertical Centered")
132 | }
133 | Row(modifier = Modifier.weight(1f),
134 | horizontalArrangement = Arrangement.Center,
135 | verticalAlignment = Alignment.CenterVertically
136 | ) {
137 | Checkbox(
138 | checked = horizontalCentered,
139 | onCheckedChange = { horizontalCentered = it }
140 | )
141 | Text("Horizontal Centered")
142 | }
143 | }
144 | Column(
145 | horizontalAlignment = Alignment.CenterHorizontally,
146 | verticalArrangement = Arrangement.Center,
147 | modifier = Modifier.fillMaxSize()
148 | ) {
149 | val horizontalAlignment = if (verticalCentered)
150 | Alignment.CenterHorizontally else Alignment.Start
151 | val verticalArrangement = if (horizontalCentered)
152 | Arrangement.Center else Arrangement.Top
153 | Column(
154 | horizontalAlignment = horizontalAlignment,
155 | verticalArrangement = verticalArrangement,
156 | modifier = Modifier
157 | .size(containerSize.dp)
158 | .background(Color.Yellow)
159 | ) {
160 | BoxLayout(
161 | constraintOffSet.dp,
162 | layoutSizeChange.dp,
163 | placement.dp,
164 | boxSize.dp,
165 | useBoxSize
166 | ) {
167 | Box(modifier = Modifier.fillMaxSize())
168 | }
169 | }
170 | }
171 | }
172 | }
173 |
174 | @Composable
175 | private fun BoxLayout(
176 | constraintOffSet: Dp,
177 | layoutSizeChange: Dp,
178 | placement: Dp,
179 | boxSize: Dp,
180 | useBoxSize: Boolean,
181 | content: @Composable BoxScope.() -> Unit = {}
182 | ) {
183 | Box(modifier = Modifier
184 | .conditional(useBoxSize) {
185 | size(boxSize)
186 | }
187 | .background(GrayAlpha)
188 | .layout { measurable, constraints ->
189 | // Measure
190 | val placeable = measurable.measure(
191 | constraints.offset(
192 | constraintOffSet.roundToPx(),
193 | constraintOffSet.roundToPx()
194 | )
195 | )
196 |
197 | // Layout
198 | layout(
199 | placeable.width + layoutSizeChange.roundToPx(),
200 | placeable.height + layoutSizeChange.roundToPx()
201 | ) {
202 | placeable.place(placement.roundToPx(), placement.roundToPx())
203 | }
204 | }
205 | .border(1.dp, Color.Red),
206 | content = content
207 | )
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/DividerColumnExperimentActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.fillMaxHeight
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.height
13 | import androidx.compose.foundation.layout.padding
14 | import androidx.compose.foundation.layout.width
15 | import androidx.compose.material3.Divider
16 | import androidx.compose.material3.MaterialTheme
17 | import androidx.compose.material3.Slider
18 | import androidx.compose.material3.Surface
19 | import androidx.compose.material3.Text
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.runtime.getValue
22 | import androidx.compose.runtime.mutableStateOf
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.setValue
25 | import androidx.compose.ui.Alignment
26 | import androidx.compose.ui.Modifier
27 | import androidx.compose.ui.graphics.Color
28 | import androidx.compose.ui.layout.layout
29 | import androidx.compose.ui.unit.dp
30 | import androidx.compose.ui.unit.offset
31 | import com.example.customlayout.ui.theme.CustomLayoutTheme
32 |
33 | class DividerColumnExperimentActivity : ComponentActivity() {
34 | override fun onCreate(savedInstanceState: Bundle?) {
35 | super.onCreate(savedInstanceState)
36 | setContent {
37 | CustomLayoutTheme {
38 | // A surface container using the 'background' color from the theme
39 | Surface(
40 | modifier = Modifier.fillMaxSize(),
41 | color = MaterialTheme.colorScheme.background
42 | ) {
43 | Greeting()
44 | }
45 | }
46 | }
47 | }
48 |
49 | @Composable
50 | fun Greeting() {
51 | Column(
52 | horizontalAlignment = Alignment.CenterHorizontally,
53 | ) {
54 | val textWidth = 180.dp
55 | var parentPadding by remember { mutableStateOf(0) }
56 | Row(verticalAlignment = Alignment.CenterVertically) {
57 | Text(text = "Parent Padding: $parentPadding", modifier = Modifier.width(textWidth))
58 | Slider(
59 | value = parentPadding.toFloat(),
60 | onValueChange = { parentPadding = it.toInt() },
61 | valueRange = 0f..64f
62 | )
63 | }
64 | var parentWidth by remember { mutableStateOf(150) }
65 | Row(verticalAlignment = Alignment.CenterVertically) {
66 | Text(text = "Parent Width: $parentWidth", modifier = Modifier.width(textWidth))
67 | Slider(
68 | value = parentWidth.toFloat(),
69 | onValueChange = { parentWidth = it.toInt() },
70 | valueRange = 0f..300f
71 | )
72 | }
73 | var layoutSizeChange by remember { mutableStateOf(0) }
74 | Row(verticalAlignment = Alignment.CenterVertically) {
75 | Text(
76 | text = "Layout's Change: $layoutSizeChange",
77 | modifier = Modifier.width(textWidth)
78 | )
79 | Slider(
80 | value = layoutSizeChange.toFloat(),
81 | onValueChange = { layoutSizeChange = it.toInt() },
82 | valueRange = -250f..250f
83 | )
84 | }
85 | var constraintOffSet by remember { mutableStateOf(0) }
86 | Row(verticalAlignment = Alignment.CenterVertically) {
87 | Text(
88 | text = "Constraint Offset: $constraintOffSet",
89 | modifier = Modifier.width(textWidth)
90 | )
91 | Slider(
92 | value = constraintOffSet.toFloat(),
93 | onValueChange = { constraintOffSet = it.toInt() },
94 | valueRange = -250f..250f
95 | )
96 | }
97 | var placementX by remember { mutableStateOf(0) }
98 | Row(verticalAlignment = Alignment.CenterVertically) {
99 | Text(text = "Placement X: $placementX", modifier = Modifier.width(textWidth))
100 | Slider(
101 | value = placementX.toFloat(),
102 | onValueChange = { placementX = it.toInt() },
103 | valueRange = -150f..150f
104 | )
105 | }
106 | var placementY by remember { mutableStateOf(0) }
107 | Row(verticalAlignment = Alignment.CenterVertically) {
108 | Text(text = "Placement Y: $placementY", modifier = Modifier.width(textWidth))
109 | Slider(
110 | value = placementY.toFloat(),
111 | onValueChange = { placementY = it.toInt() },
112 | valueRange = -150f..150f
113 | )
114 | }
115 | Column(
116 | verticalArrangement = Arrangement.spacedBy(16.dp),
117 | modifier = Modifier
118 | .width(parentWidth.dp)
119 | .fillMaxHeight()
120 | .background(Color.Yellow)
121 | .padding(parentPadding.dp)
122 | ) {
123 | Divider(modifier = Modifier.height(20.dp))
124 | Divider(modifier = Modifier
125 | .height(20.dp)
126 | .layout { measurable, constraints ->
127 | val placeable =
128 | measurable.measure(constraints.offset(constraintOffSet.dp.roundToPx()))
129 | layout(
130 | placeable.width + layoutSizeChange.dp.roundToPx(),
131 | placeable.height
132 | ) { placeable.place(placementX.dp.roundToPx(), placementY.dp.roundToPx()) }
133 | }
134 | )
135 | }
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/DividerLazyColumnExperimentActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.fillMaxHeight
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.height
13 | import androidx.compose.foundation.layout.padding
14 | import androidx.compose.foundation.layout.width
15 | import androidx.compose.foundation.lazy.LazyColumn
16 | import androidx.compose.material3.Divider
17 | import androidx.compose.material3.MaterialTheme
18 | import androidx.compose.material3.Slider
19 | import androidx.compose.material3.Surface
20 | import androidx.compose.material3.Text
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.mutableStateOf
24 | import androidx.compose.runtime.remember
25 | import androidx.compose.runtime.setValue
26 | import androidx.compose.ui.Alignment
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.graphics.Color
29 | import androidx.compose.ui.layout.layout
30 | import androidx.compose.ui.unit.dp
31 | import androidx.compose.ui.unit.offset
32 | import com.example.customlayout.ui.theme.CustomLayoutTheme
33 |
34 | class DividerLazyColumnExperimentActivity: ComponentActivity() {
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 | setContent {
38 | CustomLayoutTheme {
39 | // A surface container using the 'background' color from the theme
40 | Surface(
41 | modifier = Modifier.fillMaxSize(),
42 | color = MaterialTheme.colorScheme.background
43 | ) {
44 | Greeting()
45 | }
46 | }
47 | }
48 | }
49 |
50 | @Composable
51 | fun Greeting() {
52 | Column(
53 | horizontalAlignment = Alignment.CenterHorizontally,
54 | ) {
55 | val textWidth = 180.dp
56 | var parentPadding by remember { mutableStateOf(0) }
57 | Row(verticalAlignment = Alignment.CenterVertically) {
58 | Text(text = "Parent Padding: $parentPadding", modifier = Modifier.width(textWidth))
59 | Slider(
60 | value = parentPadding.toFloat(),
61 | onValueChange = { parentPadding = it.toInt() },
62 | valueRange = 0f..64f
63 | )
64 | }
65 | var parentWidth by remember { mutableStateOf(150) }
66 | Row(verticalAlignment = Alignment.CenterVertically) {
67 | Text(text = "Parent Width: $parentWidth", modifier = Modifier.width(textWidth))
68 | Slider(
69 | value = parentWidth.toFloat(),
70 | onValueChange = { parentWidth = it.toInt() },
71 | valueRange = 0f..300f
72 | )
73 | }
74 | var layoutSizeChange by remember { mutableStateOf(0) }
75 | Row(verticalAlignment = Alignment.CenterVertically) {
76 | Text(
77 | text = "Layout's Change: $layoutSizeChange",
78 | modifier = Modifier.width(textWidth)
79 | )
80 | Slider(
81 | value = layoutSizeChange.toFloat(),
82 | onValueChange = { layoutSizeChange = it.toInt() },
83 | valueRange = -250f..250f
84 | )
85 | }
86 | var constraintOffSet by remember { mutableStateOf(0) }
87 | Row(verticalAlignment = Alignment.CenterVertically) {
88 | Text(
89 | text = "Constraint Offset: $constraintOffSet",
90 | modifier = Modifier.width(textWidth)
91 | )
92 | Slider(
93 | value = constraintOffSet.toFloat(),
94 | onValueChange = { constraintOffSet = it.toInt() },
95 | valueRange = -250f..250f
96 | )
97 | }
98 | var placementX by remember { mutableStateOf(0) }
99 | Row(verticalAlignment = Alignment.CenterVertically) {
100 | Text(text = "Placement X: $placementX", modifier = Modifier.width(textWidth))
101 | Slider(
102 | value = placementX.toFloat(),
103 | onValueChange = { placementX = it.toInt() },
104 | valueRange = -150f..150f
105 | )
106 | }
107 | var placementY by remember { mutableStateOf(0) }
108 | Row(verticalAlignment = Alignment.CenterVertically) {
109 | Text(text = "Placement Y: $placementY", modifier = Modifier.width(textWidth))
110 | Slider(
111 | value = placementY.toFloat(),
112 | onValueChange = { placementY = it.toInt() },
113 | valueRange = -150f..150f
114 | )
115 | }
116 | LazyColumn(
117 | verticalArrangement = Arrangement.spacedBy(16.dp),
118 | modifier = Modifier
119 | .width(parentWidth.dp)
120 | .fillMaxHeight()
121 | .background(Color.Yellow)
122 | .padding(parentPadding.dp)
123 | ) {
124 | item {
125 | Divider(modifier = Modifier.height(20.dp))
126 | }
127 | item {
128 | Divider(modifier = Modifier.height(20.dp)
129 | .layout { measurable, constraints ->
130 | val placeable =
131 | measurable.measure(constraints.offset(constraintOffSet.dp.roundToPx()))
132 | layout(
133 | placeable.width + layoutSizeChange.dp.roundToPx(),
134 | placeable.height
135 | ) { placeable.place(placementX.dp.roundToPx(), placementY.dp.roundToPx()) }
136 | }
137 | )
138 | }
139 | }
140 | }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/DividerLikeColumnActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.BoxScope
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.Spacer
14 | import androidx.compose.foundation.layout.fillMaxSize
15 | import androidx.compose.foundation.layout.fillMaxWidth
16 | import androidx.compose.foundation.layout.height
17 | import androidx.compose.foundation.layout.size
18 | import androidx.compose.foundation.layout.width
19 | import androidx.compose.material3.Checkbox
20 | import androidx.compose.material3.Divider
21 | import androidx.compose.material3.MaterialTheme
22 | import androidx.compose.material3.Slider
23 | import androidx.compose.material3.Surface
24 | import androidx.compose.material3.Text
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.runtime.getValue
27 | import androidx.compose.runtime.mutableStateOf
28 | import androidx.compose.runtime.remember
29 | import androidx.compose.runtime.setValue
30 | import androidx.compose.ui.Alignment
31 | import androidx.compose.ui.Modifier
32 | import androidx.compose.ui.graphics.Color
33 | import androidx.compose.ui.layout.layout
34 | import androidx.compose.ui.unit.Dp
35 | import androidx.compose.ui.unit.dp
36 | import androidx.compose.ui.unit.offset
37 | import com.example.customlayout.ui.theme.CustomLayoutTheme
38 |
39 | class DividerLikeColumnActivity :ComponentActivity() {
40 | override fun onCreate(savedInstanceState: Bundle?) {
41 | super.onCreate(savedInstanceState)
42 | setContent {
43 | CustomLayoutTheme {
44 | // A surface container using the 'background' color from the theme
45 | Surface(
46 | modifier = Modifier.fillMaxSize(),
47 | color = MaterialTheme.colorScheme.background
48 | ) {
49 | Greeting()
50 | }
51 | }
52 | }
53 | }
54 |
55 | @Composable
56 | fun Greeting() {
57 | Column(
58 | horizontalAlignment = Alignment.CenterHorizontally,
59 | ) {
60 | val textWidth = 180.dp
61 | var containerSize by remember { mutableStateOf(150) }
62 | Row(verticalAlignment = Alignment.CenterVertically) {
63 | Text(text = "Container Size: $containerSize", modifier = Modifier.width(textWidth))
64 | Slider(
65 | value = containerSize.toFloat(),
66 | onValueChange = { containerSize = it.toInt() },
67 | valueRange = 0f..300f
68 | )
69 | }
70 | var dividerWidth by remember { mutableStateOf(150) }
71 | var useDividerWidth by remember { mutableStateOf(false) }
72 | Row(verticalAlignment = Alignment.CenterVertically) {
73 | Row(modifier = Modifier.width(textWidth),
74 | verticalAlignment = Alignment.CenterVertically
75 | ) {
76 | Checkbox(
77 | checked = useDividerWidth,
78 | onCheckedChange = { useDividerWidth = it }
79 | )
80 | Text(text = "Divider Size: $dividerWidth")
81 | }
82 | Slider(
83 | value = dividerWidth.toFloat(),
84 | onValueChange = { dividerWidth = it.toInt() },
85 | valueRange = 0f..300f,
86 | enabled = useDividerWidth
87 | )
88 | }
89 | var constraintOffSet by remember { mutableStateOf(0) }
90 |
91 | Row(verticalAlignment = Alignment.CenterVertically) {
92 | Text(
93 | text = "Constraint Offset: $constraintOffSet",
94 | modifier = Modifier.width(textWidth)
95 | )
96 | Slider(
97 | value = constraintOffSet.toFloat(),
98 | onValueChange = { constraintOffSet = it.toInt() },
99 | valueRange = -150f..150f
100 | )
101 | }
102 | var layoutSizeChange by remember { mutableStateOf(0) }
103 | Row(verticalAlignment = Alignment.CenterVertically) {
104 | Text(
105 | text = "Layout's Change: $layoutSizeChange",
106 | modifier = Modifier.width(textWidth)
107 | )
108 | Slider(
109 | value = layoutSizeChange.toFloat(),
110 | onValueChange = { layoutSizeChange = it.toInt() },
111 | valueRange = -200f..200f
112 | )
113 | }
114 | var placement by remember { mutableStateOf(0) }
115 | Row(verticalAlignment = Alignment.CenterVertically) {
116 | Text(text = "Placement: $placement", modifier = Modifier.width(textWidth))
117 | Slider(
118 | value = placement.toFloat(),
119 | onValueChange = { placement = it.toInt() },
120 | valueRange = -200f..200f
121 | )
122 | }
123 | var verticalCentered by remember { mutableStateOf(false) }
124 | var horizontalCentered by remember { mutableStateOf(true) }
125 | Row {
126 | Row(modifier = Modifier.weight(1f),
127 | horizontalArrangement = Arrangement.Center,
128 | verticalAlignment = Alignment.CenterVertically
129 | ) {
130 | Checkbox(
131 | checked = verticalCentered,
132 | onCheckedChange = { verticalCentered = it }
133 | )
134 | Text("Vertical Centered")
135 | }
136 | Row(modifier = Modifier.weight(1f),
137 | horizontalArrangement = Arrangement.Center,
138 | verticalAlignment = Alignment.CenterVertically
139 | ) {
140 | Checkbox(
141 | checked = horizontalCentered,
142 | onCheckedChange = { horizontalCentered = it }
143 | )
144 | Text("Horizontal Centered")
145 | }
146 | }
147 | Column(
148 | horizontalAlignment = Alignment.CenterHorizontally,
149 | verticalArrangement = Arrangement.Center,
150 | modifier = Modifier.fillMaxSize()
151 | ) {
152 | val horizontalAlignment = if (verticalCentered)
153 | Alignment.CenterHorizontally else Alignment.Start
154 | val verticalArrangement = if (horizontalCentered)
155 | Arrangement.Center else Arrangement.Top
156 | Column(
157 | horizontalAlignment = horizontalAlignment,
158 | verticalArrangement = verticalArrangement,
159 | modifier = Modifier
160 | .size(containerSize.dp)
161 | .background(Color.Yellow)
162 | ) {
163 | val height = 20.dp
164 | Divider(
165 | Modifier
166 | .height(height)
167 | .fillMaxWidth())
168 | Spacer(modifier = Modifier.height(height))
169 | DividerLayout(
170 | constraintOffSet.dp,
171 | layoutSizeChange.dp,
172 | placement.dp,
173 | dividerWidth.dp,
174 | useDividerWidth
175 | ) {
176 | Box(modifier = Modifier
177 | .height(height)
178 | .fillMaxWidth())
179 | }
180 | }
181 | }
182 | }
183 | }
184 |
185 | @Composable
186 | private fun DividerLayout(
187 | constraintOffSet: Dp,
188 | layoutSizeChange: Dp,
189 | placement: Dp,
190 | dividerWidth: Dp,
191 | useDividerWidth: Boolean,
192 | content: @Composable BoxScope.() -> Unit = {}
193 | ) {
194 | Box(modifier = Modifier
195 | .conditional(useDividerWidth) {
196 | size(dividerWidth)
197 | }
198 | .background(GrayAlpha)
199 | .layout { measurable, constraints ->
200 | // Measure
201 | val placeable = measurable.measure(
202 | constraints.offset(
203 | constraintOffSet.roundToPx(), 0
204 | )
205 | )
206 |
207 | // Layout
208 | layout(
209 | placeable.width + layoutSizeChange.roundToPx(),
210 | placeable.height
211 | ) {
212 | placeable.place(placement.roundToPx(), 0)
213 | }
214 | }
215 | .border(1.dp, Color.Red),
216 | content = content
217 | )
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/DividerLikeLazyColumnActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.BoxScope
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.Spacer
14 | import androidx.compose.foundation.layout.fillMaxSize
15 | import androidx.compose.foundation.layout.fillMaxWidth
16 | import androidx.compose.foundation.layout.height
17 | import androidx.compose.foundation.layout.size
18 | import androidx.compose.foundation.layout.width
19 | import androidx.compose.foundation.lazy.LazyColumn
20 | import androidx.compose.material3.Checkbox
21 | import androidx.compose.material3.Divider
22 | import androidx.compose.material3.MaterialTheme
23 | import androidx.compose.material3.Slider
24 | import androidx.compose.material3.Surface
25 | import androidx.compose.material3.Text
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.runtime.getValue
28 | import androidx.compose.runtime.mutableStateOf
29 | import androidx.compose.runtime.remember
30 | import androidx.compose.runtime.setValue
31 | import androidx.compose.ui.Alignment
32 | import androidx.compose.ui.Modifier
33 | import androidx.compose.ui.graphics.Color
34 | import androidx.compose.ui.layout.layout
35 | import androidx.compose.ui.unit.Dp
36 | import androidx.compose.ui.unit.dp
37 | import androidx.compose.ui.unit.offset
38 | import com.example.customlayout.ui.theme.CustomLayoutTheme
39 |
40 | class DividerLikeLazyColumnActivity :ComponentActivity() {
41 | override fun onCreate(savedInstanceState: Bundle?) {
42 | super.onCreate(savedInstanceState)
43 | setContent {
44 | CustomLayoutTheme {
45 | // A surface container using the 'background' color from the theme
46 | Surface(
47 | modifier = Modifier.fillMaxSize(),
48 | color = MaterialTheme.colorScheme.background
49 | ) {
50 | Greeting()
51 | }
52 | }
53 | }
54 | }
55 |
56 | @Composable
57 | fun Greeting() {
58 | Column(
59 | horizontalAlignment = Alignment.CenterHorizontally,
60 | ) {
61 | val textWidth = 180.dp
62 | var containerSize by remember { mutableStateOf(150) }
63 | Row(verticalAlignment = Alignment.CenterVertically) {
64 | Text(text = "Container Size: $containerSize", modifier = Modifier.width(textWidth))
65 | Slider(
66 | value = containerSize.toFloat(),
67 | onValueChange = { containerSize = it.toInt() },
68 | valueRange = 0f..300f
69 | )
70 | }
71 | var dividerWidth by remember { mutableStateOf(150) }
72 | var useDividerWidth by remember { mutableStateOf(false) }
73 | Row(verticalAlignment = Alignment.CenterVertically) {
74 | Row(modifier = Modifier.width(textWidth),
75 | verticalAlignment = Alignment.CenterVertically
76 | ) {
77 | Checkbox(
78 | checked = useDividerWidth,
79 | onCheckedChange = { useDividerWidth = it }
80 | )
81 | Text(text = "Divider Size: $dividerWidth")
82 | }
83 | Slider(
84 | value = dividerWidth.toFloat(),
85 | onValueChange = { dividerWidth = it.toInt() },
86 | valueRange = 0f..300f,
87 | enabled = useDividerWidth
88 | )
89 | }
90 | var constraintOffSet by remember { mutableStateOf(0) }
91 |
92 | Row(verticalAlignment = Alignment.CenterVertically) {
93 | Text(
94 | text = "Constraint Offset: $constraintOffSet",
95 | modifier = Modifier.width(textWidth)
96 | )
97 | Slider(
98 | value = constraintOffSet.toFloat(),
99 | onValueChange = { constraintOffSet = it.toInt() },
100 | valueRange = -150f..150f
101 | )
102 | }
103 | var layoutSizeChange by remember { mutableStateOf(0) }
104 | Row(verticalAlignment = Alignment.CenterVertically) {
105 | Text(
106 | text = "Layout's Change: $layoutSizeChange",
107 | modifier = Modifier.width(textWidth)
108 | )
109 | Slider(
110 | value = layoutSizeChange.toFloat(),
111 | onValueChange = { layoutSizeChange = it.toInt() },
112 | valueRange = -200f..200f
113 | )
114 | }
115 | var placement by remember { mutableStateOf(0) }
116 | Row(verticalAlignment = Alignment.CenterVertically) {
117 | Text(text = "Placement: $placement", modifier = Modifier.width(textWidth))
118 | Slider(
119 | value = placement.toFloat(),
120 | onValueChange = { placement = it.toInt() },
121 | valueRange = -200f..200f
122 | )
123 | }
124 | var verticalCentered by remember { mutableStateOf(false) }
125 | var horizontalCentered by remember { mutableStateOf(true) }
126 | Row {
127 | Row(modifier = Modifier.weight(1f),
128 | horizontalArrangement = Arrangement.Center,
129 | verticalAlignment = Alignment.CenterVertically
130 | ) {
131 | Checkbox(
132 | checked = verticalCentered,
133 | onCheckedChange = { verticalCentered = it }
134 | )
135 | Text("Vertical Centered")
136 | }
137 | Row(modifier = Modifier.weight(1f),
138 | horizontalArrangement = Arrangement.Center,
139 | verticalAlignment = Alignment.CenterVertically
140 | ) {
141 | Checkbox(
142 | checked = horizontalCentered,
143 | onCheckedChange = { horizontalCentered = it }
144 | )
145 | Text("Horizontal Centered")
146 | }
147 | }
148 | Column(
149 | horizontalAlignment = Alignment.CenterHorizontally,
150 | verticalArrangement = Arrangement.Center,
151 | modifier = Modifier.fillMaxSize()
152 | ) {
153 | val horizontalAlignment = if (verticalCentered)
154 | Alignment.CenterHorizontally else Alignment.Start
155 | val verticalArrangement = if (horizontalCentered)
156 | Arrangement.Center else Arrangement.Top
157 | LazyColumn(
158 | horizontalAlignment = horizontalAlignment,
159 | verticalArrangement = verticalArrangement,
160 | modifier = Modifier
161 | .size(containerSize.dp)
162 | .background(Color.Yellow)
163 | ) {
164 | val height = 20.dp
165 | item {
166 | Divider(
167 | Modifier
168 | .height(height)
169 | .fillMaxWidth()
170 | )
171 | }
172 | item {
173 | Spacer(modifier = Modifier.height(height))
174 | }
175 | item {
176 | DividerLayout(
177 | constraintOffSet.dp,
178 | layoutSizeChange.dp,
179 | placement.dp,
180 | dividerWidth.dp,
181 | useDividerWidth
182 | ) {
183 | Box(
184 | modifier = Modifier
185 | .height(height)
186 | .fillMaxWidth()
187 | )
188 | }
189 | }
190 | }
191 | }
192 | }
193 | }
194 |
195 | @Composable
196 | private fun DividerLayout(
197 | constraintOffSet: Dp,
198 | layoutSizeChange: Dp,
199 | placement: Dp,
200 | dividerWidth: Dp,
201 | useDividerWidth: Boolean,
202 | content: @Composable BoxScope.() -> Unit = {}
203 | ) {
204 | Box(modifier = Modifier
205 | .conditional(useDividerWidth) {
206 | size(dividerWidth)
207 | }
208 | .background(GrayAlpha)
209 | .layout { measurable, constraints ->
210 | // Measure
211 | val placeable = measurable.measure(
212 | constraints.offset(
213 | constraintOffSet.roundToPx(), 0
214 | )
215 | )
216 |
217 | // Layout
218 | layout(
219 | placeable.width + layoutSizeChange.roundToPx(),
220 | placeable.height
221 | ) {
222 | placeable.place(placement.roundToPx(), 0)
223 | }
224 | }
225 | .border(1.dp, Color.Red),
226 | content = content
227 | )
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/DividerLikeOnlyColumnActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.BoxScope
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.Spacer
14 | import androidx.compose.foundation.layout.fillMaxSize
15 | import androidx.compose.foundation.layout.fillMaxWidth
16 | import androidx.compose.foundation.layout.height
17 | import androidx.compose.foundation.layout.size
18 | import androidx.compose.foundation.layout.width
19 | import androidx.compose.material3.Checkbox
20 | import androidx.compose.material3.Divider
21 | import androidx.compose.material3.MaterialTheme
22 | import androidx.compose.material3.Slider
23 | import androidx.compose.material3.Surface
24 | import androidx.compose.material3.Text
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.runtime.getValue
27 | import androidx.compose.runtime.mutableStateOf
28 | import androidx.compose.runtime.remember
29 | import androidx.compose.runtime.setValue
30 | import androidx.compose.ui.Alignment
31 | import androidx.compose.ui.Modifier
32 | import androidx.compose.ui.graphics.Color
33 | import androidx.compose.ui.layout.layout
34 | import androidx.compose.ui.unit.Dp
35 | import androidx.compose.ui.unit.dp
36 | import androidx.compose.ui.unit.offset
37 | import com.example.customlayout.ui.theme.CustomLayoutTheme
38 |
39 | class DividerLikeOnlyColumnActivity :ComponentActivity() {
40 | override fun onCreate(savedInstanceState: Bundle?) {
41 | super.onCreate(savedInstanceState)
42 | setContent {
43 | CustomLayoutTheme {
44 | // A surface container using the 'background' color from the theme
45 | Surface(
46 | modifier = Modifier.fillMaxSize(),
47 | color = MaterialTheme.colorScheme.background
48 | ) {
49 | Greeting()
50 | }
51 | }
52 | }
53 | }
54 |
55 | @Composable
56 | fun Greeting() {
57 | Column(
58 | horizontalAlignment = Alignment.CenterHorizontally,
59 | ) {
60 | val textWidth = 180.dp
61 | var containerSize by remember { mutableStateOf(150) }
62 | Row(verticalAlignment = Alignment.CenterVertically) {
63 | Text(text = "Container Size: $containerSize", modifier = Modifier.width(textWidth))
64 | Slider(
65 | value = containerSize.toFloat(),
66 | onValueChange = { containerSize = it.toInt() },
67 | valueRange = 0f..300f
68 | )
69 | }
70 | var layoutSizeChange by remember { mutableStateOf(0) }
71 | Row(verticalAlignment = Alignment.CenterVertically) {
72 | Text(
73 | text = "Layout's Change: $layoutSizeChange",
74 | modifier = Modifier.width(textWidth)
75 | )
76 | Slider(
77 | value = layoutSizeChange.toFloat(),
78 | onValueChange = { layoutSizeChange = it.toInt() },
79 | valueRange = -200f..200f
80 | )
81 | }
82 | var verticalCentered by remember { mutableStateOf(false) }
83 | var horizontalCentered by remember { mutableStateOf(true) }
84 | Row {
85 | Row(modifier = Modifier.weight(1f),
86 | horizontalArrangement = Arrangement.Center,
87 | verticalAlignment = Alignment.CenterVertically
88 | ) {
89 | Checkbox(
90 | checked = verticalCentered,
91 | onCheckedChange = { verticalCentered = it }
92 | )
93 | Text("Vertical Centered")
94 | }
95 | Row(modifier = Modifier.weight(1f),
96 | horizontalArrangement = Arrangement.Center,
97 | verticalAlignment = Alignment.CenterVertically
98 | ) {
99 | Checkbox(
100 | checked = horizontalCentered,
101 | onCheckedChange = { horizontalCentered = it }
102 | )
103 | Text("Horizontal Centered")
104 | }
105 | }
106 | Column(
107 | horizontalAlignment = Alignment.CenterHorizontally,
108 | modifier = Modifier.fillMaxSize()
109 | ) {
110 | val horizontalAlignment = if (verticalCentered)
111 | Alignment.CenterHorizontally else Alignment.Start
112 | val verticalArrangement = if (horizontalCentered)
113 | Arrangement.Center else Arrangement.Top
114 | Column(
115 | horizontalAlignment = horizontalAlignment,
116 | verticalArrangement = verticalArrangement,
117 | modifier = Modifier
118 | .size(containerSize.dp)
119 | .background(Color.Yellow)
120 | ) {
121 | val height = 20.dp
122 | Divider(
123 | Modifier
124 | .height(height)
125 | .fillMaxWidth())
126 | Spacer(modifier = Modifier.height(height))
127 | Divider(modifier = Modifier
128 | .height(20.dp)
129 | .layout { measurable, constraints ->
130 | val placeable = measurable.measure(constraints)
131 | layout(
132 | width = layoutSizeChange.dp.roundToPx(),
133 | height = placeable.height
134 | ) { placeable.place(0, 0) }
135 | }
136 | )
137 | Spacer(modifier = Modifier.height(height))
138 | DividerLayout(
139 | layoutSizeChange.dp,
140 | ) {
141 | Box(modifier = Modifier
142 | .height(height)
143 | .fillMaxWidth())
144 | }
145 | }
146 | }
147 | }
148 | }
149 |
150 | @Composable
151 | private fun DividerLayout(
152 | layoutSizeChange: Dp,
153 | content: @Composable BoxScope.() -> Unit = {}
154 | ) {
155 | Box(modifier = Modifier
156 | .background(GrayAlpha)
157 | .layout { measurable, constraints ->
158 | // Measure
159 | val placeable = measurable.measure(constraints)
160 |
161 | // Layout
162 | layout(
163 | layoutSizeChange.roundToPx(),
164 | placeable.height
165 | ) {
166 | placeable.place(0, 0)
167 | }
168 | }
169 | .border(1.dp, Color.Red),
170 | content = content
171 | )
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/LayoutModifierSingleExperimentActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.BoxScope
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.fillMaxHeight
14 | import androidx.compose.foundation.layout.fillMaxSize
15 | import androidx.compose.foundation.layout.fillMaxWidth
16 | import androidx.compose.foundation.layout.size
17 | import androidx.compose.foundation.layout.width
18 | import androidx.compose.material3.Checkbox
19 | import androidx.compose.material3.ExperimentalMaterial3Api
20 | import androidx.compose.material3.MaterialTheme
21 | import androidx.compose.material3.Slider
22 | import androidx.compose.material3.Surface
23 | import androidx.compose.material3.Text
24 | import androidx.compose.material3.TextField
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.runtime.getValue
27 | import androidx.compose.runtime.mutableStateOf
28 | import androidx.compose.runtime.remember
29 | import androidx.compose.runtime.setValue
30 | import androidx.compose.ui.Alignment
31 | import androidx.compose.ui.Modifier
32 | import androidx.compose.ui.graphics.Color
33 | import androidx.compose.ui.layout.layout
34 | import androidx.compose.ui.text.input.TextFieldValue
35 | import androidx.compose.ui.unit.Dp
36 | import androidx.compose.ui.unit.dp
37 | import com.example.customlayout.ui.theme.CustomLayoutTheme
38 |
39 | class LayoutModifierSingleExperimentActivity : ComponentActivity() {
40 | override fun onCreate(savedInstanceState: Bundle?) {
41 | super.onCreate(savedInstanceState)
42 | setContent {
43 | CustomLayoutTheme {
44 | // A surface container using the 'background' color from the theme
45 | Surface(
46 | modifier = Modifier.fillMaxSize(),
47 | color = MaterialTheme.colorScheme.background
48 | ) {
49 | Greeting()
50 | }
51 | }
52 | }
53 | }
54 |
55 | @OptIn(ExperimentalMaterial3Api::class)
56 | @Composable
57 | fun Greeting() {
58 | Column(
59 | horizontalAlignment = Alignment.CenterHorizontally,
60 | ) {
61 | val textWidth = 180.dp
62 | var boxSize by remember { mutableStateOf(150) }
63 | Row(verticalAlignment = Alignment.CenterVertically) {
64 | Text(text = "Box Size: $boxSize", modifier = Modifier.width(textWidth))
65 | Slider(
66 | value = boxSize.toFloat(),
67 | onValueChange = { boxSize = it.toInt() },
68 | valueRange = 0f..300f
69 | )
70 | }
71 | var minSize by remember { mutableStateOf(150) }
72 | var maxSize by remember { mutableStateOf(150) }
73 |
74 | Row(verticalAlignment = Alignment.CenterVertically) {
75 | Text(
76 | text = "Box Min Size: $minSize",
77 | modifier = Modifier.width(textWidth)
78 | )
79 | Slider(
80 | value = minSize.toFloat(),
81 | onValueChange = { minSize = it.toInt() },
82 | valueRange = 0f..maxSize.toFloat()
83 | )
84 | }
85 | Row(verticalAlignment = Alignment.CenterVertically) {
86 | Text(
87 | text = "Box Max Size: $maxSize",
88 | modifier = Modifier.width(textWidth)
89 | )
90 | Slider(
91 | value = maxSize.toFloat(),
92 | onValueChange = { maxSize = it.toInt() },
93 | valueRange = minSize.toFloat()..300f
94 | )
95 | }
96 | var layoutSizeChange by remember { mutableStateOf(0) }
97 | Row(verticalAlignment = Alignment.CenterVertically) {
98 | Text(
99 | text = "Layout's Change: $layoutSizeChange",
100 | modifier = Modifier.width(textWidth)
101 | )
102 | Slider(
103 | value = layoutSizeChange.toFloat(),
104 | onValueChange = { layoutSizeChange = it.toInt() },
105 | valueRange = -150f..150f
106 | )
107 | }
108 | var placementX by remember { mutableStateOf(0) }
109 | Row(verticalAlignment = Alignment.CenterVertically) {
110 | Text(text = "Placement X: $placementX", modifier = Modifier.width(textWidth))
111 | Slider(
112 | value = placementX.toFloat(),
113 | onValueChange = { placementX = it.toInt() },
114 | valueRange = -150f..150f
115 | )
116 | }
117 | var placementY by remember { mutableStateOf(0) }
118 | Row(verticalAlignment = Alignment.CenterVertically) {
119 | Text(text = "Placement Y: $placementY", modifier = Modifier.width(textWidth))
120 | Slider(
121 | value = placementY.toFloat(),
122 | onValueChange = { placementY = it.toInt() },
123 | valueRange = -150f..150f
124 | )
125 | }
126 | var text by remember { mutableStateOf(TextFieldValue("")) }
127 | TextField(
128 | value = text,
129 | onValueChange = { newText ->
130 | text = newText
131 | }
132 | )
133 | var fullWidthCheckedState by remember { mutableStateOf(false) }
134 | var fullHeightCheckedState by remember { mutableStateOf(false) }
135 | Row {
136 | Row(modifier = Modifier.weight(1f),
137 | horizontalArrangement = Arrangement.Center,
138 | verticalAlignment = Alignment.CenterVertically
139 | ) {
140 | Checkbox(
141 | checked = fullWidthCheckedState,
142 | onCheckedChange = { fullWidthCheckedState = it }
143 | )
144 | Text("Full Width")
145 | }
146 | Row(modifier = Modifier.weight(1f),
147 | horizontalArrangement = Arrangement.Center,
148 | verticalAlignment = Alignment.CenterVertically
149 | ) {
150 | Checkbox(
151 | checked = fullHeightCheckedState,
152 | onCheckedChange = { fullHeightCheckedState = it }
153 | )
154 | Text("Full Height")
155 | }
156 | }
157 | Column(
158 | horizontalAlignment = Alignment.CenterHorizontally,
159 | verticalArrangement = Arrangement.Center,
160 | modifier = Modifier.fillMaxSize()
161 | ) {
162 | BoxLayout(
163 | boxSize.dp,
164 | minSize.dp,
165 | maxSize.dp,
166 | layoutSizeChange.dp,
167 | placementX.dp,
168 | placementY.dp
169 | ) {
170 | val modifier = Modifier
171 | .conditional(fullWidthCheckedState) {
172 | fillMaxWidth()
173 | }
174 | .conditional(fullHeightCheckedState) {
175 | fillMaxHeight()
176 | }
177 | Text(text.text, modifier = modifier)
178 | }
179 | }
180 | }
181 | }
182 |
183 | @Composable
184 | private fun BoxLayout(
185 | size: Dp,
186 | minSize: Dp,
187 | maxSize: Dp,
188 | layoutSizeChange: Dp,
189 | placementX: Dp,
190 | placementY: Dp,
191 | content: @Composable BoxScope.() -> Unit = {}
192 | ) { Box(modifier = Modifier.size(size).background(Color.Yellow)) {
193 |
194 | Box(modifier = Modifier
195 | .size(size)
196 | .background(GrayAlpha)
197 | .layout { measurable, constraints ->
198 | // Measure
199 | val looseConstraints = constraints.copy(
200 | minWidth = minSize.roundToPx(),
201 | maxWidth = maxSize.roundToPx(),
202 | minHeight = minSize.roundToPx(),
203 | maxHeight = maxSize.roundToPx(),
204 | )
205 | val placeable = measurable.measure(looseConstraints)
206 |
207 | // Layout
208 | layout(
209 | constraints.maxWidth + layoutSizeChange.roundToPx(),
210 | constraints.maxHeight + layoutSizeChange.roundToPx()
211 | ) {
212 | placeable.place(placementX.roundToPx(), placementY.roundToPx())
213 | }
214 | }
215 | .border(1.dp, Color.Red),
216 | content = content
217 | )
218 | }
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/LayoutModifierSizeExperimentActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.BoxScope
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.fillMaxHeight
14 | import androidx.compose.foundation.layout.fillMaxSize
15 | import androidx.compose.foundation.layout.fillMaxWidth
16 | import androidx.compose.foundation.layout.height
17 | import androidx.compose.foundation.layout.width
18 | import androidx.compose.material3.MaterialTheme
19 | import androidx.compose.material3.Slider
20 | import androidx.compose.material3.Surface
21 | import androidx.compose.material3.Text
22 | import androidx.compose.runtime.Composable
23 | import androidx.compose.runtime.getValue
24 | import androidx.compose.runtime.mutableStateOf
25 | import androidx.compose.runtime.remember
26 | import androidx.compose.runtime.setValue
27 | import androidx.compose.ui.Alignment
28 | import androidx.compose.ui.Alignment.Companion.CenterHorizontally
29 | import androidx.compose.ui.Modifier
30 | import androidx.compose.ui.graphics.Color
31 | import androidx.compose.ui.layout.layout
32 | import androidx.compose.ui.unit.Dp
33 | import androidx.compose.ui.unit.dp
34 | import androidx.compose.ui.unit.offset
35 | import com.example.customlayout.ui.theme.CustomLayoutTheme
36 |
37 | class LayoutModifierSizeExperimentActivity : ComponentActivity() {
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 | super.onCreate(savedInstanceState)
40 | setContent {
41 | CustomLayoutTheme {
42 | // A surface container using the 'background' color from the theme
43 | Surface(
44 | modifier = Modifier.fillMaxSize(),
45 | color = MaterialTheme.colorScheme.background
46 | ) {
47 | Greeting()
48 | }
49 | }
50 | }
51 | }
52 |
53 | @Composable
54 | fun Greeting() {
55 | Column(
56 | horizontalAlignment = Alignment.CenterHorizontally,
57 | ) {
58 | val textWidth = 180.dp
59 | var boxSize by remember { mutableStateOf(150) }
60 | var parentSizeX by remember { mutableStateOf(0.9f) }
61 | var parentSizeY by remember { mutableStateOf(0.9f) }
62 | Row (verticalAlignment = Alignment.CenterVertically) {
63 | Text(text = "X Ratio: ${parentSizeX.format(2)}", modifier = Modifier.width(textWidth))
64 | Slider(
65 | value = parentSizeX,
66 | onValueChange = { parentSizeX = it },
67 | valueRange = 0f..1f
68 | )
69 | }
70 | Row (verticalAlignment = Alignment.CenterVertically) {
71 | Text(text = "Y Ratio ${parentSizeY.format(2)}", modifier = Modifier.width(textWidth))
72 | Slider(
73 | value = parentSizeY,
74 | onValueChange = { parentSizeY = it },
75 | valueRange = 0f..1f
76 | )
77 | }
78 | Row (verticalAlignment = Alignment.CenterVertically) {
79 | Text(text = "Size: $boxSize", modifier = Modifier.width(textWidth))
80 | Slider(
81 | value = boxSize.toFloat(),
82 | onValueChange = { boxSize = it.toInt() },
83 | valueRange = 0f..300f
84 | )
85 | }
86 | var layoutSizeChange by remember { mutableStateOf(0) }
87 | Row (verticalAlignment = Alignment.CenterVertically) {
88 | Text(text = "Layout's Change: $layoutSizeChange", modifier = Modifier.width(textWidth))
89 | Slider(
90 | value = layoutSizeChange.toFloat(),
91 | onValueChange = { layoutSizeChange = it.toInt() },
92 | valueRange = -150f..150f
93 | )
94 | }
95 | var constraintOffSet by remember { mutableStateOf(0) }
96 | Row (verticalAlignment = Alignment.CenterVertically) {
97 | Text(text = "Constraint Offset: $constraintOffSet", modifier = Modifier.width(textWidth))
98 | Slider(
99 | value = constraintOffSet.toFloat(),
100 | onValueChange = { constraintOffSet = it.toInt() },
101 | valueRange = -150f..150f
102 | )
103 | }
104 | var placementX by remember { mutableStateOf(0) }
105 | Row (verticalAlignment = Alignment.CenterVertically) {
106 | Text(text = "Placement X: $placementX", modifier = Modifier.width(textWidth))
107 | Slider(
108 | value = placementX.toFloat(),
109 | onValueChange = { placementX = it.toInt() },
110 | valueRange = -150f..150f
111 | )
112 | }
113 | var placementY by remember { mutableStateOf(0) }
114 | Row (verticalAlignment = Alignment.CenterVertically) {
115 | Text(text = "Placement Y: $placementY", modifier = Modifier.width(textWidth))
116 | Slider(
117 | value = placementY.toFloat(),
118 | onValueChange = { placementY = it.toInt() },
119 | valueRange = -150f..150f
120 | )
121 | }
122 | Column(
123 | horizontalAlignment = CenterHorizontally,
124 | verticalArrangement = Arrangement.Center,
125 | modifier = Modifier.fillMaxSize()
126 | ) {
127 | Column(
128 | horizontalAlignment = CenterHorizontally,
129 | verticalArrangement = Arrangement.Center,
130 | modifier = Modifier
131 | .fillMaxWidth(parentSizeX)
132 | .fillMaxHeight(parentSizeY)
133 | .background(Color.LightGray)
134 | ) {
135 | val size = boxSize.dp
136 | BoxLayout(
137 | size,
138 | Color.Red,
139 | layoutSizeChange.dp,
140 | constraintOffSet.dp,
141 | placementX.dp,
142 | placementY.dp
143 | ) {
144 | BoxLayout(
145 | size,
146 | Color.Green,
147 | layoutSizeChange.dp,
148 | constraintOffSet.dp,
149 | placementX.dp,
150 | placementY.dp
151 | ) {
152 | BoxLayout(
153 | size,
154 | Color.Blue,
155 | layoutSizeChange.dp,
156 | constraintOffSet.dp,
157 | placementX.dp,
158 | placementY.dp
159 | ) {
160 | BoxLayout(
161 | size,
162 | Color.Magenta,
163 | layoutSizeChange.dp,
164 | constraintOffSet.dp,
165 | placementX.dp,
166 | placementY.dp
167 | ) {
168 | val textModifier = Modifier.border(1.dp, Color.Cyan)
169 | Text("Hello There", modifier = textModifier)
170 | }
171 | }
172 | }
173 | }
174 | }
175 | }
176 | }
177 |
178 | }
179 |
180 | @Composable
181 | private fun BoxLayout(
182 | size: Dp,
183 | borderColor: Color,
184 | layoutSizeChange: Dp,
185 | constraintOffSet: Dp,
186 | placementX: Dp,
187 | placementY: Dp,
188 | content: @Composable BoxScope.() -> Unit = {}
189 | ) {
190 | Box(modifier = Modifier
191 | .width(size)
192 | .height(size)
193 | .layout { measurable, constraints ->
194 | val placeable = measurable.measure(
195 | constraints.offset(
196 | constraintOffSet.roundToPx(),
197 | constraintOffSet.roundToPx()
198 | )
199 | )
200 | layout(
201 | placeable.width + layoutSizeChange.roundToPx(),
202 | placeable.height + layoutSizeChange.roundToPx()
203 | ) {
204 | placeable.place(placementX.roundToPx(), placementY.roundToPx())
205 | }
206 | }
207 | .border(1.dp, borderColor), content = content)
208 | }
209 | }
210 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/LayoutSizeExperimentActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.border
8 | import androidx.compose.foundation.layout.Arrangement
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.BoxScope
11 | import androidx.compose.foundation.layout.Column
12 | import androidx.compose.foundation.layout.Row
13 | import androidx.compose.foundation.layout.fillMaxHeight
14 | import androidx.compose.foundation.layout.fillMaxSize
15 | import androidx.compose.foundation.layout.fillMaxWidth
16 | import androidx.compose.foundation.layout.height
17 | import androidx.compose.foundation.layout.size
18 | import androidx.compose.foundation.layout.width
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.material3.Slider
21 | import androidx.compose.material3.Surface
22 | import androidx.compose.material3.Text
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.runtime.mutableStateOf
25 | import androidx.compose.runtime.getValue
26 | import androidx.compose.runtime.remember
27 | import androidx.compose.runtime.setValue
28 | import androidx.compose.ui.Alignment
29 | import androidx.compose.ui.Modifier
30 | import androidx.compose.ui.graphics.Color
31 | import androidx.compose.ui.layout.Layout
32 | import androidx.compose.ui.unit.Dp
33 | import androidx.compose.ui.unit.dp
34 | import androidx.compose.ui.unit.offset
35 | import com.example.customlayout.ui.theme.CustomLayoutTheme
36 |
37 | class LayoutSizeExperimentActivity : ComponentActivity() {
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 | super.onCreate(savedInstanceState)
40 | setContent {
41 | CustomLayoutTheme {
42 | // A surface container using the 'background' color from the theme
43 | Surface(
44 | modifier = Modifier.fillMaxSize(),
45 | color = MaterialTheme.colorScheme.background
46 | ) {
47 | Greeting()
48 | }
49 | }
50 | }
51 | }
52 |
53 | @Composable
54 | fun Greeting() {
55 | Column(
56 | horizontalAlignment = Alignment.CenterHorizontally,
57 | ) {
58 | val textWidth = 180.dp
59 | var boxSize by remember { mutableStateOf(150) }
60 | var parentSizeX by remember { mutableStateOf(0.9f) }
61 | var parentSizeY by remember { mutableStateOf(0.9f) }
62 | Row(verticalAlignment = Alignment.CenterVertically) {
63 | Text(
64 | text = "X Ratio: ${parentSizeX.format(2)}",
65 | modifier = Modifier.width(textWidth)
66 | )
67 | Slider(
68 | value = parentSizeX,
69 | onValueChange = { parentSizeX = it },
70 | valueRange = 0f..1f
71 | )
72 | }
73 | Row(verticalAlignment = Alignment.CenterVertically) {
74 | Text(
75 | text = "Y Ratio ${parentSizeY.format(2)}",
76 | modifier = Modifier.width(textWidth)
77 | )
78 | Slider(
79 | value = parentSizeY,
80 | onValueChange = { parentSizeY = it },
81 | valueRange = 0f..1f
82 | )
83 | }
84 | Row(verticalAlignment = Alignment.CenterVertically) {
85 | Text(text = "Size: $boxSize", modifier = Modifier.width(textWidth))
86 | Slider(
87 | value = boxSize.toFloat(),
88 | onValueChange = { boxSize = it.toInt() },
89 | valueRange = 0f..300f
90 | )
91 | }
92 | var layoutSizeChange by remember { mutableStateOf(0) }
93 | Row(verticalAlignment = Alignment.CenterVertically) {
94 | Text(
95 | text = "Layout's Change: $layoutSizeChange",
96 | modifier = Modifier.width(textWidth)
97 | )
98 | Slider(
99 | value = layoutSizeChange.toFloat(),
100 | onValueChange = { layoutSizeChange = it.toInt() },
101 | valueRange = -150f..150f
102 | )
103 | }
104 | var constraintOffSet by remember { mutableStateOf(0) }
105 | Row(verticalAlignment = Alignment.CenterVertically) {
106 | Text(
107 | text = "Constraint Offset: $constraintOffSet",
108 | modifier = Modifier.width(textWidth)
109 | )
110 | Slider(
111 | value = constraintOffSet.toFloat(),
112 | onValueChange = { constraintOffSet = it.toInt() },
113 | valueRange = -150f..150f
114 | )
115 | }
116 | var placementX by remember { mutableStateOf(0) }
117 | Row(verticalAlignment = Alignment.CenterVertically) {
118 | Text(text = "Placement X: $placementX", modifier = Modifier.width(textWidth))
119 | Slider(
120 | value = placementX.toFloat(),
121 | onValueChange = { placementX = it.toInt() },
122 | valueRange = -150f..150f
123 | )
124 | }
125 | var placementY by remember { mutableStateOf(0) }
126 | Row(verticalAlignment = Alignment.CenterVertically) {
127 | Text(text = "Placement Y: $placementY", modifier = Modifier.width(textWidth))
128 | Slider(
129 | value = placementY.toFloat(),
130 | onValueChange = { placementY = it.toInt() },
131 | valueRange = -150f..150f
132 | )
133 | }
134 | Column(
135 | horizontalAlignment = Alignment.CenterHorizontally,
136 | verticalArrangement = Arrangement.Center,
137 | modifier = Modifier.fillMaxSize()
138 | ) {
139 | Column(
140 | horizontalAlignment = Alignment.CenterHorizontally,
141 | verticalArrangement = Arrangement.Center,
142 | modifier = Modifier
143 | .fillMaxWidth(parentSizeX)
144 | .fillMaxHeight(parentSizeY)
145 | .background(Color.LightGray)
146 | ) {
147 | val size = boxSize.dp
148 | BoxLayout(
149 | size,
150 | Color.Red,
151 | layoutSizeChange.dp,
152 | constraintOffSet.dp,
153 | placementX.dp,
154 | placementY.dp
155 | ) {
156 | BoxLayout(
157 | size,
158 | Color.Green,
159 | layoutSizeChange.dp,
160 | constraintOffSet.dp,
161 | placementX.dp,
162 | placementY.dp
163 | ) {
164 | BoxLayout(
165 | size,
166 | Color.Blue,
167 | layoutSizeChange.dp,
168 | constraintOffSet.dp,
169 | placementX.dp,
170 | placementY.dp
171 | ) {
172 | BoxLayout(
173 | size,
174 | Color.Magenta,
175 | layoutSizeChange.dp,
176 | constraintOffSet.dp,
177 | placementX.dp,
178 | placementY.dp
179 | ) {
180 | val textModifier = Modifier.border(1.dp, Color.Blue)
181 | Text("Hello There", modifier = textModifier)
182 | Text("Something Here", modifier = textModifier)
183 | Text("Test it one", modifier = textModifier)
184 | Text("Sometimes", modifier = textModifier)
185 | Text("Short", modifier = textModifier)
186 | }
187 | }
188 | }
189 | }
190 | }
191 | }
192 | }
193 | }
194 |
195 | @Composable
196 | fun BoxLayout(
197 | size: Dp,
198 | borderColor: Color,
199 | layoutSizeChange: Dp,
200 | constraintOffSet: Dp,
201 | placementX: Dp,
202 | placementY: Dp,
203 | content: @Composable () -> Unit = {}
204 | ) {
205 | Layout(
206 | modifier = Modifier
207 | .width(size)
208 | .height(size)
209 | .border(1.dp, borderColor),
210 | content = content
211 | ) { measurables, constraints ->
212 | val placaebles = measurables.map { measurable ->
213 | val looseConstraints = constraints.copy(
214 | minWidth = 0,
215 | minHeight = 0,
216 | )
217 | measurable.measure(constraints = looseConstraints.offset(
218 | constraintOffSet.roundToPx(),
219 | constraintOffSet.roundToPx()
220 | ))
221 | }
222 |
223 | layout(
224 | placaebles.maxOf { it.width } + layoutSizeChange.roundToPx(),
225 | placaebles.sumOf { it.height } + layoutSizeChange.roundToPx()
226 | ) {
227 | var y = placementY.roundToPx()
228 | placaebles.forEach { placeable ->
229 | placeable.place(placementX.roundToPx(), y)
230 | y += placeable.height
231 | }
232 | }
233 | }
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import androidx.activity.ComponentActivity
6 | import androidx.activity.compose.setContent
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.fillMaxSize
10 | import androidx.compose.material3.Button
11 | import androidx.compose.material3.MaterialTheme
12 | import androidx.compose.material3.Surface
13 | import androidx.compose.material3.Text
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.ui.Alignment
16 | import androidx.compose.ui.Modifier
17 | import androidx.compose.ui.unit.dp
18 | import com.example.customlayout.ui.theme.CustomLayoutTheme
19 | import kotlin.reflect.KClass
20 |
21 | class MainActivity : ComponentActivity() {
22 | override fun onCreate(savedInstanceState: Bundle?) {
23 | super.onCreate(savedInstanceState)
24 | setContent {
25 | CustomLayoutTheme {
26 | // A surface container using the 'background' color from the theme
27 | Surface(
28 | modifier = Modifier.fillMaxSize(),
29 | color = MaterialTheme.colorScheme.background
30 | ) {
31 | Greeting()
32 | }
33 | }
34 | }
35 | }
36 |
37 | @Composable
38 | private fun Greeting(modifier: Modifier = Modifier) {
39 | Column(
40 | horizontalAlignment = Alignment.CenterHorizontally,
41 | verticalArrangement = Arrangement.spacedBy(16.dp)
42 | ) {
43 | ButtonLauncher(AutoWidthSortColumnActivity::class)
44 | ButtonLauncher(LayoutSizeExperimentActivity::class)
45 | ButtonLauncher(LayoutModifierSizeExperimentActivity::class)
46 | ButtonLauncher(LayoutModifierSingleExperimentActivity::class)
47 | ButtonLauncher(DividerColumnExperimentActivity::class)
48 | ButtonLauncher(DividerLazyColumnExperimentActivity::class)
49 | ButtonLauncher(NegativePaddingExperimentActivity::class)
50 | ButtonLauncher(BoxAsDividerNegativePaddingBehaviorActivity::class)
51 | ButtonLauncher(DividerLikeOnlyColumnActivity::class)
52 | ButtonLauncher(DividerLikeColumnActivity::class)
53 | ButtonLauncher(DividerLikeLazyColumnActivity::class)
54 | }
55 | }
56 |
57 | @Composable
58 | fun ButtonLauncher(kclass: KClass<*>) {
59 | Button(onClick = {
60 | startActivity(Intent(this, kclass.java))
61 | }) {
62 | Text(kclass.simpleName.toString())
63 | }
64 | }
65 | }
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/NegativePaddingExperimentActivity.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.compose.foundation.background
7 | import androidx.compose.foundation.layout.Arrangement
8 | import androidx.compose.foundation.layout.Column
9 | import androidx.compose.foundation.layout.Row
10 | import androidx.compose.foundation.layout.fillMaxHeight
11 | import androidx.compose.foundation.layout.fillMaxSize
12 | import androidx.compose.foundation.layout.fillMaxWidth
13 | import androidx.compose.foundation.layout.height
14 | import androidx.compose.foundation.layout.offset
15 | import androidx.compose.foundation.layout.padding
16 | import androidx.compose.foundation.layout.requiredWidth
17 | import androidx.compose.foundation.layout.width
18 | import androidx.compose.material3.Divider
19 | import androidx.compose.material3.MaterialTheme
20 | import androidx.compose.material3.Slider
21 | import androidx.compose.material3.Surface
22 | import androidx.compose.material3.Text
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.runtime.getValue
25 | import androidx.compose.runtime.mutableStateOf
26 | import androidx.compose.runtime.remember
27 | import androidx.compose.runtime.setValue
28 | import androidx.compose.ui.Alignment
29 | import androidx.compose.ui.Modifier
30 | import androidx.compose.ui.graphics.Color
31 | import androidx.compose.ui.layout.layout
32 | import androidx.compose.ui.text.style.TextAlign
33 | import androidx.compose.ui.unit.dp
34 | import androidx.compose.ui.unit.offset
35 | import com.example.customlayout.ui.theme.CustomLayoutTheme
36 |
37 | class NegativePaddingExperimentActivity : ComponentActivity() {
38 | override fun onCreate(savedInstanceState: Bundle?) {
39 | super.onCreate(savedInstanceState)
40 | setContent {
41 | CustomLayoutTheme {
42 | // A surface container using the 'background' color from the theme
43 | Surface(
44 | modifier = Modifier.fillMaxSize(),
45 | color = MaterialTheme.colorScheme.background
46 | ) {
47 | Greeting()
48 | }
49 | }
50 | }
51 | }
52 |
53 | @Composable
54 | fun Greeting() {
55 | Column(
56 | horizontalAlignment = Alignment.CenterHorizontally,
57 | ) {
58 | val textWidth = 180.dp
59 | var parentWidth by remember { mutableStateOf(300) }
60 | Row(verticalAlignment = Alignment.CenterVertically) {
61 | Text(text = "Parent Width: $parentWidth", modifier = Modifier.width(textWidth))
62 | Slider(
63 | value = parentWidth.toFloat(),
64 | onValueChange = { parentWidth = it.toInt() },
65 | valueRange = 0f..300f
66 | )
67 | }
68 | var parentPadding by remember { mutableStateOf(0) }
69 | Row(verticalAlignment = Alignment.CenterVertically) {
70 | Text(text = "Parent Padding: $parentPadding", modifier = Modifier.width(textWidth))
71 | Slider(
72 | value = parentPadding.toFloat(),
73 | onValueChange = { parentPadding = it.toInt() },
74 | valueRange = 0f..64f
75 | )
76 | }
77 | var dividersPadding by remember { mutableStateOf(0) }
78 | Row(verticalAlignment = Alignment.CenterVertically) {
79 | Text(
80 | text = "Dividers' Padding: $dividersPadding",
81 | modifier = Modifier.width(textWidth)
82 | )
83 | Slider(
84 | value = dividersPadding.toFloat(),
85 | onValueChange = { dividersPadding = it.toInt() },
86 | valueRange = -150f..150f
87 | )
88 | }
89 | Column(
90 | verticalArrangement = Arrangement.spacedBy(16.dp),
91 | modifier = Modifier
92 | .width(parentWidth.dp)
93 | .fillMaxHeight()
94 | .background(Color.Yellow)
95 | .padding(parentPadding.dp)
96 | ) {
97 | Text("0. Reference Divider")
98 | Divider(modifier = Modifier.height(20.dp))
99 | Text("1. Offset Padding")
100 | DividerOffset(dividersPadding)
101 | Text("2. Required Width Padding")
102 | DividerRequiredWidth(parentWidth, parentPadding, dividersPadding)
103 | Text("3. Layout Constraint Only Padding")
104 | DividerLayoutConstraint(dividersPadding)
105 | Text("4. Layout Managed Padding")
106 | DividerLayoutManaged(dividersPadding)
107 | }
108 | }
109 | }
110 |
111 | @Composable
112 | private fun DividerLayoutManaged(dividersPadding: Int) {
113 | Divider(modifier = Modifier
114 | .height(20.dp)
115 | .layout { measurable, constraints ->
116 | val placeable =
117 | measurable.measure(constraints.offset((-dividersPadding * 2).dp.roundToPx()))
118 | layout(
119 | placeable.width + (dividersPadding * 2).dp.roundToPx(),
120 | placeable.height
121 | ) { placeable.place(0 + dividersPadding.dp.roundToPx(), 0) }
122 | }
123 | )
124 | }
125 |
126 | @Composable
127 | private fun DividerLayoutConstraint(dividersPadding: Int) {
128 | Divider(modifier = Modifier
129 | .height(20.dp)
130 | .layout { measurable, constraints ->
131 | val placeable =
132 | measurable.measure(constraints.offset((-dividersPadding * 2).dp.roundToPx()))
133 | layout(
134 | placeable.width,
135 | placeable.height
136 | ) { placeable.place(0, 0) }
137 | }
138 | )
139 | }
140 |
141 | @Composable
142 | private fun DividerRequiredWidth(
143 | parentWidth: Int,
144 | parentPadding: Int,
145 | dividersPadding: Int
146 | ) {
147 | Divider(
148 | modifier = Modifier
149 | .height(20.dp)
150 | .requiredWidth(
151 | width = (parentWidth.dp - parentPadding.dp * 2 - dividersPadding.dp * 2),
152 | )
153 | )
154 | }
155 |
156 | @Composable
157 | private fun DividerOffset(dividersPadding: Int) {
158 | Divider(modifier = Modifier
159 | .height((20.dp))
160 | .offset(dividersPadding.dp))
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/Util.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.graphics.Color
5 |
6 | fun Float.format(digits: Int) = "%.${digits}f".format(this)
7 |
8 | fun Modifier.conditional(condition : Boolean, modifier : Modifier.() -> Modifier) : Modifier {
9 | return if (condition) {
10 | then(modifier(Modifier))
11 | } else {
12 | this
13 | }
14 | }
15 |
16 | val GrayAlpha: Color
17 | get() = Color(148, 148, 148, 128)
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val Purple80 = Color(0xFFD0BCFF)
6 | val PurpleGrey80 = Color(0xFFCCC2DC)
7 | val Pink80 = Color(0xFFEFB8C8)
8 |
9 | val Purple40 = Color(0xFF6650a4)
10 | val PurpleGrey40 = Color(0xFF625b71)
11 | val Pink40 = Color(0xFF7D5260)
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout.ui.theme
2 |
3 | import android.app.Activity
4 | import android.os.Build
5 | import androidx.compose.foundation.isSystemInDarkTheme
6 | import androidx.compose.material3.MaterialTheme
7 | import androidx.compose.material3.darkColorScheme
8 | import androidx.compose.material3.dynamicDarkColorScheme
9 | import androidx.compose.material3.dynamicLightColorScheme
10 | import androidx.compose.material3.lightColorScheme
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.SideEffect
13 | import androidx.compose.ui.graphics.toArgb
14 | import androidx.compose.ui.platform.LocalContext
15 | import androidx.compose.ui.platform.LocalView
16 | import androidx.core.view.WindowCompat
17 |
18 | private val DarkColorScheme = darkColorScheme(
19 | primary = Purple80,
20 | secondary = PurpleGrey80,
21 | tertiary = Pink80
22 | )
23 |
24 | private val LightColorScheme = lightColorScheme(
25 | primary = Purple40,
26 | secondary = PurpleGrey40,
27 | tertiary = Pink40
28 |
29 | /* Other default colors to override
30 | background = Color(0xFFFFFBFE),
31 | surface = Color(0xFFFFFBFE),
32 | onPrimary = Color.White,
33 | onSecondary = Color.White,
34 | onTertiary = Color.White,
35 | onBackground = Color(0xFF1C1B1F),
36 | onSurface = Color(0xFF1C1B1F),
37 | */
38 | )
39 |
40 | @Composable
41 | fun CustomLayoutTheme(
42 | darkTheme: Boolean = isSystemInDarkTheme(),
43 | // Dynamic color is available on Android 12+
44 | dynamicColor: Boolean = true,
45 | content: @Composable () -> Unit
46 | ) {
47 | val colorScheme = when {
48 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
49 | val context = LocalContext.current
50 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
51 | }
52 |
53 | darkTheme -> DarkColorScheme
54 | else -> LightColorScheme
55 | }
56 | val view = LocalView.current
57 | if (!view.isInEditMode) {
58 | SideEffect {
59 | val window = (view.context as Activity).window
60 | window.statusBarColor = colorScheme.primary.toArgb()
61 | WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
62 | }
63 | }
64 |
65 | MaterialTheme(
66 | colorScheme = colorScheme,
67 | typography = Typography,
68 | content = content
69 | )
70 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/customlayout/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout.ui.theme
2 |
3 | import androidx.compose.material3.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val Typography = Typography(
11 | bodyLarge = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp,
15 | lineHeight = 24.sp,
16 | letterSpacing = 0.5.sp
17 | )
18 | /* Other default text styles to override
19 | titleLarge = TextStyle(
20 | fontFamily = FontFamily.Default,
21 | fontWeight = FontWeight.Normal,
22 | fontSize = 22.sp,
23 | lineHeight = 28.sp,
24 | letterSpacing = 0.sp
25 | ),
26 | labelSmall = TextStyle(
27 | fontFamily = FontFamily.Default,
28 | fontWeight = FontWeight.Medium,
29 | fontSize = 11.sp,
30 | lineHeight = 16.sp,
31 | letterSpacing = 0.5.sp
32 | )
33 | */
34 | )
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | CustomLayout
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/customlayout/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.example.customlayout
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | plugins {
3 | id 'com.android.application' version '8.0.1' apply false
4 | id 'com.android.library' version '8.0.1' apply false
5 | id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
6 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elye/demo_android_jetpack_compose_custom_layout/a0b4809293f9b60a03319e23284030c9dfdb36c5/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Jun 18 17:02:36 AEST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "CustomLayout"
16 | include ':app'
17 |
--------------------------------------------------------------------------------