1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.constraintlayout.core.widgets; 18 19 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_PERCENT; 20 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_RATIO; 21 import static androidx.constraintlayout.core.widgets.ConstraintWidget.MATCH_CONSTRAINT_SPREAD; 22 23 import androidx.constraintlayout.core.widgets.ConstraintWidget.DimensionBehaviour; 24 25 import java.util.ArrayList; 26 27 /** 28 * Class to represent a chain by its main elements. 29 */ 30 public class ChainHead { 31 32 protected ConstraintWidget mFirst; 33 protected ConstraintWidget mFirstVisibleWidget; 34 protected ConstraintWidget mLast; 35 protected ConstraintWidget mLastVisibleWidget; 36 protected ConstraintWidget mHead; 37 protected ConstraintWidget mFirstMatchConstraintWidget; 38 protected ConstraintWidget mLastMatchConstraintWidget; 39 protected ArrayList<ConstraintWidget> mWeightedMatchConstraintsWidgets; 40 protected int mWidgetsCount; 41 protected int mWidgetsMatchCount; 42 protected float mTotalWeight = 0f; 43 int mVisibleWidgets; 44 int mTotalSize; 45 int mTotalMargins; 46 boolean mOptimizable; 47 private int mOrientation; 48 private boolean mIsRtl = false; 49 protected boolean mHasUndefinedWeights; 50 protected boolean mHasDefinedWeights; 51 protected boolean mHasComplexMatchWeights; 52 protected boolean mHasRatio; 53 private boolean mDefined; 54 55 /** 56 * Initialize variables, then determine visible widgets, the head of chain and 57 * matched constraint widgets. 58 * 59 * @param first first widget in a chain 60 * @param orientation orientation of the chain (either Horizontal or Vertical) 61 * @param isRtl Right-to-left layout flag to determine the actual head of the chain 62 */ ChainHead(ConstraintWidget first, int orientation, boolean isRtl)63 public ChainHead(ConstraintWidget first, int orientation, boolean isRtl) { 64 mFirst = first; 65 mOrientation = orientation; 66 mIsRtl = isRtl; 67 } 68 69 /** 70 * Returns true if the widget should be part of the match equality rules in the chain 71 * 72 * @param widget the widget to test 73 * @param orientation current orientation, HORIZONTAL or VERTICAL 74 */ isMatchConstraintEqualityCandidate(ConstraintWidget widget, int orientation)75 private static boolean isMatchConstraintEqualityCandidate(ConstraintWidget widget, 76 int orientation) { 77 return widget.getVisibility() != ConstraintWidget.GONE 78 && widget.mListDimensionBehaviors[orientation] 79 == ConstraintWidget.DimensionBehaviour.MATCH_CONSTRAINT 80 && (widget.mResolvedMatchConstraintDefault[orientation] == MATCH_CONSTRAINT_SPREAD 81 || widget.mResolvedMatchConstraintDefault[orientation] == MATCH_CONSTRAINT_RATIO); 82 } 83 defineChainProperties()84 private void defineChainProperties() { 85 int offset = mOrientation * 2; 86 ConstraintWidget lastVisited = mFirst; 87 mOptimizable = true; 88 89 // TraverseChain 90 ConstraintWidget widget = mFirst; 91 ConstraintWidget next = mFirst; 92 boolean done = false; 93 while (!done) { 94 mWidgetsCount++; 95 widget.mNextChainWidget[mOrientation] = null; 96 widget.mListNextMatchConstraintsWidget[mOrientation] = null; 97 if (widget.getVisibility() != ConstraintWidget.GONE) { 98 mVisibleWidgets++; 99 if (widget.getDimensionBehaviour(mOrientation) 100 != DimensionBehaviour.MATCH_CONSTRAINT) { 101 mTotalSize += widget.getLength(mOrientation); 102 } 103 mTotalSize += widget.mListAnchors[offset].getMargin(); 104 mTotalSize += widget.mListAnchors[offset + 1].getMargin(); 105 mTotalMargins += widget.mListAnchors[offset].getMargin(); 106 mTotalMargins += widget.mListAnchors[offset + 1].getMargin(); 107 // Visible widgets linked list. 108 if (mFirstVisibleWidget == null) { 109 mFirstVisibleWidget = widget; 110 } 111 mLastVisibleWidget = widget; 112 113 // Match constraint linked list. 114 if (widget.mListDimensionBehaviors[mOrientation] 115 == DimensionBehaviour.MATCH_CONSTRAINT) { 116 if (widget.mResolvedMatchConstraintDefault[mOrientation] 117 == MATCH_CONSTRAINT_SPREAD 118 || widget.mResolvedMatchConstraintDefault[mOrientation] 119 == MATCH_CONSTRAINT_RATIO 120 || widget.mResolvedMatchConstraintDefault[mOrientation] 121 == MATCH_CONSTRAINT_PERCENT) { 122 mWidgetsMatchCount++; 123 // Note: Might cause an issue if we support MATCH_CONSTRAINT_RATIO_RESOLVED 124 // in chain optimization. (we currently don't) 125 float weight = widget.mWeight[mOrientation]; 126 if (weight > 0) { 127 mTotalWeight += widget.mWeight[mOrientation]; 128 } 129 130 if (isMatchConstraintEqualityCandidate(widget, mOrientation)) { 131 if (weight < 0) { 132 mHasUndefinedWeights = true; 133 } else { 134 mHasDefinedWeights = true; 135 } 136 if (mWeightedMatchConstraintsWidgets == null) { 137 mWeightedMatchConstraintsWidgets = new ArrayList<>(); 138 } 139 mWeightedMatchConstraintsWidgets.add(widget); 140 } 141 142 if (mFirstMatchConstraintWidget == null) { 143 mFirstMatchConstraintWidget = widget; 144 } 145 if (mLastMatchConstraintWidget != null) { 146 mLastMatchConstraintWidget 147 .mListNextMatchConstraintsWidget[mOrientation] = widget; 148 } 149 mLastMatchConstraintWidget = widget; 150 } 151 if (mOrientation == ConstraintWidget.HORIZONTAL) { 152 if (widget.mMatchConstraintDefaultWidth 153 != ConstraintWidget.MATCH_CONSTRAINT_SPREAD) { 154 mOptimizable = false; 155 } else if (widget.mMatchConstraintMinWidth != 0 156 || widget.mMatchConstraintMaxWidth != 0) { 157 mOptimizable = false; 158 } 159 } else { 160 if (widget.mMatchConstraintDefaultHeight 161 != ConstraintWidget.MATCH_CONSTRAINT_SPREAD) { 162 mOptimizable = false; 163 } else if (widget.mMatchConstraintMinHeight != 0 164 || widget.mMatchConstraintMaxHeight != 0) { 165 mOptimizable = false; 166 } 167 } 168 if (widget.mDimensionRatio != 0.0f) { 169 //TODO: Improve (Could use ratio optimization). 170 mOptimizable = false; 171 mHasRatio = true; 172 } 173 } 174 } 175 if (lastVisited != widget) { 176 lastVisited.mNextChainWidget[mOrientation] = widget; 177 } 178 lastVisited = widget; 179 180 // go to the next widget 181 ConstraintAnchor nextAnchor = widget.mListAnchors[offset + 1].mTarget; 182 if (nextAnchor != null) { 183 next = nextAnchor.mOwner; 184 if (next.mListAnchors[offset].mTarget == null 185 || next.mListAnchors[offset].mTarget.mOwner != widget) { 186 next = null; 187 } 188 } else { 189 next = null; 190 } 191 if (next != null) { 192 widget = next; 193 } else { 194 done = true; 195 } 196 } 197 if (mFirstVisibleWidget != null) { 198 mTotalSize -= mFirstVisibleWidget.mListAnchors[offset].getMargin(); 199 } 200 if (mLastVisibleWidget != null) { 201 mTotalSize -= mLastVisibleWidget.mListAnchors[offset + 1].getMargin(); 202 } 203 mLast = widget; 204 205 if (mOrientation == ConstraintWidget.HORIZONTAL && mIsRtl) { 206 mHead = mLast; 207 } else { 208 mHead = mFirst; 209 } 210 211 mHasComplexMatchWeights = mHasDefinedWeights && mHasUndefinedWeights; 212 } 213 getFirst()214 public ConstraintWidget getFirst() { 215 return mFirst; 216 } 217 getFirstVisibleWidget()218 public ConstraintWidget getFirstVisibleWidget() { 219 return mFirstVisibleWidget; 220 } 221 getLast()222 public ConstraintWidget getLast() { 223 return mLast; 224 } 225 getLastVisibleWidget()226 public ConstraintWidget getLastVisibleWidget() { 227 return mLastVisibleWidget; 228 } 229 getHead()230 public ConstraintWidget getHead() { 231 return mHead; 232 } 233 getFirstMatchConstraintWidget()234 public ConstraintWidget getFirstMatchConstraintWidget() { 235 return mFirstMatchConstraintWidget; 236 } 237 getLastMatchConstraintWidget()238 public ConstraintWidget getLastMatchConstraintWidget() { 239 return mLastMatchConstraintWidget; 240 } 241 getTotalWeight()242 public float getTotalWeight() { 243 return mTotalWeight; 244 } 245 246 // @TODO: add description define()247 public void define() { 248 if (!mDefined) { 249 defineChainProperties(); 250 } 251 mDefined = true; 252 } 253 } 254