ChainRun.java
/*
* Copyright (C) 2019 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 androidx.constraintlayout.core.widgets.analyzer;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.GONE;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_WRAP;
import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL;
import androidx.constraintlayout.core.widgets.ConstraintAnchor;
import androidx.constraintlayout.core.widgets.ConstraintWidget;
import androidx.constraintlayout.core.widgets.ConstraintWidgetContainer;
import java.util.ArrayList;
public class ChainRun extends WidgetRun {
ArrayList<WidgetRun> mWidgets = new ArrayList<>();
private int mChainStyle;
public ChainRun(ConstraintWidget widget, int orientation) {
super(widget);
this.orientation = orientation;
build();
}
@Override
public String toString() {
StringBuilder log = new StringBuilder("ChainRun ");
log.append((orientation == HORIZONTAL ? "horizontal : " : "vertical : "));
for (WidgetRun run : mWidgets) {
log.append("<");
log.append(run);
log.append("> ");
}
return log.toString();
}
@Override
boolean supportsWrapComputation() {
final int count = mWidgets.size();
for (int i = 0; i < count; i++) {
WidgetRun run = mWidgets.get(i);
if (!run.supportsWrapComputation()) {
return false;
}
}
return true;
}
/**
* @TODO: add description
*/
@Override
public long getWrapDimension() {
final int count = mWidgets.size();
long wrapDimension = 0;
for (int i = 0; i < count; i++) {
WidgetRun run = mWidgets.get(i);
wrapDimension += run.start.mMargin;
wrapDimension += run.getWrapDimension();
wrapDimension += run.end.mMargin;
}
return wrapDimension;
}
private void build() {
ConstraintWidget current = mWidget;
ConstraintWidget previous = current.getPreviousChainMember(orientation);
while (previous != null) {
current = previous;
previous = current.getPreviousChainMember(orientation);
}
mWidget = current; // first element of the chain
mWidgets.add(current.getRun(orientation));
ConstraintWidget next = current.getNextChainMember(orientation);
while (next != null) {
current = next;
mWidgets.add(current.getRun(orientation));
next = current.getNextChainMember(orientation);
}
for (WidgetRun run : mWidgets) {
if (orientation == HORIZONTAL) {
run.mWidget.horizontalChainRun = this;
} else if (orientation == ConstraintWidget.VERTICAL) {
run.mWidget.verticalChainRun = this;
}
}
boolean isInRtl = (orientation == HORIZONTAL)
&& ((ConstraintWidgetContainer) mWidget.getParent()).isRtl();
if (isInRtl && mWidgets.size() > 1) {
mWidget = mWidgets.get(mWidgets.size() - 1).mWidget;
}
mChainStyle = orientation == HORIZONTAL
? mWidget.getHorizontalChainStyle() : mWidget.getVerticalChainStyle();
}
@Override
void clear() {
mRunGroup = null;
for (WidgetRun run : mWidgets) {
run.clear();
}
}
@Override
void reset() {
start.resolved = false;
end.resolved = false;
}
@Override
public void update(Dependency dependency) {
if (!(start.resolved && end.resolved)) {
return;
}
ConstraintWidget parent = mWidget.getParent();
boolean isInRtl = false;
if (parent instanceof ConstraintWidgetContainer) {
isInRtl = ((ConstraintWidgetContainer) parent).isRtl();
}
int distance = end.value - start.value;
int size = 0;
int numMatchConstraints = 0;
float weights = 0;
int numVisibleWidgets = 0;
final int count = mWidgets.size();
// let's find the first visible widget...
int firstVisibleWidget = -1;
for (int i = 0; i < count; i++) {
WidgetRun run = mWidgets.get(i);
if (run.mWidget.getVisibility() == GONE) {
continue;
}
firstVisibleWidget = i;
break;
}
// now the last visible widget...
int lastVisibleWidget = -1;
for (int i = count - 1; i >= 0; i--) {
WidgetRun run = mWidgets.get(i);
if (run.mWidget.getVisibility() == GONE) {
continue;
}
lastVisibleWidget = i;
break;
}
for (int j = 0; j < 2; j++) {
for (int i = 0; i < count; i++) {
WidgetRun run = mWidgets.get(i);
if (run.mWidget.getVisibility() == GONE) {
continue;
}
numVisibleWidgets++;
if (i > 0 && i >= firstVisibleWidget) {
size += run.start.mMargin;
}
int dimension = run.mDimension.value;
boolean treatAsFixed = run.mDimensionBehavior != MATCH_CONSTRAINT;
if (treatAsFixed) {
if (orientation == HORIZONTAL
&& !run.mWidget.mHorizontalRun.mDimension.resolved) {
return;
}
if (orientation == VERTICAL && !run.mWidget.mVerticalRun.mDimension.resolved) {
return;
}
} else if (run.matchConstraintsType == MATCH_CONSTRAINT_WRAP && j == 0) {
treatAsFixed = true;
dimension = run.mDimension.wrapValue;
numMatchConstraints++;
} else if (run.mDimension.resolved) {
treatAsFixed = true;
}
if (!treatAsFixed) { // only for the first pass
numMatchConstraints++;
float weight = run.mWidget.mWeight[orientation];
if (weight >= 0) {
weights += weight;
}
} else {
size += dimension;
}
if (i < count - 1 && i < lastVisibleWidget) {
size += -run.end.mMargin;
}
}
if (size < distance || numMatchConstraints == 0) {
break; // we are good to go!
}
// otherwise, let's do another pass with using match_constraints
numVisibleWidgets = 0;
numMatchConstraints = 0;
size = 0;
weights = 0;
}
int position = start.value;
if (isInRtl) {
position = end.value;
}
if (size > distance) {
if (isInRtl) {
position += (int) (0.5f + (size - distance) / 2f);
} else {
position -= (int) (0.5f + (size - distance) / 2f);
}
}
int matchConstraintsDimension = 0;
if (numMatchConstraints > 0) {
matchConstraintsDimension =
(int) (0.5f + (distance - size) / (float) numMatchConstraints);
int appliedLimits = 0;
for (int i = 0; i < count; i++) {
WidgetRun run = mWidgets.get(i);
if (run.mWidget.getVisibility() == GONE) {
continue;
}
if (run.mDimensionBehavior == MATCH_CONSTRAINT && !run.mDimension.resolved) {
int dimension = matchConstraintsDimension;
if (weights > 0) {
float weight = run.mWidget.mWeight[orientation];
dimension = (int) (0.5f + weight * (distance - size) / weights);
}
int max;
int min;
int value = dimension;
if (orientation == HORIZONTAL) {
max = run.mWidget.mMatchConstraintMaxWidth;
min = run.mWidget.mMatchConstraintMinWidth;
} else {
max = run.mWidget.mMatchConstraintMaxHeight;
min = run.mWidget.mMatchConstraintMinHeight;
}
if (run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
value = Math.min(value, run.mDimension.wrapValue);
}
value = Math.max(min, value);
if (max > 0) {
value = Math.min(max, value);
}
if (value != dimension) {
appliedLimits++;
dimension = value;
}
run.mDimension.resolve(dimension);
}
}
if (appliedLimits > 0) {
numMatchConstraints -= appliedLimits;
// we have to recompute the sizes
size = 0;
for (int i = 0; i < count; i++) {
WidgetRun run = mWidgets.get(i);
if (run.mWidget.getVisibility() == GONE) {
continue;
}
if (i > 0 && i >= firstVisibleWidget) {
size += run.start.mMargin;
}
size += run.mDimension.value;
if (i < count - 1 && i < lastVisibleWidget) {
size += -run.end.mMargin;
}
}
}
if (mChainStyle == ConstraintWidget.CHAIN_PACKED && appliedLimits == 0) {
mChainStyle = ConstraintWidget.CHAIN_SPREAD;
}
}
if (size > distance) {
mChainStyle = ConstraintWidget.CHAIN_PACKED;
}
if (numVisibleWidgets > 0 && numMatchConstraints == 0
&& firstVisibleWidget == lastVisibleWidget) {
// only one widget of fixed size to display...
mChainStyle = ConstraintWidget.CHAIN_PACKED;
}
if (mChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE) {
int gap = 0;
if (numVisibleWidgets > 1) {
gap = (distance - size) / (numVisibleWidgets - 1);
} else if (numVisibleWidgets == 1) {
gap = (distance - size) / 2;
}
if (numMatchConstraints > 0) {
gap = 0;
}
for (int i = 0; i < count; i++) {
int index = i;
if (isInRtl) {
index = count - (i + 1);
}
WidgetRun run = mWidgets.get(index);
if (run.mWidget.getVisibility() == GONE) {
run.start.resolve(position);
run.end.resolve(position);
continue;
}
if (i > 0) {
if (isInRtl) {
position -= gap;
} else {
position += gap;
}
}
if (i > 0 && i >= firstVisibleWidget) {
if (isInRtl) {
position -= run.start.mMargin;
} else {
position += run.start.mMargin;
}
}
if (isInRtl) {
run.end.resolve(position);
} else {
run.start.resolve(position);
}
int dimension = run.mDimension.value;
if (run.mDimensionBehavior == MATCH_CONSTRAINT
&& run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
dimension = run.mDimension.wrapValue;
}
if (isInRtl) {
position -= dimension;
} else {
position += dimension;
}
if (isInRtl) {
run.start.resolve(position);
} else {
run.end.resolve(position);
}
run.mResolved = true;
if (i < count - 1 && i < lastVisibleWidget) {
if (isInRtl) {
position -= -run.end.mMargin;
} else {
position += -run.end.mMargin;
}
}
}
} else if (mChainStyle == ConstraintWidget.CHAIN_SPREAD) {
int gap = (distance - size) / (numVisibleWidgets + 1);
if (numMatchConstraints > 0) {
gap = 0;
}
for (int i = 0; i < count; i++) {
int index = i;
if (isInRtl) {
index = count - (i + 1);
}
WidgetRun run = mWidgets.get(index);
if (run.mWidget.getVisibility() == GONE) {
run.start.resolve(position);
run.end.resolve(position);
continue;
}
if (isInRtl) {
position -= gap;
} else {
position += gap;
}
if (i > 0 && i >= firstVisibleWidget) {
if (isInRtl) {
position -= run.start.mMargin;
} else {
position += run.start.mMargin;
}
}
if (isInRtl) {
run.end.resolve(position);
} else {
run.start.resolve(position);
}
int dimension = run.mDimension.value;
if (run.mDimensionBehavior == MATCH_CONSTRAINT
&& run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
dimension = Math.min(dimension, run.mDimension.wrapValue);
}
if (isInRtl) {
position -= dimension;
} else {
position += dimension;
}
if (isInRtl) {
run.start.resolve(position);
} else {
run.end.resolve(position);
}
if (i < count - 1 && i < lastVisibleWidget) {
if (isInRtl) {
position -= -run.end.mMargin;
} else {
position += -run.end.mMargin;
}
}
}
} else if (mChainStyle == ConstraintWidget.CHAIN_PACKED) {
float bias = (orientation == HORIZONTAL) ? mWidget.getHorizontalBiasPercent()
: mWidget.getVerticalBiasPercent();
if (isInRtl) {
bias = 1 - bias;
}
int gap = (int) (0.5f + (distance - size) * bias);
if (gap < 0 || numMatchConstraints > 0) {
gap = 0;
}
if (isInRtl) {
position -= gap;
} else {
position += gap;
}
for (int i = 0; i < count; i++) {
int index = i;
if (isInRtl) {
index = count - (i + 1);
}
WidgetRun run = mWidgets.get(index);
if (run.mWidget.getVisibility() == GONE) {
run.start.resolve(position);
run.end.resolve(position);
continue;
}
if (i > 0 && i >= firstVisibleWidget) {
if (isInRtl) {
position -= run.start.mMargin;
} else {
position += run.start.mMargin;
}
}
if (isInRtl) {
run.end.resolve(position);
} else {
run.start.resolve(position);
}
int dimension = run.mDimension.value;
if (run.mDimensionBehavior == MATCH_CONSTRAINT
&& run.matchConstraintsType == MATCH_CONSTRAINT_WRAP) {
dimension = run.mDimension.wrapValue;
}
if (isInRtl) {
position -= dimension;
} else {
position += dimension;
}
if (isInRtl) {
run.start.resolve(position);
} else {
run.end.resolve(position);
}
if (i < count - 1 && i < lastVisibleWidget) {
if (isInRtl) {
position -= -run.end.mMargin;
} else {
position += -run.end.mMargin;
}
}
}
}
}
/**
* @TODO: add description
*/
@Override
public void applyToWidget() {
for (int i = 0; i < mWidgets.size(); i++) {
WidgetRun run = mWidgets.get(i);
run.applyToWidget();
}
}
private ConstraintWidget getFirstVisibleWidget() {
for (int i = 0; i < mWidgets.size(); i++) {
WidgetRun run = mWidgets.get(i);
if (run.mWidget.getVisibility() != GONE) {
return run.mWidget;
}
}
return null;
}
private ConstraintWidget getLastVisibleWidget() {
for (int i = mWidgets.size() - 1; i >= 0; i--) {
WidgetRun run = mWidgets.get(i);
if (run.mWidget.getVisibility() != GONE) {
return run.mWidget;
}
}
return null;
}
@Override
void apply() {
for (WidgetRun run : mWidgets) {
run.apply();
}
int count = mWidgets.size();
if (count < 1) {
return;
}
// get the first and last element of the chain
ConstraintWidget firstWidget = mWidgets.get(0).mWidget;
ConstraintWidget lastWidget = mWidgets.get(count - 1).mWidget;
if (orientation == HORIZONTAL) {
ConstraintAnchor startAnchor = firstWidget.mLeft;
ConstraintAnchor endAnchor = lastWidget.mRight;
DependencyNode startTarget = getTarget(startAnchor, HORIZONTAL);
int startMargin = startAnchor.getMargin();
ConstraintWidget firstVisibleWidget = getFirstVisibleWidget();
if (firstVisibleWidget != null) {
startMargin = firstVisibleWidget.mLeft.getMargin();
}
if (startTarget != null) {
addTarget(start, startTarget, startMargin);
}
DependencyNode endTarget = getTarget(endAnchor, HORIZONTAL);
int endMargin = endAnchor.getMargin();
ConstraintWidget lastVisibleWidget = getLastVisibleWidget();
if (lastVisibleWidget != null) {
endMargin = lastVisibleWidget.mRight.getMargin();
}
if (endTarget != null) {
addTarget(end, endTarget, -endMargin);
}
} else {
ConstraintAnchor startAnchor = firstWidget.mTop;
ConstraintAnchor endAnchor = lastWidget.mBottom;
DependencyNode startTarget = getTarget(startAnchor, VERTICAL);
int startMargin = startAnchor.getMargin();
ConstraintWidget firstVisibleWidget = getFirstVisibleWidget();
if (firstVisibleWidget != null) {
startMargin = firstVisibleWidget.mTop.getMargin();
}
if (startTarget != null) {
addTarget(start, startTarget, startMargin);
}
DependencyNode endTarget = getTarget(endAnchor, VERTICAL);
int endMargin = endAnchor.getMargin();
ConstraintWidget lastVisibleWidget = getLastVisibleWidget();
if (lastVisibleWidget != null) {
endMargin = lastVisibleWidget.mBottom.getMargin();
}
if (endTarget != null) {
addTarget(end, endTarget, -endMargin);
}
}
start.updateDelegate = this;
end.updateDelegate = this;
}
}