diff options
author | Oscar Adame Vázquez <oscarad@google.com> | 2018-06-07 21:14:07 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2018-06-07 21:14:07 +0000 |
commit | db7542da7aec8e442a8f2fc5c4ff40bd86086837 (patch) | |
tree | b5ec52ad4bef55bc67fa5d020f78dad3e9dc360d | |
parent | aaddafb4b7aedd31ae4b87d30fe43e09df77c5a6 (diff) | |
parent | ad720ed1bde5c5621701a3dc764699adf899bcde (diff) | |
download | sherpa-db7542da7aec8e442a8f2fc5c4ff40bd86086837.tar.gz |
Merge "Cleaning up Chains related code" into studio-master-dev
6 files changed, 272 insertions, 123 deletions
diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/Chain.java b/solver/src/main/java/android/support/constraint/solver/widgets/Chain.java index e51fbaa..6d6f12b 100644 --- a/solver/src/main/java/android/support/constraint/solver/widgets/Chain.java +++ b/solver/src/main/java/android/support/constraint/solver/widgets/Chain.java @@ -43,7 +43,7 @@ class Chain { // Don't skip things. Either the element is GONE or not. int offset = 0; int chainsSize = 0; - ConstraintWidget[] chainsArray = null; + ChainHead[] chainsArray = null; if (orientation == ConstraintWidget.HORIZONTAL) { offset = 0; chainsSize = constraintWidgetContainer.mHorizontalChainsSize; @@ -54,7 +54,7 @@ class Chain { chainsArray = constraintWidgetContainer.mVerticalChainsArray; } for (int i = 0; i < chainsSize; i++) { - ConstraintWidget first = chainsArray[i]; + ChainHead first = chainsArray[i]; if (constraintWidgetContainer.optimizeFor(Optimizer.OPTIMIZATION_CHAIN)) { if (!Optimizer.applyChainOptimized(constraintWidgetContainer, system, orientation, offset, first)) { applyChainConstraints(constraintWidgetContainer, system, orientation, offset, first); @@ -73,14 +73,19 @@ class Chain { * @param system the linear system we add the equations to * @param orientation HORIZONTAL or VERTICAL * @param offset 0 or 2 to accomodate for HORIZONTAL / VERTICAL - * @param first first widget of the chain + * @param chainHead a chain represented by its main elements */ static void applyChainConstraints(ConstraintWidgetContainer container, LinearSystem system, - int orientation, int offset, ConstraintWidget first) { + int orientation, int offset, ChainHead chainHead) { + ConstraintWidget first = chainHead.mFirst; + ConstraintWidget last = chainHead.mLast; + ConstraintWidget firstVisibleWidget = chainHead.mFirstVisibleWidget; + ConstraintWidget lastVisibleWidget = chainHead.mLastVisibleWidget; + ConstraintWidget head = chainHead.mHead; + ConstraintWidget widget = first; ConstraintWidget next = null; - ConstraintWidget firstVisibleWidget = null; - ConstraintWidget lastVisibleWidget = null; + boolean done = false; int numMatchConstraints = 0; float totalWeights = 0; @@ -92,33 +97,6 @@ class Chain { boolean isChainSpreadInside = false; boolean isChainPacked = false; - ConstraintWidget head = first; - if (orientation == ConstraintWidget.HORIZONTAL && container.isRtl()) { - // find the last widget - while (!done) { - // go to the next widget - ConstraintAnchor nextAnchor = widget.mListAnchors[offset + 1].mTarget; - if (nextAnchor != null) { - next = nextAnchor.mOwner; - if (next.mListAnchors[offset].mTarget == null - || next.mListAnchors[offset].mTarget.mOwner != widget) { - next = null; - } - } else { - next = null; - } - if (next != null) { - widget = next; - } else { - done = true; - } - } - head = widget; - widget = first; - next = null; - done = false; - } - if (orientation == ConstraintWidget.HORIZONTAL) { isChainSpread = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_SPREAD; isChainSpreadInside = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE; @@ -131,24 +109,9 @@ class Chain { // The first traversal will: // - set up some basic ordering constraints - // - build a linked list of visible widgets // - build a linked list of matched constraints widgets while (!done) { - // apply ordering on the current widget - - // First, let's maintain a linked list of visible widgets for the chain - widget.mListNextVisibleWidget[orientation] = null; - if (widget.getVisibility() != ConstraintWidget.GONE) { - if (lastVisibleWidget != null) { - lastVisibleWidget.mListNextVisibleWidget[orientation] = widget; - } - if (firstVisibleWidget == null) { - firstVisibleWidget = widget; - } - lastVisibleWidget = widget; - } - ConstraintAnchor begin = widget.mListAnchors[offset]; int strength = SolverVariable.STRENGTH_HIGHEST; if (isWrapContent || isChainPacked) { @@ -221,7 +184,6 @@ class Chain { done = true; } } - ConstraintWidget last = widget; // Make sure we have constraints for the last anchors / targets if (lastVisibleWidget != null && last.mListAnchors[offset + 1].mTarget != null) { diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ChainHead.java b/solver/src/main/java/android/support/constraint/solver/widgets/ChainHead.java new file mode 100644 index 0000000..ee56b7a --- /dev/null +++ b/solver/src/main/java/android/support/constraint/solver/widgets/ChainHead.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2018 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 android.support.constraint.solver.widgets; + +/** + * Class to represent a chain by its main elements. + */ +public class ChainHead { + + protected ConstraintWidget mFirst; + protected ConstraintWidget mFirstVisibleWidget; + protected ConstraintWidget mLast; + protected ConstraintWidget mLastVisibleWidget; + protected ConstraintWidget mHead; + private int mOrientation; + private boolean mIsRtl = false; + // TODO: Apply linked list of matched constraint widgets. + + /** + * Initialize variables, then determine visible widgets and head. + * + * @param first first widget in a chain + * @param orientation orientation of the chain (either Horizontal or Vertical) + * @param isRtl Right-to-left layout flag to determine the actual head of the chain + */ + public ChainHead(ConstraintWidget first, int orientation, boolean isRtl){ + mFirst = first; + mOrientation = orientation; + mIsRtl = isRtl; + defineChainProperties(); + } + + private void defineChainProperties(){ + int offset = mOrientation * 2; + + // TraverseChain + ConstraintWidget widget = mFirst; + ConstraintWidget next = mFirst; + boolean done = false; + widget.mListNextVisibleWidget[mOrientation] = null; + + while (!done) { + // go to the next widget + ConstraintAnchor nextAnchor = widget.mListAnchors[offset + 1].mTarget; + if (nextAnchor != null) { + next = nextAnchor.mOwner; + if (next.mListAnchors[offset].mTarget == null + || next.mListAnchors[offset].mTarget.mOwner != widget) { + next = null; + } + } else { + next = null; + } + if(widget.getVisibility() != ConstraintWidget.GONE) { + if (mFirstVisibleWidget == null) { + mFirstVisibleWidget = widget; + } + if(mLastVisibleWidget != null){ + mLastVisibleWidget.mListNextVisibleWidget[mOrientation] = widget; + } + mLastVisibleWidget = widget; + } + if (next != null) { + widget = next; + } else { + done = true; + } + } + mLast = widget; + + if(mOrientation == ConstraintWidget.HORIZONTAL && mIsRtl) { + mHead = mLast; + }else{ + mHead = mFirst; + } + } + + public ConstraintWidget getFirst() { + return mFirst; + } + + public ConstraintWidget getFirstVisibleWidget() { + return mFirstVisibleWidget; + } + + public ConstraintWidget getLast() { + return mLast; + } + + public ConstraintWidget getLastVisibleWidget() { + return mLastVisibleWidget; + } + + public ConstraintWidget getHead() { + return mHead; + } +} diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidget.java b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidget.java index a5e143d..ed8bd32 100644 --- a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidget.java +++ b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidget.java @@ -2194,15 +2194,21 @@ public class ConstraintWidget { verticalParentWrapContent = mParent != null ? mParent.mListDimensionBehaviors[DIMENSION_VERTICAL] == WRAP_CONTENT : false; // Add this widget to an horizontal chain if dual connections are found + if((mLeft.mTarget != null && mLeft.mTarget.mTarget != mLeft) && + mRight.mTarget != null && mRight.mTarget.mTarget == mRight){ + ((ConstraintWidgetContainer) mParent).addChain(this, HORIZONTAL); + } if ((mLeft.mTarget != null && mLeft.mTarget.mTarget == mLeft) || (mRight.mTarget != null && mRight.mTarget.mTarget == mRight)) { - ((ConstraintWidgetContainer) mParent).addChain(this, HORIZONTAL); inHorizontalChain = true; } // Add this widget to an vertical chain if dual connections are found + if((mTop.mTarget != null && mTop.mTarget.mTarget != mTop) && + mBottom.mTarget != null && mBottom.mTarget.mTarget == mBottom){ + ((ConstraintWidgetContainer) mParent).addChain(this, VERTICAL); + } if ((mTop.mTarget != null && mTop.mTarget.mTarget == mTop) || (mBottom.mTarget != null && mBottom.mTarget.mTarget == mBottom)) { - ((ConstraintWidgetContainer) mParent).addChain(this, VERTICAL); inVerticalChain = true; } diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetContainer.java b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetContainer.java index 15a2ce2..322aef9 100644 --- a/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetContainer.java +++ b/solver/src/main/java/android/support/constraint/solver/widgets/ConstraintWidgetContainer.java @@ -55,8 +55,8 @@ public class ConstraintWidgetContainer extends WidgetContainer { int mHorizontalChainsSize = 0; int mVerticalChainsSize = 0; - ConstraintWidget[] mVerticalChainsArray = new ConstraintWidget[4]; - ConstraintWidget[] mHorizontalChainsArray = new ConstraintWidget[4]; + ChainHead[] mVerticalChainsArray = new ChainHead[4]; + ChainHead[] mHorizontalChainsArray = new ChainHead[4]; private int mOptimizationLevel = Optimizer.OPTIMIZATION_STANDARD; @@ -664,22 +664,8 @@ public class ConstraintWidgetContainer extends WidgetContainer { void addChain(ConstraintWidget constraintWidget, int type) { ConstraintWidget widget = constraintWidget; if (type == HORIZONTAL) { - // find the left most widget that doesn't have a dual connection (i.e., start of chain) - while (widget.mLeft.mTarget != null - && widget.mLeft.mTarget.mOwner.mRight.mTarget != null - && widget.mLeft.mTarget.mOwner.mRight.mTarget == widget.mLeft - && widget.mLeft.mTarget.mOwner != widget) { - widget = widget.mLeft.mTarget.mOwner; - } addHorizontalChain(widget); } else if (type == VERTICAL) { - // find the top most widget that doesn't have a dual connection (i.e., start of chain) - while (widget.mTop.mTarget != null - && widget.mTop.mTarget.mOwner.mBottom.mTarget != null - && widget.mTop.mTarget.mOwner.mBottom.mTarget == widget.mTop - && widget.mTop.mTarget.mOwner != widget) { - widget = widget.mTop.mTarget.mOwner; - } addVerticalChain(widget); } } @@ -691,15 +677,10 @@ public class ConstraintWidgetContainer extends WidgetContainer { * @param widget widget starting the chain */ private void addHorizontalChain(ConstraintWidget widget) { - for (int i = 0; i < mHorizontalChainsSize; i++) { - if (mHorizontalChainsArray[i] == widget) { - return; - } - } if (mHorizontalChainsSize + 1 >= mHorizontalChainsArray.length) { mHorizontalChainsArray = Arrays.copyOf(mHorizontalChainsArray, mHorizontalChainsArray.length * 2); } - mHorizontalChainsArray[mHorizontalChainsSize] = widget; + mHorizontalChainsArray[mHorizontalChainsSize] = new ChainHead(widget, HORIZONTAL, isRtl()); mHorizontalChainsSize++; } @@ -710,15 +691,10 @@ public class ConstraintWidgetContainer extends WidgetContainer { * @param widget widget starting the chain */ private void addVerticalChain(ConstraintWidget widget) { - for (int i = 0; i < mVerticalChainsSize; i++) { - if (mVerticalChainsArray[i] == widget) { - return; - } - } if (mVerticalChainsSize + 1 >= mVerticalChainsArray.length) { mVerticalChainsArray = Arrays.copyOf(mVerticalChainsArray, mVerticalChainsArray.length * 2); } - mVerticalChainsArray[mVerticalChainsSize] = widget; + mVerticalChainsArray[mVerticalChainsSize] = new ChainHead(widget, VERTICAL, isRtl()); mVerticalChainsSize++; } diff --git a/solver/src/main/java/android/support/constraint/solver/widgets/Optimizer.java b/solver/src/main/java/android/support/constraint/solver/widgets/Optimizer.java index d60c069..4fb16d3 100644 --- a/solver/src/main/java/android/support/constraint/solver/widgets/Optimizer.java +++ b/solver/src/main/java/android/support/constraint/solver/widgets/Optimizer.java @@ -372,17 +372,21 @@ public class Optimizer { * @param system * @param orientation * @param offset - * @param first + * @param chainHead * * @return true if the chain has been optimized, false otherwise */ static boolean applyChainOptimized(ConstraintWidgetContainer container, LinearSystem system, - int orientation, int offset, ConstraintWidget first) { + int orientation, int offset, ChainHead chainHead) { + + ConstraintWidget first = chainHead.mFirst; + ConstraintWidget last = chainHead.mLast; + ConstraintWidget firstVisibleWidget = chainHead.mFirstVisibleWidget; + ConstraintWidget lastVisibleWidget = chainHead.mLastVisibleWidget; + ConstraintWidget head = chainHead.mHead; ConstraintWidget widget = first; ConstraintWidget next = null; - ConstraintWidget firstVisibleWidget = null; - ConstraintWidget lastVisibleWidget = null; boolean done = false; @@ -396,33 +400,6 @@ public class Optimizer { boolean isChainSpreadInside = false; boolean isChainPacked = false; - ConstraintWidget head = first; - if (orientation == ConstraintWidget.HORIZONTAL && container.isRtl()) { - // find the last widget - while (!done) { - // go to the next widget - ConstraintAnchor nextAnchor = widget.mListAnchors[offset + 1].mTarget; - if (nextAnchor != null) { - next = nextAnchor.mOwner; - if (next.mListAnchors[offset].mTarget == null - || next.mListAnchors[offset].mTarget.mOwner != widget) { - next = null; - } - } else { - next = null; - } - if (next != null) { - widget = next; - } else { - done = true; - } - } - head = widget; - widget = first; - next = null; - done = false; - } - if (orientation == ConstraintWidget.HORIZONTAL) { isChainSpread = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_SPREAD; isChainSpreadInside = head.mHorizontalChainStyle == ConstraintWidget.CHAIN_SPREAD_INSIDE; @@ -435,7 +412,6 @@ public class Optimizer { // The first traversal will: // - set up some basic ordering constraints - // - build a linked list of visible widgets // - build a linked list of matched constraints widgets float totalSize = 0; @@ -443,18 +419,8 @@ public class Optimizer { int numVisibleWidgets = 0; while (!done) { - // apply ordering on the current widget - - // First, let's maintain a linked list of visible widgets for the chain - widget.mListNextVisibleWidget[orientation] = null; + // Measure visible widgets and add margins. if (widget.getVisibility() != ConstraintWidget.GONE) { - if (lastVisibleWidget != null) { - lastVisibleWidget.mListNextVisibleWidget[orientation] = widget; - } - if (firstVisibleWidget == null) { - firstVisibleWidget = widget; - } - lastVisibleWidget = widget; numVisibleWidgets ++; if (orientation == HORIZONTAL) { totalSize += widget.getWidth(); @@ -514,7 +480,6 @@ public class Optimizer { done = true; } } - ConstraintWidget last = widget; ResolutionAnchor firstNode = first.mListAnchors[offset].getResolutionNode(); ResolutionAnchor lastNode = last.mListAnchors[offset + 1].getResolutionNode(); diff --git a/solver/src/test/java/android/support/constraint/solver/ChainHeadTest.java b/solver/src/test/java/android/support/constraint/solver/ChainHeadTest.java new file mode 100644 index 0000000..99bf0a2 --- /dev/null +++ b/solver/src/test/java/android/support/constraint/solver/ChainHeadTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2018 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 android.support.constraint.solver; + +import android.support.constraint.solver.widgets.ChainHead; +import android.support.constraint.solver.widgets.ConstraintAnchor; +import android.support.constraint.solver.widgets.ConstraintWidget; +import android.support.constraint.solver.widgets.ConstraintWidgetContainer; +import org.testng.annotations.Test; + +import static org.testng.Assert.assertEquals; + +public class ChainHeadTest { + + @Test + public void basicHorizontalChainHeadTest(){ + ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 600, 600); + ConstraintWidget A = new ConstraintWidget(100, 20); + ConstraintWidget B = new ConstraintWidget(100, 20); + ConstraintWidget C = new ConstraintWidget(100, 20); + + root.setDebugName("root"); + A.setDebugName("A"); + B.setDebugName("B"); + C.setDebugName("C"); + + root.add(A); + root.add(B); + root.add(C); + + A.connect(ConstraintAnchor.Type.LEFT, root, ConstraintAnchor.Type.LEFT); + A.connect(ConstraintAnchor.Type.RIGHT, B, ConstraintAnchor.Type.LEFT); + B.connect(ConstraintAnchor.Type.LEFT, A, ConstraintAnchor.Type.RIGHT); + B.connect(ConstraintAnchor.Type.RIGHT, C, ConstraintAnchor.Type.LEFT); + C.connect(ConstraintAnchor.Type.LEFT, B, ConstraintAnchor.Type.RIGHT); + C.connect(ConstraintAnchor.Type.RIGHT, root, ConstraintAnchor.Type.RIGHT); + A.connect(ConstraintAnchor.Type.TOP, root, ConstraintAnchor.Type.TOP); + B.connect(ConstraintAnchor.Type.TOP, root, ConstraintAnchor.Type.TOP); + C.connect(ConstraintAnchor.Type.TOP, root, ConstraintAnchor.Type.TOP); + + ChainHead chainHead = new ChainHead(A, ConstraintWidget.HORIZONTAL, false); + + assertEquals(chainHead.getHead(), A); + assertEquals(chainHead.getFirst(), A); + assertEquals(chainHead.getFirstVisibleWidget(), A); + assertEquals(chainHead.getLast(), C); + assertEquals(chainHead.getLastVisibleWidget(), C); + + A.setVisibility(ConstraintWidget.GONE); + + chainHead = new ChainHead(A, ConstraintWidget.HORIZONTAL, false); + + assertEquals(chainHead.getHead(), A); + assertEquals(chainHead.getFirst(), A); + assertEquals(chainHead.getFirstVisibleWidget(), B); + + + chainHead = new ChainHead(A, ConstraintWidget.HORIZONTAL, true); + + assertEquals(chainHead.getHead(), C); + assertEquals(chainHead.getFirst(), A); + assertEquals(chainHead.getFirstVisibleWidget(), B); + } + + @Test + public void basicVerticalChainHeadTest(){ + ConstraintWidgetContainer root = new ConstraintWidgetContainer(0, 0, 600, 600); + ConstraintWidget A = new ConstraintWidget(100, 20); + ConstraintWidget B = new ConstraintWidget(100, 20); + ConstraintWidget C = new ConstraintWidget(100, 20); + + root.setDebugName("root"); + A.setDebugName("A"); + B.setDebugName("B"); + C.setDebugName("C"); + + root.add(A); + root.add(B); + root.add(C); + + A.connect(ConstraintAnchor.Type.TOP, root, ConstraintAnchor.Type.TOP); + A.connect(ConstraintAnchor.Type.BOTTOM, B, ConstraintAnchor.Type.TOP); + B.connect(ConstraintAnchor.Type.TOP, A, ConstraintAnchor.Type.BOTTOM); + B.connect(ConstraintAnchor.Type.BOTTOM, C, ConstraintAnchor.Type.TOP); + C.connect(ConstraintAnchor.Type.TOP, B, ConstraintAnchor.Type.BOTTOM); + C.connect(ConstraintAnchor.Type.BOTTOM, root, ConstraintAnchor.Type.BOTTOM); + A.connect(ConstraintAnchor.Type.LEFT, root, ConstraintAnchor.Type.LEFT); + B.connect(ConstraintAnchor.Type.LEFT, root, ConstraintAnchor.Type.LEFT); + C.connect(ConstraintAnchor.Type.LEFT, root, ConstraintAnchor.Type.LEFT); + + ChainHead chainHead = new ChainHead(A, ConstraintWidget.VERTICAL, false); + + assertEquals(chainHead.getHead(), A); + assertEquals(chainHead.getFirst(), A); + assertEquals(chainHead.getFirstVisibleWidget(), A); + assertEquals(chainHead.getLast(), C); + assertEquals(chainHead.getLastVisibleWidget(), C); + + A.setVisibility(ConstraintWidget.GONE); + + chainHead = new ChainHead(A, ConstraintWidget.VERTICAL, false); + + assertEquals(chainHead.getHead(), A); + assertEquals(chainHead.getFirst(), A); + assertEquals(chainHead.getFirstVisibleWidget(), B); + + + chainHead = new ChainHead(A, ConstraintWidget.VERTICAL, true); + + assertEquals(chainHead.getHead(), A); + assertEquals(chainHead.getFirst(), A); + assertEquals(chainHead.getFirstVisibleWidget(), B); + } + +} |