Skip to content
This repository was archived by the owner on Dec 27, 2024. It is now read-only.

Commit b65bbfb

Browse files
authored
Add support for custom properties in MotionLayout/Compose (#247)
- added a MotionLayoutScope with utility functions - supports float/int/color/sp/dp for now - add ScreenExample13 showing custom properties
1 parent d932b4b commit b65bbfb

File tree

6 files changed

+325
-20
lines changed

6 files changed

+325
-20
lines changed

constraintlayout/compose/src/main/java/androidx/constraintlayout/compose/ConstraintSetParser.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,13 +417,59 @@ fun parseWidget(
417417
var value = layoutVariables.get(element[constraintName])
418418
reference.rotationZ(value) // element.getDouble(constraintName).toFloat())
419419
}
420+
"custom" -> {
421+
parseCustomProperties(state, layoutVariables, element, reference, constraintName)
422+
}
420423
else -> {
421424
parseConstraint(state, layoutVariables, element, reference, constraintName)
422425
}
423426
}
424427
}
425428
}
426429

430+
private fun parseCustomProperties(
431+
state: State,
432+
layoutVariables: LayoutVariables,
433+
element: JSONObject,
434+
reference: ConstraintReference,
435+
constraintName: String
436+
) {
437+
var json = element.optJSONObject(constraintName)
438+
if (json == null) {
439+
return
440+
}
441+
val properties = json.names() ?: return
442+
(0 until properties.length()).forEach { i ->
443+
val property = properties[i].toString()
444+
val value = json[property]
445+
if (value is Int) {
446+
reference.addCustomFloat(property, value.toFloat())
447+
} else if (value is Float) {
448+
reference.addCustomFloat(property, value)
449+
} else if (value is String) {
450+
if (value.startsWith('#')) {
451+
var r = 0f
452+
var g = 0f
453+
var b = 0f
454+
var a = 1f
455+
if (value.length == 7 || value.length == 9) {
456+
var hr = Integer.valueOf(value.substring(1, 3), 16)
457+
var hg = Integer.valueOf(value.substring(3, 5), 16)
458+
var hb = Integer.valueOf(value.substring(5, 7), 16)
459+
r = hr.toFloat() / 255f
460+
g = hg.toFloat() / 255f
461+
b = hb.toFloat() / 255f
462+
}
463+
if (value.length == 9) {
464+
var ha = Integer.valueOf(value.substring(5, 7), 16)
465+
a = ha.toFloat() / 255f
466+
}
467+
reference.addCustomColor(property, r, g, b, a)
468+
}
469+
}
470+
}
471+
}
472+
427473
private fun parseConstraint(
428474
state: State,
429475
layoutVariables: LayoutVariables,

constraintlayout/compose/src/main/java/androidx/constraintlayout/compose/MotionLayout.kt

Lines changed: 131 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import android.graphics.Matrix
2020
import androidx.compose.foundation.Canvas
2121
import androidx.compose.foundation.layout.Box
2222
import androidx.compose.foundation.layout.BoxScope
23+
import androidx.compose.foundation.layout.LayoutScopeMarker
2324
import androidx.compose.runtime.*
2425
import androidx.compose.ui.Modifier
2526
import androidx.compose.ui.geometry.Offset
@@ -35,9 +36,7 @@ import androidx.compose.ui.layout.MeasureScope
3536
import androidx.compose.ui.layout.MultiMeasureLayout
3637
import androidx.compose.ui.layout.layoutId
3738
import androidx.compose.ui.semantics.semantics
38-
import androidx.compose.ui.unit.Constraints
39-
import androidx.compose.ui.unit.IntSize
40-
import androidx.compose.ui.unit.LayoutDirection
39+
import androidx.compose.ui.unit.*
4140
import androidx.constraintlayout.core.state.Dimension
4241
import androidx.constraintlayout.core.state.WidgetFrame
4342
import androidx.constraintlayout.core.widgets.Optimizer
@@ -57,9 +56,10 @@ inline fun MotionLayout(
5756
debug: EnumSet<MotionLayoutDebugFlags> = EnumSet.of(MotionLayoutDebugFlags.NONE),
5857
modifier: Modifier = Modifier,
5958
optimizationLevel: Int = Optimizer.OPTIMIZATION_STANDARD,
60-
noinline content: @Composable () -> Unit
59+
crossinline content: @Composable MotionLayoutScope.() -> Unit
6160
) {
6261
val measurer = remember { MotionMeasurer() }
62+
val scope = remember { MotionLayoutScope(measurer) }
6363
val progressState = remember { mutableStateOf(0f) }
6464
SideEffect { progressState.value = progress }
6565
val measurePolicy =
@@ -70,7 +70,7 @@ inline fun MotionLayout(
7070
(MultiMeasureLayout(
7171
modifier = modifier.semantics { designInfoProvider = measurer },
7272
measurePolicy = measurePolicy,
73-
content = content
73+
content = { scope.content() }
7474
))
7575
with(measurer) {
7676
drawDebug()
@@ -81,11 +81,78 @@ inline fun MotionLayout(
8181
(MultiMeasureLayout(
8282
modifier = modifier.semantics { designInfoProvider = measurer },
8383
measurePolicy = measurePolicy,
84-
content = content
84+
content = { scope.content() }
8585
))
8686
}
8787
}
8888

89+
@LayoutScopeMarker
90+
class MotionLayoutScope @PublishedApi internal constructor(measurer: MotionMeasurer) {
91+
private var myMeasurer = measurer
92+
93+
class MotionProperties internal constructor(id: String, tag: String?, measurer: MotionMeasurer) {
94+
private var myId = id
95+
private var myTag = null
96+
private var myMeasurer = measurer
97+
98+
fun id() : String {
99+
return myId
100+
}
101+
102+
fun tag() : String? {
103+
return myTag
104+
}
105+
106+
fun color(name: String) : Color {
107+
return myMeasurer.getCustomColor(myId, name)
108+
}
109+
110+
fun float(name: String) : Float {
111+
return myMeasurer.getCustomFloat(myId, name)
112+
}
113+
114+
fun int(name: String): Int {
115+
return myMeasurer.getCustomFloat(myId, name).toInt()
116+
}
117+
118+
fun distance(name: String): Dp {
119+
return myMeasurer.getCustomFloat(myId, name).dp
120+
}
121+
122+
fun fontSize(name: String) : TextUnit {
123+
return myMeasurer.getCustomFloat(myId, name).sp
124+
}
125+
}
126+
127+
fun motionProperties(id: String): MotionProperties {
128+
return MotionProperties(id, null, myMeasurer)
129+
}
130+
131+
fun motionProperties(id: String, tag: String): MotionProperties{
132+
return MotionProperties(id, tag, myMeasurer)
133+
}
134+
135+
fun motionColor(id: String, name: String): Color {
136+
return myMeasurer.getCustomColor(id, name)
137+
}
138+
139+
fun motionFloat(id: String, name: String): Float {
140+
return myMeasurer.getCustomFloat(id, name)
141+
}
142+
143+
fun motionInt(id: String, name: String): Int {
144+
return myMeasurer.getCustomFloat(id, name).toInt()
145+
}
146+
147+
fun motionDistance(id: String, name: String): Dp {
148+
return myMeasurer.getCustomFloat(id, name).dp
149+
}
150+
151+
fun motionFontSize(id: String, name: String): TextUnit {
152+
return myMeasurer.getCustomFloat(id, name).sp
153+
}
154+
}
155+
89156
enum class MotionLayoutDebugFlags {
90157
NONE,
91158
SHOW_ALL
@@ -126,6 +193,8 @@ internal class MotionMeasurer : Measurer() {
126193
var framesStart = ArrayList<WidgetFrame>()
127194
var framesEnd = ArrayList<WidgetFrame>()
128195

196+
fun getProgress() : Float { return motionProgress }
197+
129198
private fun measureConstraintSet(optimizationLevel: Int, constraintSetStart: ConstraintSet,
130199
measurables: List<Measurable>, constraints: Constraints
131200
) {
@@ -329,6 +398,62 @@ internal class MotionMeasurer : Measurer() {
329398
fun clear() {
330399
frameCache.clear()
331400
}
401+
402+
private fun interpolateColor(start: WidgetFrame.Color, end: WidgetFrame.Color, progress: Float) : Color {
403+
if (progress < 0) {
404+
return Color(start.r, start.g, start.b, start.a)
405+
}
406+
if (progress > 1) {
407+
return Color(end.r, end.g, end.b, end.a)
408+
}
409+
val r = (1f - progress) * start.r + progress * (end.r)
410+
val g = (1f - progress) * start.g + progress * (end.g)
411+
val b = (1f - progress) * start.b + progress * (end.b)
412+
return Color(r, g, b)
413+
}
414+
415+
fun findChild(id: String) : Int {
416+
if (root.children.size == 0) {
417+
return -1
418+
}
419+
val ref = state.constraints(id)
420+
val cw = ref.constraintWidget
421+
var index = 0;
422+
for (child in root.children) {
423+
if (cw == child) {
424+
return index
425+
}
426+
index++
427+
}
428+
return -1
429+
}
430+
431+
fun getCustomColor(id: String, name: String): Color {
432+
val index = findChild(id)
433+
if (index == -1) {
434+
return Color.Black
435+
}
436+
val startFrame = framesStart[index]
437+
val endFrame = framesEnd[index]
438+
val startColor = startFrame.getCustomColor(name)
439+
val endColor = endFrame.getCustomColor(name)
440+
if (startColor != null && endColor != null) {
441+
return interpolateColor(startColor, endColor, motionProgress)
442+
}
443+
return Color.Black
444+
}
445+
446+
fun getCustomFloat(id: String, name: String): Float {
447+
val index = findChild(id)
448+
if (index == -1) {
449+
return 0f;
450+
}
451+
val startFrame = framesStart[index]
452+
val endFrame = framesEnd[index]
453+
val startFloat = startFrame.getCustomFloat(name)
454+
val endFloat = endFrame.getCustomFloat(name)
455+
return (1f - motionProgress) * startFloat + motionProgress * endFloat
456+
}
332457
}
333458

334459
private val DEBUG = false

constraintlayout/core/src/main/java/androidx/constraintlayout/core/state/ConstraintReference.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import androidx.constraintlayout.core.widgets.ConstraintWidget;
2222

2323
import java.util.ArrayList;
24+
import java.util.HashMap;
2425

2526
import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
2627
import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
@@ -113,6 +114,9 @@ public interface ConstraintReferenceFactory {
113114
private Object mView;
114115
private ConstraintWidget mConstraintWidget;
115116

117+
private HashMap<String, WidgetFrame.Color> mCustomColors = null;
118+
private HashMap<String, Float> mCustomFloats = null;
119+
116120
public void setView(Object view) {
117121
mView = view;
118122
if (mConstraintWidget != null) {
@@ -355,6 +359,21 @@ public ConstraintReference baseline() {
355359
return this;
356360
}
357361

362+
public void addCustomColor(String name, float r, float g, float b, float a) {
363+
WidgetFrame.Color color = new WidgetFrame.Color(r, g, b, a);
364+
if (mCustomColors == null) {
365+
mCustomColors = new HashMap<>();
366+
}
367+
mCustomColors.put(name, color);
368+
}
369+
370+
public void addCustomFloat(String name, float value) {
371+
if (mCustomFloats == null) {
372+
mCustomFloats = new HashMap<>();
373+
}
374+
mCustomFloats.put(name, value);
375+
}
376+
358377
private void dereference() {
359378
mLeftToLeft = get(mLeftToLeft);
360379
mLeftToRight = get(mLeftToRight);
@@ -835,5 +854,8 @@ public void apply() {
835854
mConstraintWidget.frame.scaleX = mScaleX;
836855
mConstraintWidget.frame.scaleY = mScaleY;
837856
mConstraintWidget.frame.alpha = mAlpha;
857+
858+
mConstraintWidget.frame.mCustomFloats = mCustomFloats;
859+
mConstraintWidget.frame.mCustomColors = mCustomColors;
838860
}
839861
}

0 commit comments

Comments
 (0)