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

[Row & Column feature parity] LastBaseline alignment experiment #805

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ import androidx.compose.foundation.layout.LayoutScopeMarker
import androidx.compose.runtime.Stable
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.core.parser.CLArray
@@ -83,6 +84,11 @@ class ConstrainScope internal constructor(
*/
val baseline: BaselineAnchorable = ConstraintBaselineAnchorable(containerObject)

/**
* The [LastBaseline] of the layout - can be constrained using [LastBaselineAnchorable.linkTo].
*/
val lastBaseline: LastBaselineAnchorable = ConstraintLastBaselineAnchorable(containerObject)

/**
* The width of the [ConstraintLayout] child.
*/
@@ -399,6 +405,7 @@ class ConstrainScope internal constructor(
containerObject.remove("top")
containerObject.remove("bottom")
containerObject.remove("baseline")
containerObject.remove("lastBaseline")
}

/**
@@ -523,6 +530,23 @@ private class ConstraintBaselineAnchorable constructor(
containerObject.put("baseline", constraintArray)
}

/**
* Adds a link towards a [ConstraintLayoutBaseScope.LastBaselineAnchor].
*/
override fun linkTo(
anchor: ConstraintLayoutBaseScope.LastBaselineAnchor,
margin: Dp,
goneMargin: Dp
) {
val constraintArray = CLArray(charArrayOf()).apply {
add(CLString.from(anchor.id.toString()))
add(CLString.from("lastBaseline"))
add(CLNumber(margin.value))
add(CLNumber(goneMargin.value))
}
containerObject.put("baseline", constraintArray)
}

/**
* Adds a link towards a [ConstraintLayoutBaseScope.HorizontalAnchor].
*/
@@ -540,4 +564,64 @@ private class ConstraintBaselineAnchorable constructor(
}
containerObject.put("baseline", constraintArray)
}
}

/**
* Represents the [LastBaseline] of a layout that can be anchored
* using [linkTo] in their `Modifier.constrainAs` blocks.
*/
private class ConstraintLastBaselineAnchorable constructor(
private val containerObject: CLObject
) : LastBaselineAnchorable {
/**
* Adds a link towards a [ConstraintLayoutBaseScope.BaselineAnchor].
*/
override fun linkTo(
anchor: ConstraintLayoutBaseScope.BaselineAnchor,
margin: Dp,
goneMargin: Dp
) {
val constraintArray = CLArray(charArrayOf()).apply {
add(CLString.from(anchor.id.toString()))
add(CLString.from("baseline"))
add(CLNumber(margin.value))
add(CLNumber(goneMargin.value))
}
containerObject.put("lastBaseline", constraintArray)
}

/**
* Adds a link towards a [ConstraintLayoutBaseScope.LastBaselineAnchor].
*/
override fun linkTo(
anchor: ConstraintLayoutBaseScope.LastBaselineAnchor,
margin: Dp,
goneMargin: Dp
) {
val constraintArray = CLArray(charArrayOf()).apply {
add(CLString.from(anchor.id.toString()))
add(CLString.from("lastBaseline"))
add(CLNumber(margin.value))
add(CLNumber(goneMargin.value))
}
containerObject.put("lastBaseline", constraintArray)
}

/**
* Adds a link towards a [ConstraintLayoutBaseScope.HorizontalAnchor].
*/
override fun linkTo(
anchor: ConstraintLayoutBaseScope.HorizontalAnchor,
margin: Dp,
goneMargin: Dp
) {
val targetAnchorName = AnchorFunctions.horizontalAnchorIndexToAnchorName(anchor.index)
val constraintArray = CLArray(charArrayOf()).apply {
add(CLString.from(anchor.id.toString()))
add(CLString.from(targetAnchorName))
add(CLNumber(margin.value))
add(CLNumber(goneMargin.value))
}
containerObject.put("lastBaseline", constraintArray)
}
}
Original file line number Diff line number Diff line change
@@ -55,6 +55,7 @@ import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.drawscope.DrawScope
import androidx.compose.ui.layout.AlignmentLine
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.layout.LayoutIdParentData
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.layout.MeasurePolicy
@@ -1210,6 +1211,8 @@ internal open class Measurer(
val baseline =
if (currentPlaceable != null && state.isBaselineNeeded(constraintWidget)) {
currentPlaceable[FirstBaseline]
} else if (currentPlaceable != null && state.isLastBaselineNeeded(constraintWidget)) {
currentPlaceable[LastBaseline]
} else {
AlignmentLine.Unspecified
}
Original file line number Diff line number Diff line change
@@ -19,6 +19,7 @@ package androidx.constraintlayout.compose
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.core.parser.CLArray
@@ -105,6 +106,19 @@ abstract class ConstraintLayoutBaseScope internal constructor(extendFrom: CLObje
val reference: LayoutReference
)

/**
* Represents a horizontal anchor corresponding to the [LastBaseline] of a layout that other
* layouts can link to in their `Modifier.constrainAs` or `constrain` blocks.
*
* @param reference The [LayoutReference] that this anchor belongs to.
*/
// TODO(popam): investigate if this can be just a HorizontalAnchor
@Stable
data class LastBaselineAnchor internal constructor(
internal val id: Any,
val reference: LayoutReference
)

/**
* Specifies additional constraints associated to the horizontal chain identified with [ref].
*/
@@ -1740,6 +1754,12 @@ class ConstrainedLayoutReference(override val id: Any) : LayoutReference(id) {
*/
@Stable
val baseline = ConstraintLayoutBaseScope.BaselineAnchor(id, this)

/**
* The lastBaseline anchor of this layout.
*/
@Stable
val lastBaseline = ConstraintLayoutBaseScope.LastBaselineAnchor(id, this)
}

/**
@@ -1918,6 +1938,7 @@ class VerticalAlign internal constructor(
val Bottom = VerticalAlign("bottom")
val Center = VerticalAlign("center")
val Baseline = VerticalAlign("baseline")
val LastBaseline = VerticalAlign("lastBaseline")
}
}

Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@ package androidx.constraintlayout.compose

import android.util.Log
import androidx.compose.ui.layout.FirstBaseline
import androidx.compose.ui.layout.LastBaseline
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.constraintlayout.core.parser.CLArray
@@ -64,6 +65,15 @@ interface HorizontalAnchorable {
margin: Dp = 0.dp,
goneMargin: Dp = 0.dp
)

/**
* Adds a link towards a [ConstraintLayoutBaseScope.LastBaselineAnchor].
*/
fun linkTo(
anchor: ConstraintLayoutBaseScope.LastBaselineAnchor,
margin: Dp = 0.dp,
goneMargin: Dp = 0.dp
)
}

@JvmDefaultWithCompatibility
@@ -81,6 +91,49 @@ interface BaselineAnchorable {
goneMargin: Dp = 0.dp
)

/**
* Adds a link towards a [ConstraintLayoutBaseScope.LastBaselineAnchor].
*/
fun linkTo(
anchor: ConstraintLayoutBaseScope.LastBaselineAnchor,
margin: Dp = 0.dp,
goneMargin: Dp = 0.dp
)

/**
* Adds a link towards a [ConstraintLayoutBaseScope.HorizontalAnchor].
*/
fun linkTo(
anchor: ConstraintLayoutBaseScope.HorizontalAnchor,
margin: Dp = 0.dp,
goneMargin: Dp = 0.dp
)
}

@JvmDefaultWithCompatibility
/**
* Represents the [LastBaseline] of a layout that can be anchored
* using [linkTo] in their `Modifier.constrainAs` blocks.
*/
interface LastBaselineAnchorable {
/**
* Adds a link towards a [ConstraintLayoutBaseScope.BaselineAnchor].
*/
fun linkTo(
anchor: ConstraintLayoutBaseScope.BaselineAnchor,
margin: Dp = 0.dp,
goneMargin: Dp = 0.dp
)

/**
* Adds a link towards a [ConstraintLayoutBaseScope.LastBaselineAnchor].
*/
fun linkTo(
anchor: ConstraintLayoutBaseScope.LastBaselineAnchor,
margin: Dp = 0.dp,
goneMargin: Dp = 0.dp
)

/**
* Adds a link towards a [ConstraintLayoutBaseScope.HorizontalAnchor].
*/
@@ -147,6 +200,20 @@ internal abstract class BaseHorizontalAnchorable(
}
containerObject.put(anchorName, constraintArray)
}

final override fun linkTo(
anchor: ConstraintLayoutBaseScope.LastBaselineAnchor,
margin: Dp,
goneMargin: Dp
) {
val constraintArray = CLArray(charArrayOf()).apply {
add(CLString.from(anchor.id.toString()))
add(CLString.from("lastBaseline"))
add(CLNumber(margin.value))
add(CLNumber(goneMargin.value))
}
containerObject.put(anchorName, constraintArray)
}
}

internal object AnchorFunctions {
Original file line number Diff line number Diff line change
@@ -87,7 +87,9 @@ public interface ConstraintReferenceFactory {
protected int mMarginBottomGone = 0;

int mMarginBaseline = 0;
int mMarginLastBaseline = 0;
int mMarginBaselineGone = 0;
int mMarginLastBaselineGone = 0;

float mPivotX = Float.NaN;
float mPivotY = Float.NaN;
@@ -118,12 +120,19 @@ public interface ConstraintReferenceFactory {
protected Object mTopToTop = null;
protected Object mTopToBottom = null;
@Nullable Object mTopToBaseline = null;
@Nullable Object mTopToLastBaseline = null;
protected Object mBottomToTop = null;
protected Object mBottomToBottom = null;
@Nullable Object mBottomToBaseline = null;
@Nullable Object mBottomToLastBaseline = null;
Object mBaselineToBaseline = null;
Object mBaselineToLastBaseline = null;
Object mBaselineToTop = null;
Object mBaselineToBottom = null;
Object mLastBaselineToTop = null;
Object mLastBaselineToBottom = null;
Object mLastBaselineToBaseline = null;
Object mLastBaselineToLastBaseline = null;
Object mCircularConstraint = null;
private float mCircularAngle;
private float mCircularDistance;
@@ -292,6 +301,7 @@ public void setVerticalChainWeight(float weight) {
public ConstraintReference clearVertical() {
top().clear();
baseline().clear();
lastBaseline().clear();
bottom().clear();
return this;
}
@@ -487,6 +497,12 @@ public ConstraintReference baseline() {
return this;
}

// @TODO: add description
public ConstraintReference lastBaseline() {
mLast = State.Constraint.LASTBASELINE_TO_LASTBASELINE;
return this;
}

// @TODO: add description
public void addCustomColor(String name, int color) {
mCustomColors.put(name, color);
@@ -514,8 +530,13 @@ private void dereference() {
mBottomToTop = get(mBottomToTop);
mBottomToBottom = get(mBottomToBottom);
mBaselineToBaseline = get(mBaselineToBaseline);
mBaselineToLastBaseline = get(mBaselineToLastBaseline);
mBaselineToTop = get(mBaselineToTop);
mBaselineToBottom = get(mBaselineToBottom);
mLastBaselineToBaseline = get(mLastBaselineToBaseline);
mLastBaselineToLastBaseline = get(mLastBaselineToLastBaseline);
mLastBaselineToTop = get(mLastBaselineToTop);
mLastBaselineToBottom = get(mLastBaselineToBottom);
}

// @TODO: add description
@@ -594,6 +615,12 @@ ConstraintReference topToBaseline(Object reference) {
return this;
}

ConstraintReference topToLastBaseline(Object reference) {
mLast = State.Constraint.TOP_TO_LASTBASELINE;
mTopToLastBaseline = reference;
return this;
}

// @TODO: add description
public ConstraintReference bottomToTop(Object reference) {
mLast = State.Constraint.BOTTOM_TO_TOP;
@@ -614,13 +641,26 @@ ConstraintReference bottomToBaseline(Object reference) {
return this;
}

ConstraintReference bottomToLastBaseline(Object reference) {
mLast = State.Constraint.BOTTOM_TO_LASTBASELINE;
mBottomToLastBaseline = reference;
return this;
}

// @TODO: add description
public ConstraintReference baselineToBaseline(Object reference) {
mLast = State.Constraint.BASELINE_TO_BASELINE;
mBaselineToBaseline = reference;
return this;
}

// @TODO: add description
public ConstraintReference baselineToLastBaseline(Object reference) {
mLast = State.Constraint.BASELINE_TO_LASTBASELINE;
mBaselineToLastBaseline = reference;
return this;
}

// @TODO: add description
public ConstraintReference baselineToTop(Object reference) {
mLast = State.Constraint.BASELINE_TO_TOP;
@@ -635,6 +675,34 @@ public ConstraintReference baselineToBottom(Object reference) {
return this;
}

// @TODO: add description
public ConstraintReference lastBaselineToTop(Object reference) {
mLast = State.Constraint.LASTBASELINE_TO_TOP;
mLastBaselineToTop = reference;
return this;
}

// @TODO: add description
public ConstraintReference lastBaselineToBottom(Object reference) {
mLast = State.Constraint.LASTBASELINE_TO_BOTTOM;
mLastBaselineToBottom = reference;
return this;
}

// @TODO: add description
public ConstraintReference lastBaselineToBaseline(Object reference) {
mLast = State.Constraint.LASTBASELINE_TO_BASELINE;
mLastBaselineToBaseline = reference;
return this;
}

// @TODO: add description
public ConstraintReference lastBaselineToLastBaseline(Object reference) {
mLast = State.Constraint.LASTBASELINE_TO_LASTBASELINE;
mLastBaselineToLastBaseline = reference;
return this;
}

// @TODO: add description
public ConstraintReference centerHorizontally(Object reference) {
Object ref = get(reference);
@@ -731,22 +799,32 @@ public ConstraintReference margin(int value) {
break;
case TOP_TO_TOP:
case TOP_TO_BOTTOM:
case TOP_TO_BASELINE: {
case TOP_TO_BASELINE:
case TOP_TO_LASTBASELINE: {
mMarginTop = value;
}
break;
case BOTTOM_TO_TOP:
case BOTTOM_TO_BOTTOM:
case BOTTOM_TO_BASELINE: {
case BOTTOM_TO_BASELINE:
case BOTTOM_TO_LASTBASELINE: {
mMarginBottom = value;
}
break;
case BASELINE_TO_BOTTOM:
case BASELINE_TO_TOP:
case BASELINE_TO_BASELINE: {
case BASELINE_TO_BASELINE:
case BASELINE_TO_LASTBASELINE: {
mMarginBaseline = value;
}
break;
case LASTBASELINE_TO_BOTTOM:
case LASTBASELINE_TO_TOP:
case LASTBASELINE_TO_BASELINE:
case LASTBASELINE_TO_LASTBASELINE: {
mMarginLastBaseline = value;
}
break;
case CIRCULAR_CONSTRAINT: {
mCircularDistance = value;
}
@@ -791,22 +869,32 @@ public ConstraintReference marginGone(int value) {
break;
case TOP_TO_TOP:
case TOP_TO_BOTTOM:
case TOP_TO_BASELINE: {
case TOP_TO_BASELINE:
case TOP_TO_LASTBASELINE: {
mMarginTopGone = value;
}
break;
case BOTTOM_TO_TOP:
case BOTTOM_TO_BOTTOM:
case BOTTOM_TO_BASELINE: {
case BOTTOM_TO_BASELINE:
case BOTTOM_TO_LASTBASELINE: {
mMarginBottomGone = value;
}
break;
case BASELINE_TO_TOP:
case BASELINE_TO_BOTTOM:
case BASELINE_TO_BASELINE: {
case BASELINE_TO_BASELINE:
case BASELINE_TO_LASTBASELINE: {
mMarginBaselineGone = value;
}
break;
case LASTBASELINE_TO_TOP:
case LASTBASELINE_TO_BOTTOM:
case LASTBASELINE_TO_BASELINE:
case LASTBASELINE_TO_LASTBASELINE: {
mMarginLastBaselineGone = value;
}
break;
default:
break;
}
@@ -855,9 +943,11 @@ public ConstraintReference bias(float value) {
case TOP_TO_TOP:
case TOP_TO_BOTTOM:
case TOP_TO_BASELINE:
case TOP_TO_LASTBASELINE:
case BOTTOM_TO_TOP:
case BOTTOM_TO_BOTTOM:
case BOTTOM_TO_BASELINE: {
case BOTTOM_TO_BASELINE:
case BOTTOM_TO_LASTBASELINE: {
mVerticalBias = value;
}
break;
@@ -890,6 +980,11 @@ public ConstraintReference clearAll() {
mBottomToBottom = null;
mMarginBottom = 0;
mBaselineToBaseline = null;
mLastBaselineToTop = null;
mLastBaselineToBottom = null;
mLastBaselineToLastBaseline = null;
mLastBaselineToBaseline = null;
mMarginLastBaseline = 0;
mCircularConstraint = null;
mHorizontalBias = 0.5f;
mVerticalBias = 0.5f;
@@ -940,20 +1035,24 @@ public ConstraintReference clear() {
break;
case TOP_TO_TOP:
case TOP_TO_BOTTOM:
case TOP_TO_BASELINE: {
case TOP_TO_BASELINE:
case TOP_TO_LASTBASELINE: {
mTopToTop = null;
mTopToBottom = null;
mTopToBaseline = null;
mTopToLastBaseline = null;
mMarginTop = 0;
mMarginTopGone = 0;
}
break;
case BOTTOM_TO_TOP:
case BOTTOM_TO_BOTTOM:
case BOTTOM_TO_BASELINE: {
case BOTTOM_TO_BASELINE:
case BOTTOM_TO_LASTBASELINE: {
mBottomToTop = null;
mBottomToBottom = null;
mBottomToBaseline = null;
mBottomToLastBaseline = null;
mMarginBottom = 0;
mMarginBottomGone = 0;
}
@@ -962,6 +1061,22 @@ public ConstraintReference clear() {
mBaselineToBaseline = null;
}
break;
case LASTBASELINE_TO_LASTBASELINE: {
mLastBaselineToLastBaseline = null;
}
break;
case LASTBASELINE_TO_BASELINE: {
mLastBaselineToBaseline = null;
}
break;
case LASTBASELINE_TO_TOP: {
mLastBaselineToTop = null;
}
break;
case LASTBASELINE_TO_BOTTOM: {
mLastBaselineToBottom = null;
}
break;
case CIRCULAR_CONSTRAINT: {
mCircularConstraint = null;
}
@@ -1046,7 +1161,8 @@ private void applyConnection(ConstraintWidget widget,
ConstraintAnchor.Type.BOTTOM), mMarginTop, mMarginTopGone, false);
}
break;
case TOP_TO_BASELINE: {
case TOP_TO_BASELINE:
case TOP_TO_LASTBASELINE: {
widget.immediateConnect(ConstraintAnchor.Type.TOP, target,
ConstraintAnchor.Type.BASELINE, mMarginTop, mMarginTopGone);
}
@@ -1061,22 +1177,28 @@ private void applyConnection(ConstraintWidget widget,
ConstraintAnchor.Type.BOTTOM), mMarginBottom, mMarginBottomGone, false);
}
break;
case BOTTOM_TO_BASELINE: {
case BOTTOM_TO_BASELINE:
case BOTTOM_TO_LASTBASELINE: {
widget.immediateConnect(ConstraintAnchor.Type.BOTTOM, target,
ConstraintAnchor.Type.BASELINE, mMarginBottom, mMarginBottomGone);
}
break;
case BASELINE_TO_BASELINE: {
case BASELINE_TO_BASELINE:
case BASELINE_TO_LASTBASELINE:
case LASTBASELINE_TO_BASELINE:
case LASTBASELINE_TO_LASTBASELINE: {
widget.immediateConnect(ConstraintAnchor.Type.BASELINE, target,
ConstraintAnchor.Type.BASELINE, mMarginBaseline, mMarginBaselineGone);
}
break;
case BASELINE_TO_TOP: {
case BASELINE_TO_TOP:
case LASTBASELINE_TO_TOP: {
widget.immediateConnect(ConstraintAnchor.Type.BASELINE,
target, ConstraintAnchor.Type.TOP, mMarginBaseline, mMarginBaselineGone);
}
break;
case BASELINE_TO_BOTTOM: {
case BASELINE_TO_BOTTOM:
case LASTBASELINE_TO_BOTTOM: {
widget.immediateConnect(ConstraintAnchor.Type.BASELINE, target,
ConstraintAnchor.Type.BOTTOM, mMarginBaseline, mMarginBaselineGone);
}
@@ -1105,13 +1227,27 @@ public void applyWidgetConstraints() {
applyConnection(mConstraintWidget, mTopToTop, State.Constraint.TOP_TO_TOP);
applyConnection(mConstraintWidget, mTopToBottom, State.Constraint.TOP_TO_BOTTOM);
applyConnection(mConstraintWidget, mTopToBaseline, State.Constraint.TOP_TO_BASELINE);
applyConnection(mConstraintWidget, mTopToLastBaseline,
State.Constraint.TOP_TO_LASTBASELINE);
applyConnection(mConstraintWidget, mBottomToTop, State.Constraint.BOTTOM_TO_TOP);
applyConnection(mConstraintWidget, mBottomToBottom, State.Constraint.BOTTOM_TO_BOTTOM);
applyConnection(mConstraintWidget, mBottomToBaseline, State.Constraint.BOTTOM_TO_BASELINE);
applyConnection(mConstraintWidget, mBottomToLastBaseline,
State.Constraint.BOTTOM_TO_LASTBASELINE);
applyConnection(mConstraintWidget, mBaselineToBaseline,
State.Constraint.BASELINE_TO_BASELINE);
applyConnection(mConstraintWidget, mBaselineToLastBaseline,
State.Constraint.BASELINE_TO_LASTBASELINE);
applyConnection(mConstraintWidget, mBaselineToTop, State.Constraint.BASELINE_TO_TOP);
applyConnection(mConstraintWidget, mBaselineToBottom, State.Constraint.BASELINE_TO_BOTTOM);
applyConnection(mConstraintWidget, mLastBaselineToBaseline,
State.Constraint.LASTBASELINE_TO_BASELINE);
applyConnection(mConstraintWidget, mLastBaselineToLastBaseline,
State.Constraint.LASTBASELINE_TO_LASTBASELINE);
applyConnection(mConstraintWidget, mLastBaselineToTop,
State.Constraint.LASTBASELINE_TO_TOP);
applyConnection(mConstraintWidget, mLastBaselineToBottom,
State.Constraint.LASTBASELINE_TO_BOTTOM);
applyConnection(mConstraintWidget, mCircularConstraint,
State.Constraint.CIRCULAR_CONSTRAINT);
}
Original file line number Diff line number Diff line change
@@ -411,6 +411,7 @@ static void override(CLObject baseJson,
base.remove("top");
base.remove("bottom");
base.remove("baseline");
base.remove("lastBaseline");
base.remove("center");
base.remove("centerHorizontally");
base.remove("centerVertically");
@@ -1867,6 +1868,10 @@ static void parseConstraint(
state.baselineNeededFor(targetReference.getKey());
reference.topToBaseline(targetReference);
break;
case "lastBaseline":
state.lastBaselineNeededFor(targetReference.getKey());
reference.topToLastBaseline(targetReference);
break;
}
break;
case "bottom":
@@ -1880,6 +1885,9 @@ static void parseConstraint(
case "baseline":
state.baselineNeededFor(targetReference.getKey());
reference.bottomToBaseline(targetReference);
case "lastBaseline":
state.lastBaselineNeededFor(targetReference.getKey());
reference.bottomToLastBaseline(targetReference);
}
break;
case "baseline":
@@ -1889,6 +1897,11 @@ static void parseConstraint(
state.baselineNeededFor(targetReference.getKey());
reference.baselineToBaseline(targetReference);
break;
case "lastBaseline":
state.lastBaselineNeededFor(reference.getKey());
state.lastBaselineNeededFor(targetReference.getKey());
reference.baselineToLastBaseline(targetReference);
break;
case "top":
state.baselineNeededFor(reference.getKey());
reference.baselineToTop(targetReference);
@@ -1899,6 +1912,28 @@ static void parseConstraint(
break;
}
break;
case "lastBaseline":
switch (anchor) {
case "baseline":
state.lastBaselineNeededFor(reference.getKey());
state.lastBaselineNeededFor(targetReference.getKey());
reference.lastBaselineToBaseline(targetReference);
break;
case "lastBaseline":
state.lastBaselineNeededFor(reference.getKey());
state.lastBaselineNeededFor(targetReference.getKey());
reference.lastBaselineToLastBaseline(targetReference);
break;
case "top":
state.lastBaselineNeededFor(reference.getKey());
reference.lastBaselineToTop(targetReference);
break;
case "bottom":
state.lastBaselineNeededFor(reference.getKey());
reference.lastBaselineToBottom(targetReference);
break;
}
break;
case "left":
isHorizontalConstraint = true;
isHorOriginLeft = true;
@@ -1984,6 +2019,11 @@ static void parseConstraint(
state.baselineNeededFor(targetReference.getKey());
reference.baselineToBaseline(targetReference);
break;
case "lastBaseline":
state.lastBaselineNeededFor(reference.getKey());
state.lastBaselineNeededFor(targetReference.getKey());
reference.lastBaselineToLastBaseline(targetReference);
break;
}
}
}
Original file line number Diff line number Diff line change
@@ -68,12 +68,19 @@ public enum Constraint {
TOP_TO_TOP,
TOP_TO_BOTTOM,
TOP_TO_BASELINE,
TOP_TO_LASTBASELINE,
BOTTOM_TO_TOP,
BOTTOM_TO_BOTTOM,
BOTTOM_TO_BASELINE,
BOTTOM_TO_LASTBASELINE,
BASELINE_TO_BASELINE,
BASELINE_TO_LASTBASELINE,
BASELINE_TO_TOP,
LASTBASELINE_TO_TOP,
BASELINE_TO_BOTTOM,
LASTBASELINE_TO_BOTTOM,
LASTBASELINE_TO_BASELINE,
LASTBASELINE_TO_LASTBASELINE,
CENTER_HORIZONTALLY,
CENTER_VERTICALLY,
CIRCULAR_CONSTRAINT
@@ -688,4 +695,35 @@ public boolean isBaselineNeeded(ConstraintWidget constraintWidget) {
}
return mBaselineNeededWidgets.contains(constraintWidget);
}

// ================= add lastBaseline code================================
ArrayList<Object> mLastBaselineNeeded = new ArrayList<>();
ArrayList<ConstraintWidget> mLastBaselineNeededWidgets = new ArrayList<>();
boolean mDirtyLastBaselineNeededWidgets = true;

/**
* Baseline is needed for this object
*/
public void lastBaselineNeededFor(Object id) {
mLastBaselineNeeded.add(id);
mDirtyLastBaselineNeededWidgets = true;
}

/**
* Does this constraintWidget need a lastBaseline
*
* @return true if the constraintWidget needs a lastBaseline
*/
public boolean isLastBaselineNeeded(ConstraintWidget constraintWidget) {
if (mDirtyLastBaselineNeededWidgets) {
mLastBaselineNeededWidgets.clear();
for (Object id : mLastBaselineNeeded) {
ConstraintWidget widget = mReferences.get(id).getConstraintWidget();
if (widget != null) mLastBaselineNeededWidgets.add(widget);
}

mDirtyLastBaselineNeededWidgets = false;
}
return mLastBaselineNeededWidgets.contains(constraintWidget);
}
}
Original file line number Diff line number Diff line change
@@ -100,7 +100,8 @@ public boolean hasFinalValue() {
/**
* Define the type of anchor
*/
public enum Type {NONE, LEFT, TOP, RIGHT, BOTTOM, BASELINE, CENTER, CENTER_X, CENTER_Y}
public enum Type {NONE, LEFT, TOP, RIGHT, BOTTOM, BASELINE,
LAST_BASELINE, CENTER, CENTER_X, CENTER_Y}

private static final int UNSET_GONE_MARGIN = Integer.MIN_VALUE;

@@ -286,14 +287,17 @@ public boolean isValidConnection(ConstraintAnchor anchor) {
if (mType == Type.BASELINE
&& (!anchor.getOwner().hasBaseline() || !getOwner().hasBaseline())) {
return false;
} else if (mType == Type.LAST_BASELINE
&& (!anchor.getOwner().hasLastBaseline() || !getOwner().hasLastBaseline())) {
return false;
}
return true;
}
switch (mType) {
case CENTER: {
// allow everything but baseline and center_x/center_y
return target != Type.BASELINE && target != Type.CENTER_X
&& target != Type.CENTER_Y;
return target != Type.BASELINE && target != Type.LAST_BASELINE
&& target != Type.CENTER_X && target != Type.CENTER_Y;
}
case LEFT:
case RIGHT: {
@@ -311,7 +315,8 @@ public boolean isValidConnection(ConstraintAnchor anchor) {
}
return isCompatible;
}
case BASELINE: {
case BASELINE:
case LAST_BASELINE: {
if (target == Type.LEFT || target == Type.RIGHT) {
return false;
}
@@ -338,6 +343,7 @@ public boolean isSideAnchor() {
case BOTTOM:
return true;
case BASELINE:
case LAST_BASELINE:
case CENTER:
case CENTER_X:
case CENTER_Y:
@@ -361,7 +367,7 @@ public boolean isSimilarDimensionConnection(ConstraintAnchor anchor) {
}
switch (mType) {
case CENTER: {
return target != Type.BASELINE;
return target != Type.BASELINE && target != Type.LAST_BASELINE;
}
case LEFT:
case RIGHT:
@@ -371,9 +377,10 @@ public boolean isSimilarDimensionConnection(ConstraintAnchor anchor) {
case TOP:
case BOTTOM:
case CENTER_Y:
case BASELINE: {
return target == Type.TOP || target == Type.BOTTOM
|| target == Type.CENTER_Y || target == Type.BASELINE;
case BASELINE:
case LAST_BASELINE: {
return target == Type.TOP || target == Type.BOTTOM || target == Type.CENTER_Y
|| target == Type.BASELINE || target == Type.LAST_BASELINE;
}
case NONE:
return false;
@@ -419,6 +426,7 @@ public boolean isVerticalAnchor() {
case TOP:
case BOTTOM:
case BASELINE:
case LAST_BASELINE:
case NONE:
return true;
}
@@ -525,6 +533,7 @@ public final ConstraintAnchor getOpposite() {
return mOwner.mTop;
}
case BASELINE:
case LAST_BASELINE:
case CENTER:
case CENTER_X:
case CENTER_Y:
Original file line number Diff line number Diff line change
@@ -190,6 +190,9 @@ public void setFinalVertical(int y1, int y2) {
if (mHasBaseline) {
mBaseline.setFinalValue(y1 + mBaselineDistance);
}
if (mHasLastBaseline) {
mLastBaseline.setFinalValue(y1 + mLastBaselineDistance);
}
mResolvedVertical = true;
if (LinearSystem.FULL_DEBUG) {
System.out.println("*** SET FINAL VERTICAL FOR " + getDebugName()
@@ -211,6 +214,20 @@ public void setFinalBaseline(int baselineValue) {
mResolvedVertical = true;
}

// @TODO: add description
public void setFinalLastBaseline(int lastBaselineValue) {
if (!mHasLastBaseline) {
return;
}
int y1 = lastBaselineValue - mLastBaselineDistance;
int y2 = y1 + mHeight;
mY = y1;
mTop.setFinalValue(y1);
mBottom.setFinalValue(y2);
mLastBaseline.setFinalValue(lastBaselineValue);
mResolvedVertical = true;
}

public boolean isResolvedHorizontally() {
return mResolvedHorizontal || (mLeft.hasFinalValue() && mRight.hasFinalValue());
}
@@ -255,7 +272,8 @@ public boolean hasDanglingDimension(int orientation) {
return horizontalTargets < 2;
} else {
int verticalTargets = (mTop.mTarget != null ? 1 : 0)
+ (mBottom.mTarget != null ? 1 : 0) + (mBaseline.mTarget != null ? 1 : 0);
+ (mBottom.mTarget != null ? 1 : 0)
+ (mBaseline.mTarget != null || mLastBaseline.mTarget != null ? 1 : 0);
return verticalTargets < 2;
}
}
@@ -333,6 +351,7 @@ public boolean hasResolvedTargets(int orientation, int size) {
private int[] mMaxDimension = {Integer.MAX_VALUE, Integer.MAX_VALUE};
public float mCircleConstraintAngle = Float.NaN;
private boolean mHasBaseline = false;
private boolean mHasLastBaseline = false;
private boolean mInPlaceholder;

private boolean mInVirtualLayout = false;
@@ -385,6 +404,14 @@ public boolean getHasBaseline() {
return mHasBaseline;
}

public boolean getHasLastBaseline() {
return mHasLastBaseline;
}

public void setHasLastBaseline(boolean hasLastBaseline) {
this.mHasLastBaseline = hasLastBaseline;
}

public boolean isInPlaceholder() {
return mInPlaceholder;
}
@@ -457,6 +484,8 @@ public enum DimensionBehaviour {
public ConstraintAnchor mRight = new ConstraintAnchor(this, ConstraintAnchor.Type.RIGHT);
public ConstraintAnchor mBottom = new ConstraintAnchor(this, ConstraintAnchor.Type.BOTTOM);
public ConstraintAnchor mBaseline = new ConstraintAnchor(this, ConstraintAnchor.Type.BASELINE);
public ConstraintAnchor mLastBaseline = new ConstraintAnchor(this,
ConstraintAnchor.Type.LAST_BASELINE);
ConstraintAnchor mCenterX = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER_X);
ConstraintAnchor mCenterY = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER_Y);
public ConstraintAnchor mCenter = new ConstraintAnchor(this, ConstraintAnchor.Type.CENTER);
@@ -500,6 +529,9 @@ public enum DimensionBehaviour {
// Baseline distance relative to the top of the widget
int mBaselineDistance = 0;

// LastBaseline distance relative to the top of the widget
int mLastBaselineDistance = 0;

// Minimum sizes for the widget
protected int mMinWidth;
protected int mMinHeight;
@@ -730,6 +762,7 @@ public StringBuilder serialize(StringBuilder ret) {
serializeAnchor(ret, "right", mRight);
serializeAnchor(ret, "bottom", mBottom);
serializeAnchor(ret, "baseline", mBaseline);
serializeAnchor(ret, "lastBaseline", mLastBaseline);
serializeAnchor(ret, "centerX", mCenterX);
serializeAnchor(ret, "centerY", mCenterY);
serializeCircle(ret, mCenter, mCircleConstraintAngle);
@@ -1332,6 +1365,15 @@ public boolean hasBaseline() {
return mHasBaseline;
}

/**
* Return true if this widget has a lastBaseline
*
* @return true if the widget has a lastBaseline, false otherwise
*/
public boolean hasLastBaseline() {
return mHasLastBaseline;
}

/**
* Return the baseline distance relative to the top of the widget
*
@@ -2233,7 +2275,8 @@ public void resetAnchors() {
/**
* Given a type of anchor, returns the corresponding anchor.
*
* @param anchorType type of the anchor (LEFT, TOP, RIGHT, BOTTOM, BASELINE, CENTER_X, CENTER_Y)
* @param anchorType type of the anchor (LEFT, TOP, RIGHT, BOTTOM, BASELINE, LAST_BASELINE,
* CENTER_X, CENTER_Y)
* @return the matching anchor
*/
public ConstraintAnchor getAnchor(ConstraintAnchor.Type anchorType) {
@@ -2253,6 +2296,9 @@ public ConstraintAnchor getAnchor(ConstraintAnchor.Type anchorType) {
case BASELINE: {
return mBaseline;
}
case LAST_BASELINE: {
return mLastBaseline;
}
case CENTER_X: {
return mCenterX;
}
@@ -3763,6 +3809,7 @@ public void getSceneString(StringBuilder ret) {
getSceneString(ret, "right", mRight);
getSceneString(ret, "bottom", mBottom);
getSceneString(ret, "baseline", mBaseline);
serializeAnchor(ret, "lastBaseline", mLastBaseline);
getSceneString(ret, "centerX", mCenterX);
getSceneString(ret, "centerY", mCenterY);
getSceneString(ret, " width",
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.constraintlayout

import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.layoutId
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.constraintlayout.compose.*

@Preview(group = "baseline")
@Composable
fun LastBaselineDSL() {
ConstraintLayout(
ConstraintSet {
val a = createRefFor("num1")
val b = createRefFor("num2")
constrain(a) {
start.linkTo(parent.start)
end.linkTo(b.start)
top.linkTo(parent.top)
bottom.linkTo(parent.bottom)
}
constrain(b) {
top.linkTo(a.lastBaseline)
start.linkTo(a.end)
end.linkTo(parent.end)
}
},
modifier = Modifier.fillMaxSize()
) {
Button(
modifier = Modifier.layoutId("num1").width(100.dp),
onClick = {},
) {
Text(text = String.format("Text-%s", 1), fontSize = 30.sp)
}

Button(
modifier = Modifier.layoutId("num2").size(200.dp),
onClick = {},
) {
Text(text = String.format("Text-%s", 2), fontSize = 30.sp)
}
}
}

@Preview(group = "baseline1")
@Composable
fun LastBaselineJSON() {
ConstraintLayout(
androidx.constraintlayout.compose.ConstraintSet(
"""
{
num1 : {
left: ['parent', 'left'],
right: ['num2', 'left'],
lastBaseline: ['num2', 'top']
},
num2 : {
top: ['parent', 'top'],
bottom: ['parent', 'bottom'],
left: ['num1', 'right'],
right: ['parent', 'right'],
}
}
""".trimIndent()
),
modifier = Modifier.fillMaxSize()) {
val numArray = arrayOf("1", "2")
for (num in numArray) {
Button(
modifier = Modifier.layoutId("num$num").width(100.dp),
onClick = {},
) {
Text(text = String.format("Text-%s", num), fontSize = 30.sp)
}
}
}
}