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