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.widget; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.content.res.XmlResourceParser; 22 import android.util.AttributeSet; 23 import android.util.Log; 24 import android.util.SparseArray; 25 import android.util.Xml; 26 27 import org.xmlpull.v1.XmlPullParser; 28 import org.xmlpull.v1.XmlPullParserException; 29 30 import java.io.IOException; 31 import java.util.ArrayList; 32 33 /** 34 * 35 */ 36 37 public class StateSet { 38 public static final String TAG = "ConstraintLayoutStates"; 39 private static final boolean DEBUG = false; 40 int mDefaultState = -1; 41 42 int mCurrentStateId = -1; // default 43 int mCurrentConstraintNumber = -1; // default 44 private SparseArray<State> mStateList = new SparseArray<>(); 45 @SuppressWarnings("unused") 46 private ConstraintsChangedListener mConstraintsChangedListener = null; 47 48 /** 49 * Parse a StateSet 50 * @param context 51 * @param parser 52 */ StateSet(Context context, XmlPullParser parser)53 public StateSet(Context context, XmlPullParser parser) { 54 load(context, parser); 55 } 56 57 /** 58 * Load a constraint set from a constraintSet.xml file 59 * 60 * @param context the context for the inflation 61 * @param parser mId of xml file in res/xml/ 62 */ load(Context context, XmlPullParser parser)63 private void load(Context context, XmlPullParser parser) { 64 if (DEBUG) { 65 Log.v(TAG, "#########load stateSet###### "); 66 } 67 // Parse the stateSet attributes 68 AttributeSet attrs = Xml.asAttributeSet(parser); 69 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StateSet); 70 final int count = a.getIndexCount(); 71 72 for (int i = 0; i < count; i++) { 73 int attr = a.getIndex(i); 74 if (attr == R.styleable.StateSet_defaultState) { 75 mDefaultState = a.getResourceId(attr, mDefaultState); 76 } 77 } 78 a.recycle(); 79 80 try { 81 Variant match; 82 State state = null; 83 for (int eventType = parser.getEventType(); 84 eventType != XmlResourceParser.END_DOCUMENT; 85 eventType = parser.next()) { 86 87 switch (eventType) { 88 case XmlResourceParser.START_DOCUMENT: 89 case XmlResourceParser.TEXT: 90 break; 91 case XmlResourceParser.START_TAG: 92 String tagName = parser.getName(); 93 switch(tagName) { 94 case "LayoutDescription": 95 break; 96 case "StateSet": 97 break; 98 case "State": 99 state = new State(context, parser); 100 mStateList.put(state.mId, state); 101 break; 102 case "Variant": 103 match = new Variant(context, parser); 104 if (state != null) { 105 state.add(match); 106 } 107 break; 108 109 default: 110 if (DEBUG) { 111 Log.v(TAG, "unknown tag " + tagName); 112 } 113 } 114 115 break; 116 case XmlResourceParser.END_TAG: 117 if ("StateSet".equals(parser.getName())) { 118 if (DEBUG) { 119 Log.v(TAG, "############ finished parsing state set"); 120 } 121 return; 122 } 123 break; 124 } 125 } 126 127 } catch (XmlPullParserException e) { 128 Log.e(TAG, "Error parsing XML resource", e); 129 } catch (IOException e) { 130 Log.e(TAG, "Error parsing XML resource", e); 131 } 132 } 133 134 /** 135 * will the layout need to change 136 * @param id 137 * @param width 138 * @param height 139 * @return 140 */ needsToChange(int id, float width, float height)141 public boolean needsToChange(int id, float width, float height) { 142 if (mCurrentStateId != id) { 143 return true; 144 } 145 146 State state = (id == -1) ? mStateList.valueAt(0) : mStateList.get(mCurrentStateId); 147 148 if (mCurrentConstraintNumber != -1) { 149 if (state.mVariants.get(mCurrentConstraintNumber).match(width, height)) { 150 return false; 151 } 152 } 153 154 if (mCurrentConstraintNumber == state.findMatch(width, height)) { 155 return false; 156 } 157 return true; 158 } 159 160 /** 161 * listen for changes in constraintSet 162 * @param constraintsChangedListener 163 */ setOnConstraintsChanged(ConstraintsChangedListener constraintsChangedListener)164 public void setOnConstraintsChanged(ConstraintsChangedListener constraintsChangedListener) { 165 this.mConstraintsChangedListener = constraintsChangedListener; 166 } 167 168 /** 169 * Get the constraint id for a state 170 * @param id 171 * @param width 172 * @param height 173 * @return 174 */ stateGetConstraintID(int id, int width, int height)175 public int stateGetConstraintID(int id, int width, int height) { 176 return updateConstraints(-1, id, width, height); 177 } 178 179 /** 180 * converts a state to a constraintSet 181 * 182 * @param currentConstrainSettId 183 * @param stateId 184 * @param width 185 * @param height 186 * @return 187 */ convertToConstraintSet(int currentConstrainSettId, int stateId, float width, float height)188 public int convertToConstraintSet(int currentConstrainSettId, 189 int stateId, 190 float width, 191 float height) { 192 State state = mStateList.get(stateId); 193 if (state == null) { 194 return stateId; 195 } 196 if (width == -1 || height == -1) { // for the case without width/height matching 197 if (state.mConstraintID == currentConstrainSettId) { 198 return currentConstrainSettId; 199 } 200 for (Variant mVariant : state.mVariants) { 201 if (currentConstrainSettId == mVariant.mConstraintID) { 202 return currentConstrainSettId; 203 } 204 } 205 return state.mConstraintID; 206 } else { 207 Variant match = null; 208 for (Variant mVariant : state.mVariants) { 209 if (mVariant.match(width, height)) { 210 if (currentConstrainSettId == mVariant.mConstraintID) { 211 return currentConstrainSettId; 212 } 213 match = mVariant; 214 } 215 } 216 if (match != null) { 217 return match.mConstraintID; 218 } 219 220 return state.mConstraintID; 221 } 222 } 223 224 /** 225 * Update the Constraints 226 * @param currentId 227 * @param id 228 * @param width 229 * @param height 230 * @return 231 */ updateConstraints(int currentId, int id, float width, float height)232 public int updateConstraints(int currentId, int id, float width, float height) { 233 if (currentId == id) { 234 State state; 235 if (id == -1) { 236 state = mStateList.valueAt(0); // id not being used take the first 237 } else { 238 state = mStateList.get(mCurrentStateId); 239 240 } 241 if (state == null) { 242 return -1; 243 } 244 if (mCurrentConstraintNumber != -1) { 245 if (state.mVariants.get(currentId).match(width, height)) { 246 return currentId; 247 } 248 } 249 int match = state.findMatch(width, height); 250 if (currentId == match) { 251 return currentId; 252 } 253 254 return (match == -1) ? state.mConstraintID : state.mVariants.get(match).mConstraintID; 255 256 } else { 257 State state = mStateList.get(id); 258 if (state == null) { 259 return -1; 260 } 261 int match = state.findMatch(width, height); 262 return (match == -1) ? state.mConstraintID : state.mVariants.get(match).mConstraintID; 263 } 264 265 } 266 267 ///////////////////////////////////////////////////////////////////////// 268 // This represents one state 269 ///////////////////////////////////////////////////////////////////////// 270 static class State { 271 int mId; 272 ArrayList<Variant> mVariants = new ArrayList<>(); 273 int mConstraintID = -1; 274 boolean mIsLayout = false; 275 State(Context context, XmlPullParser parser)276 State(Context context, XmlPullParser parser) { 277 AttributeSet attrs = Xml.asAttributeSet(parser); 278 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.State); 279 final int count = a.getIndexCount(); 280 for (int i = 0; i < count; i++) { 281 int attr = a.getIndex(i); 282 if (attr == R.styleable.State_android_id) { 283 mId = a.getResourceId(attr, mId); 284 } else if (attr == R.styleable.State_constraints) { 285 mConstraintID = a.getResourceId(attr, mConstraintID); 286 String type = context.getResources().getResourceTypeName(mConstraintID); 287 @SuppressWarnings("unused") 288 String name = context.getResources().getResourceName(mConstraintID); 289 290 if ("layout".equals(type)) { 291 mIsLayout = true; 292 } 293 } 294 } 295 a.recycle(); 296 } 297 add(Variant size)298 void add(Variant size) { 299 mVariants.add(size); 300 } 301 findMatch(float width, float height)302 public int findMatch(float width, float height) { 303 for (int i = 0; i < mVariants.size(); i++) { 304 if (mVariants.get(i).match(width, height)) { 305 return i; 306 } 307 } 308 return -1; 309 } 310 } 311 312 static class Variant { 313 int mId; 314 float mMinWidth = Float.NaN; 315 float mMinHeight = Float.NaN; 316 float mMaxWidth = Float.NaN; 317 float mMaxHeight = Float.NaN; 318 int mConstraintID = -1; 319 boolean mIsLayout = false; 320 Variant(Context context, XmlPullParser parser)321 Variant(Context context, XmlPullParser parser) { 322 AttributeSet attrs = Xml.asAttributeSet(parser); 323 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Variant); 324 final int count = a.getIndexCount(); 325 if (DEBUG) { 326 Log.v(TAG, "############### Variant"); 327 } 328 329 for (int i = 0; i < count; i++) { 330 int attr = a.getIndex(i); 331 if (attr == R.styleable.Variant_constraints) { 332 mConstraintID = a.getResourceId(attr, mConstraintID); 333 String type = context.getResources().getResourceTypeName(mConstraintID); 334 @SuppressWarnings("unused") 335 String name = context.getResources().getResourceName(mConstraintID); 336 337 if ("layout".equals(type)) { 338 mIsLayout = true; 339 } 340 } else if (attr == R.styleable.Variant_region_heightLessThan) { 341 mMaxHeight = a.getDimension(attr, mMaxHeight); 342 } else if (attr == R.styleable.Variant_region_heightMoreThan) { 343 mMinHeight = a.getDimension(attr, mMinHeight); 344 } else if (attr == R.styleable.Variant_region_widthLessThan) { 345 mMaxWidth = a.getDimension(attr, mMaxWidth); 346 } else if (attr == R.styleable.Variant_region_widthMoreThan) { 347 mMinWidth = a.getDimension(attr, mMinWidth); 348 } else { 349 Log.v(TAG, "Unknown tag"); 350 } 351 } 352 a.recycle(); 353 if (DEBUG) { 354 Log.v(TAG, "############### Variant"); 355 if (!Float.isNaN(mMinWidth)) { 356 Log.v(TAG, "############### Variant mMinWidth " + mMinWidth); 357 } 358 if (!Float.isNaN(mMinHeight)) { 359 Log.v(TAG, "############### Variant mMinHeight " + mMinHeight); 360 } 361 if (!Float.isNaN(mMaxWidth)) { 362 Log.v(TAG, "############### Variant mMaxWidth " + mMaxWidth); 363 } 364 if (!Float.isNaN(mMaxHeight)) { 365 Log.v(TAG, "############### Variant mMinWidth " + mMaxHeight); 366 } 367 } 368 } 369 match(float widthDp, float heightDp)370 boolean match(float widthDp, float heightDp) { 371 if (DEBUG) { 372 Log.v(TAG, "width = " + (int) widthDp 373 + " < " + mMinWidth + " && " + (int) widthDp + " > " + mMaxWidth 374 + " height = " + (int) heightDp 375 + " < " + mMinHeight + " && " + (int) heightDp + " > " + mMaxHeight); 376 } 377 if (!Float.isNaN(mMinWidth)) { 378 if (widthDp < mMinWidth) return false; 379 } 380 if (!Float.isNaN(mMinHeight)) { 381 if (heightDp < mMinHeight) return false; 382 } 383 if (!Float.isNaN(mMaxWidth)) { 384 if (widthDp > mMaxWidth) return false; 385 } 386 if (!Float.isNaN(mMaxHeight)) { 387 if (heightDp > mMaxHeight) return false; 388 } 389 return true; 390 } 391 } 392 393 } 394