1 /* 2 * Copyright (C) 2017 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 package androidx.constraintlayout.widget; 17 18 import android.content.Context; 19 import android.content.res.TypedArray; 20 import android.graphics.Color; 21 import android.graphics.drawable.ColorDrawable; 22 import android.graphics.drawable.Drawable; 23 import android.util.AttributeSet; 24 import android.util.Log; 25 import android.util.TypedValue; 26 import android.util.Xml; 27 import android.view.View; 28 29 import org.xmlpull.v1.XmlPullParser; 30 31 import java.lang.reflect.InvocationTargetException; 32 import java.lang.reflect.Method; 33 import java.util.HashMap; 34 35 /** 36 * Defines non standard Attributes 37 * 38 * 39 */ 40 public class ConstraintAttribute { 41 private static final String TAG = "TransitionLayout"; 42 private static final boolean DEBUG = false; 43 private boolean mMethod = false; 44 String mName; 45 private AttributeType mType; 46 private int mIntegerValue; 47 private float mFloatValue; 48 private String mStringValue; 49 boolean mBooleanValue; 50 private int mColorValue; 51 52 public enum AttributeType { 53 INT_TYPE, 54 FLOAT_TYPE, 55 COLOR_TYPE, 56 COLOR_DRAWABLE_TYPE, 57 STRING_TYPE, 58 BOOLEAN_TYPE, 59 DIMENSION_TYPE, 60 REFERENCE_TYPE 61 } 62 getName()63 public String getName() { 64 return mName; 65 } 66 isMethod()67 public boolean isMethod() { 68 return mMethod; 69 } 70 getIntegerValue()71 public int getIntegerValue() { 72 return mIntegerValue; 73 } 74 getFloatValue()75 public float getFloatValue() { 76 return mFloatValue; 77 } 78 getStringValue()79 public String getStringValue() { 80 return mStringValue; 81 } 82 isBooleanValue()83 public boolean isBooleanValue() { 84 return mBooleanValue; 85 } 86 getColorValue()87 public int getColorValue() { 88 return mColorValue; 89 } 90 getType()91 public AttributeType getType() { 92 return mType; 93 } 94 95 /** 96 * Continuous types are interpolated they are fired only at 97 * @return 98 */ isContinuous()99 public boolean isContinuous() { 100 switch (mType) { 101 case REFERENCE_TYPE: 102 case BOOLEAN_TYPE: 103 case STRING_TYPE: 104 return false; 105 default: 106 return true; 107 } 108 } 109 setFloatValue(float value)110 public void setFloatValue(float value) { 111 mFloatValue = value; 112 } 113 setColorValue(int value)114 public void setColorValue(int value) { 115 mColorValue = value; 116 } 117 setIntValue(int value)118 public void setIntValue(int value) { 119 mIntegerValue = value; 120 } 121 setStringValue(String value)122 public void setStringValue(String value) { 123 mStringValue = value; 124 } 125 126 /** 127 * The number of interpolation values that need to be interpolated 128 * Typically 1 but 3 for colors. 129 * 130 * @return Typically 1 but 3 for colors. 131 */ numberOfInterpolatedValues()132 public int numberOfInterpolatedValues() { 133 switch (mType) { 134 case COLOR_TYPE: 135 case COLOR_DRAWABLE_TYPE: 136 return 4; 137 default: 138 return 1; 139 } 140 } 141 142 /** 143 * Transforms value to a float for the purpose of interpolation 144 * 145 * @return interpolation value 146 */ getValueToInterpolate()147 public float getValueToInterpolate() { 148 switch (mType) { 149 case INT_TYPE: 150 return mIntegerValue; 151 case FLOAT_TYPE: 152 case DIMENSION_TYPE: 153 return mFloatValue; 154 case COLOR_TYPE: 155 case COLOR_DRAWABLE_TYPE: 156 throw new RuntimeException("Color does not have a single color to interpolate"); 157 case STRING_TYPE: 158 throw new RuntimeException("Cannot interpolate String"); 159 case BOOLEAN_TYPE: 160 return mBooleanValue ? 1 : 0; 161 case REFERENCE_TYPE: 162 return Float.NaN; 163 } 164 return Float.NaN; 165 } 166 167 /** 168 * populate the float array with colors it will fill 4 values 169 * @param ret 170 */ getValuesToInterpolate(float[] ret)171 public void getValuesToInterpolate(float[] ret) { 172 switch (mType) { 173 case INT_TYPE: 174 ret[0] = mIntegerValue; 175 break; 176 case FLOAT_TYPE: 177 ret[0] = mFloatValue; 178 break; 179 case COLOR_DRAWABLE_TYPE: 180 case COLOR_TYPE: 181 int a = 0xFF & (mColorValue >> 24); 182 int r = 0xFF & (mColorValue >> 16); 183 int g = 0xFF & (mColorValue >> 8); 184 int b = 0xFF & mColorValue; 185 float f_r = (float) Math.pow(r / 255.0f, 2.2); 186 float f_g = (float) Math.pow(g / 255.0f, 2.2); 187 float f_b = (float) Math.pow(b / 255.0f, 2.2); 188 ret[0] = f_r; 189 ret[1] = f_g; 190 ret[2] = f_b; 191 ret[3] = a / 255f; 192 break; 193 case STRING_TYPE: 194 throw new RuntimeException("Color does not have a single color to interpolate"); 195 case BOOLEAN_TYPE: 196 ret[0] = mBooleanValue ? 1 : 0; 197 break; 198 case DIMENSION_TYPE: 199 ret[0] = mFloatValue; 200 break; 201 default: 202 if (DEBUG) { 203 Log.v(TAG, mType.toString()); 204 } 205 } 206 } 207 208 /** 209 * setValue based on the values in the array 210 * @param value 211 */ setValue(float[] value)212 public void setValue(float[] value) { 213 switch (mType) { 214 case REFERENCE_TYPE: 215 case INT_TYPE: 216 mIntegerValue = (int) value[0]; 217 break; 218 case FLOAT_TYPE: 219 mFloatValue = value[0]; 220 break; 221 case COLOR_DRAWABLE_TYPE: 222 case COLOR_TYPE: 223 mColorValue = Color.HSVToColor(value); 224 mColorValue = (mColorValue & 0xFFFFFF) | (clamp((int) (0xFF * value[3])) << 24); 225 break; 226 case STRING_TYPE: 227 throw new RuntimeException("Color does not have a single color to interpolate"); 228 case BOOLEAN_TYPE: 229 mBooleanValue = value[0] > 0.5; 230 break; 231 case DIMENSION_TYPE: 232 mFloatValue = value[0]; 233 break; 234 default: 235 if (DEBUG) { 236 Log.v(TAG, mType.toString()); 237 } 238 239 } 240 } 241 242 /** 243 * test if the two attributes are different 244 * 245 * @param constraintAttribute 246 * @return 247 */ diff(ConstraintAttribute constraintAttribute)248 public boolean diff(ConstraintAttribute constraintAttribute) { 249 if (constraintAttribute == null || mType != constraintAttribute.mType) { 250 return false; 251 } 252 switch (mType) { 253 case INT_TYPE: 254 case REFERENCE_TYPE: 255 return mIntegerValue == constraintAttribute.mIntegerValue; 256 case FLOAT_TYPE: 257 return mFloatValue == constraintAttribute.mFloatValue; 258 case COLOR_TYPE: 259 case COLOR_DRAWABLE_TYPE: 260 return mColorValue == constraintAttribute.mColorValue; 261 case STRING_TYPE: 262 return mIntegerValue == constraintAttribute.mIntegerValue; 263 case BOOLEAN_TYPE: 264 return mBooleanValue == constraintAttribute.mBooleanValue; 265 case DIMENSION_TYPE: 266 return mFloatValue == constraintAttribute.mFloatValue; 267 } 268 return false; 269 } 270 ConstraintAttribute(String name, AttributeType attributeType)271 public ConstraintAttribute(String name, AttributeType attributeType) { 272 mName = name; 273 mType = attributeType; 274 } 275 ConstraintAttribute(String name, AttributeType attributeType, Object value, boolean method)276 public ConstraintAttribute(String name, 277 AttributeType attributeType, 278 Object value, 279 boolean method) { 280 mName = name; 281 mType = attributeType; 282 mMethod = method; 283 setValue(value); 284 } 285 ConstraintAttribute(ConstraintAttribute source, Object value)286 public ConstraintAttribute(ConstraintAttribute source, Object value) { 287 mName = source.mName; 288 mType = source.mType; 289 setValue(value); 290 } 291 292 /** 293 * set the value based on casting the object 294 * @param value 295 */ setValue(Object value)296 public void setValue(Object value) { 297 switch (mType) { 298 case REFERENCE_TYPE: 299 case INT_TYPE: 300 mIntegerValue = (Integer) value; 301 break; 302 case FLOAT_TYPE: 303 mFloatValue = (Float) value; 304 break; 305 case COLOR_TYPE: 306 case COLOR_DRAWABLE_TYPE: 307 mColorValue = (Integer) value; 308 break; 309 case STRING_TYPE: 310 mStringValue = (String) value; 311 break; 312 case BOOLEAN_TYPE: 313 mBooleanValue = (Boolean) value; 314 break; 315 case DIMENSION_TYPE: 316 mFloatValue = (Float) value; 317 break; 318 } 319 } 320 321 /** 322 * extract attributes from the view 323 * @param base 324 * @param view 325 * @return 326 */ extractAttributes( HashMap<String, ConstraintAttribute> base, View view)327 public static HashMap<String, ConstraintAttribute> extractAttributes( 328 HashMap<String, ConstraintAttribute> base, View view) { 329 HashMap<String, ConstraintAttribute> ret = new HashMap<>(); 330 Class<? extends View> viewClass = view.getClass(); 331 for (String name : base.keySet()) { 332 ConstraintAttribute constraintAttribute = base.get(name); 333 334 try { 335 if (name.equals("BackgroundColor")) { // hack for getMap set background color 336 ColorDrawable viewColor = (ColorDrawable) view.getBackground(); 337 Object val = viewColor.getColor(); 338 ret.put(name, new ConstraintAttribute(constraintAttribute, val)); 339 } else { 340 Method method = viewClass.getMethod("getMap" + name); 341 Object val = method.invoke(view); 342 ret.put(name, new ConstraintAttribute(constraintAttribute, val)); 343 } 344 } catch (NoSuchMethodException e) { 345 Log.e(TAG, viewClass.getName() + " must have a method " + name, e); 346 } catch (IllegalAccessException e) { 347 Log.e(TAG, " Custom Attribute \"" + name 348 + "\" not found on " + viewClass.getName(), e); 349 } catch (InvocationTargetException e) { 350 Log.e(TAG, " Custom Attribute \"" + name 351 + "\" not found on " + viewClass.getName(), e); 352 } 353 } 354 return ret; 355 } 356 357 /** 358 * set attributes from map on to the view 359 * @param view 360 * @param map 361 */ setAttributes(View view, HashMap<String, ConstraintAttribute> map)362 public static void setAttributes(View view, HashMap<String, ConstraintAttribute> map) { 363 Class<? extends View> viewClass = view.getClass(); 364 for (String name : map.keySet()) { 365 ConstraintAttribute constraintAttribute = map.get(name); 366 String methodName = name; 367 if (!constraintAttribute.mMethod) { 368 methodName = "set" + methodName; 369 } 370 try { 371 Method method; 372 switch (constraintAttribute.mType) { 373 case INT_TYPE: 374 method = viewClass.getMethod(methodName, Integer.TYPE); 375 method.invoke(view, constraintAttribute.mIntegerValue); 376 break; 377 case FLOAT_TYPE: 378 method = viewClass.getMethod(methodName, Float.TYPE); 379 method.invoke(view, constraintAttribute.mFloatValue); 380 break; 381 case COLOR_DRAWABLE_TYPE: 382 method = viewClass.getMethod(methodName, Drawable.class); 383 ColorDrawable drawable = new ColorDrawable(); // TODO cache 384 drawable.setColor(constraintAttribute.mColorValue); 385 method.invoke(view, drawable); 386 break; 387 case COLOR_TYPE: 388 method = viewClass.getMethod(methodName, Integer.TYPE); 389 method.invoke(view, constraintAttribute.mColorValue); 390 break; 391 case STRING_TYPE: 392 method = viewClass.getMethod(methodName, CharSequence.class); 393 method.invoke(view, constraintAttribute.mStringValue); 394 break; 395 case BOOLEAN_TYPE: 396 method = viewClass.getMethod(methodName, Boolean.TYPE); 397 method.invoke(view, constraintAttribute.mBooleanValue); 398 break; 399 case DIMENSION_TYPE: 400 method = viewClass.getMethod(methodName, Float.TYPE); 401 method.invoke(view, constraintAttribute.mFloatValue); 402 break; 403 case REFERENCE_TYPE: 404 method = viewClass.getMethod(methodName, Integer.TYPE); 405 method.invoke(view, constraintAttribute.mIntegerValue); 406 } 407 } catch (NoSuchMethodException e) { 408 Log.e(TAG, viewClass.getName() + " must have a method " + methodName, e); 409 } catch (IllegalAccessException e) { 410 Log.e(TAG, " Custom Attribute \"" + name 411 + "\" not found on " + viewClass.getName(), e); 412 } catch (InvocationTargetException e) { 413 Log.e(TAG, " Custom Attribute \"" + name 414 + "\" not found on " + viewClass.getName(), e); 415 } 416 } 417 } 418 419 /** 420 * Apply custom attributes to the view 421 * @param view 422 */ applyCustom(View view)423 public void applyCustom(View view) { 424 Class<? extends View> viewClass = view.getClass(); 425 String name = this.mName; 426 String methodName = name; 427 if (!mMethod) { 428 methodName = "set" + methodName; 429 } 430 try { 431 Method method; 432 switch (this.mType) { 433 case INT_TYPE: 434 case REFERENCE_TYPE: 435 method = viewClass.getMethod(methodName, Integer.TYPE); 436 method.invoke(view, this.mIntegerValue); 437 break; 438 case FLOAT_TYPE: 439 method = viewClass.getMethod(methodName, Float.TYPE); 440 method.invoke(view, this.mFloatValue); 441 break; 442 case COLOR_DRAWABLE_TYPE: 443 method = viewClass.getMethod(methodName, Drawable.class); 444 ColorDrawable drawable = new ColorDrawable(); // TODO cache 445 drawable.setColor(this.mColorValue); 446 method.invoke(view, drawable); 447 break; 448 case COLOR_TYPE: 449 method = viewClass.getMethod(methodName, Integer.TYPE); 450 method.invoke(view, this.mColorValue); 451 break; 452 case STRING_TYPE: 453 method = viewClass.getMethod(methodName, CharSequence.class); 454 method.invoke(view, this.mStringValue); 455 break; 456 case BOOLEAN_TYPE: 457 method = viewClass.getMethod(methodName, Boolean.TYPE); 458 method.invoke(view, this.mBooleanValue); 459 break; 460 case DIMENSION_TYPE: 461 method = viewClass.getMethod(methodName, Float.TYPE); 462 method.invoke(view, this.mFloatValue); 463 break; 464 } 465 } catch (NoSuchMethodException e) { 466 Log.e(TAG, viewClass.getName() + " must have a method " + methodName, e); 467 } catch (IllegalAccessException e) { 468 Log.e(TAG, " Custom Attribute \"" + name 469 + "\" not found on " + viewClass.getName(), e); 470 } catch (InvocationTargetException e) { 471 Log.e(TAG, " Custom Attribute \"" + name 472 + "\" not found on " + viewClass.getName(), e); 473 } 474 } 475 clamp(int c)476 private static int clamp(int c) { 477 int n = 255; 478 c &= ~(c >> 31); 479 c -= n; 480 c &= (c >> 31); 481 c += n; 482 return c; 483 } 484 485 /** 486 * parse Custom attributes and fill Custom 487 * @param context 488 * @param parser 489 * @param custom 490 */ parse(Context context, XmlPullParser parser, HashMap<String, ConstraintAttribute> custom)491 public static void parse(Context context, 492 XmlPullParser parser, 493 HashMap<String, ConstraintAttribute> custom) { 494 AttributeSet attributeSet = Xml.asAttributeSet(parser); 495 TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.CustomAttribute); 496 String name = null; 497 boolean method = false; 498 Object value = null; 499 AttributeType type = null; 500 final int count = a.getIndexCount(); 501 for (int i = 0; i < count; i++) { 502 int attr = a.getIndex(i); 503 if (attr == R.styleable.CustomAttribute_attributeName) { 504 name = a.getString(attr); 505 if (name != null && name.length() > 0) { 506 name = Character.toUpperCase(name.charAt(0)) + name.substring(1); 507 } 508 } else if (attr == R.styleable.CustomAttribute_methodName) { 509 method = true; 510 name = a.getString(attr); 511 } else if (attr == R.styleable.CustomAttribute_customBoolean) { 512 value = a.getBoolean(attr, false); 513 type = AttributeType.BOOLEAN_TYPE; 514 } else if (attr == R.styleable.CustomAttribute_customColorValue) { 515 type = AttributeType.COLOR_TYPE; 516 value = a.getColor(attr, 0); 517 } else if (attr == R.styleable.CustomAttribute_customColorDrawableValue) { 518 type = AttributeType.COLOR_DRAWABLE_TYPE; 519 value = a.getColor(attr, 0); 520 } else if (attr == R.styleable.CustomAttribute_customPixelDimension) { 521 type = AttributeType.DIMENSION_TYPE; 522 value = TypedValue.applyDimension( 523 TypedValue.COMPLEX_UNIT_DIP, 524 a.getDimension(attr, 0), 525 context.getResources().getDisplayMetrics()); 526 } else if (attr == R.styleable.CustomAttribute_customDimension) { 527 type = AttributeType.DIMENSION_TYPE; 528 value = a.getDimension(attr, 0); 529 } else if (attr == R.styleable.CustomAttribute_customFloatValue) { 530 type = AttributeType.FLOAT_TYPE; 531 value = a.getFloat(attr, Float.NaN); 532 } else if (attr == R.styleable.CustomAttribute_customIntegerValue) { 533 type = AttributeType.INT_TYPE; 534 value = a.getInteger(attr, -1); 535 } else if (attr == R.styleable.CustomAttribute_customStringValue) { 536 type = AttributeType.STRING_TYPE; 537 value = a.getString(attr); 538 } else if (attr == R.styleable.CustomAttribute_customReference) { 539 type = AttributeType.REFERENCE_TYPE; 540 int tmp = a.getResourceId(attr, -1); 541 if (tmp == -1) { 542 tmp = a.getInt(attr, -1); 543 } 544 value = tmp; 545 } 546 } 547 if (name != null && value != null) { 548 custom.put(name, new ConstraintAttribute(name, type, value, method)); 549 } 550 a.recycle(); 551 } 552 553 } 554