1/* 2 * Copyright (C) 2015 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 */ 16package android.support.v17.leanback.graphics; 17 18import android.content.res.Resources; 19import android.graphics.Canvas; 20import android.graphics.ColorFilter; 21import android.graphics.PixelFormat; 22import android.graphics.Rect; 23import android.graphics.drawable.Drawable; 24import android.support.annotation.NonNull; 25import android.support.v17.leanback.graphics.BoundsRule.ValueRule; 26import android.support.v4.graphics.drawable.DrawableCompat; 27import android.util.Property; 28 29import java.util.ArrayList; 30 31/** 32 * Generic drawable class that can be composed of multiple children. Whenever the bounds changes 33 * for this class, it updates those of its children. 34 */ 35public class CompositeDrawable extends Drawable implements Drawable.Callback { 36 37 static class CompositeState extends Drawable.ConstantState { 38 39 final ArrayList<ChildDrawable> mChildren; 40 41 CompositeState() { 42 mChildren = new ArrayList<ChildDrawable>(); 43 } 44 45 CompositeState(CompositeState other, CompositeDrawable parent, Resources res) { 46 final int n = other.mChildren.size(); 47 mChildren = new ArrayList<ChildDrawable>(n); 48 for (int k = 0; k < n; k++) { 49 mChildren.add(new ChildDrawable(other.mChildren.get(k), parent, res)); 50 } 51 } 52 53 @NonNull 54 @Override 55 public Drawable newDrawable() { 56 return new CompositeDrawable(this); 57 } 58 59 @Override 60 public int getChangingConfigurations() { 61 return 0; 62 } 63 64 } 65 66 CompositeState mState; 67 boolean mMutated = false; 68 69 public CompositeDrawable() { 70 mState = new CompositeState(); 71 } 72 73 CompositeDrawable(CompositeState state) { 74 mState = state; 75 } 76 77 @Override 78 public ConstantState getConstantState() { 79 return mState; 80 } 81 82 @Override 83 public Drawable mutate() { 84 if (!mMutated && super.mutate() == this) { 85 mState = new CompositeState(mState, this, null); 86 final ArrayList<ChildDrawable> children = mState.mChildren; 87 for (int i = 0, n = children.size(); i < n; i++) { 88 final Drawable dr = children.get(i).mDrawable; 89 if (dr != null) { 90 dr.mutate(); 91 } 92 } 93 mMutated = true; 94 } 95 return this; 96 } 97 98 /** 99 * Adds the supplied region. 100 */ 101 public void addChildDrawable(Drawable drawable) { 102 mState.mChildren.add(new ChildDrawable(drawable, this)); 103 } 104 105 /** 106 * Sets the supplied region at given index. 107 */ 108 public void setChildDrawableAt(int index, Drawable drawable) { 109 mState.mChildren.set(index, new ChildDrawable(drawable, this)); 110 } 111 112 /** 113 * Returns the {@link Drawable} for the given index. 114 */ 115 public Drawable getDrawable(int index) { 116 return mState.mChildren.get(index).mDrawable; 117 } 118 119 /** 120 * Returns the {@link ChildDrawable} at the given index. 121 */ 122 public ChildDrawable getChildAt(int index) { 123 return mState.mChildren.get(index); 124 } 125 126 /** 127 * Removes the child corresponding to the given index. 128 */ 129 public void removeChild(int index) { 130 mState.mChildren.remove(index); 131 } 132 133 /** 134 * Removes the given region. 135 */ 136 public void removeDrawable(Drawable drawable) { 137 final ArrayList<ChildDrawable> children = mState.mChildren; 138 for (int i = 0; i < children.size(); i++) { 139 if (drawable == children.get(i).mDrawable) { 140 children.get(i).mDrawable.setCallback(null); 141 children.remove(i); 142 return; 143 } 144 } 145 } 146 147 /** 148 * Returns the total number of children. 149 */ 150 public int getChildCount() { 151 return mState.mChildren.size(); 152 } 153 154 @Override 155 public void draw(Canvas canvas) { 156 final ArrayList<ChildDrawable> children = mState.mChildren; 157 for (int i = 0; i < children.size(); i++) { 158 children.get(i).mDrawable.draw(canvas); 159 } 160 } 161 162 @Override 163 protected void onBoundsChange(Rect bounds) { 164 super.onBoundsChange(bounds); 165 updateBounds(bounds); 166 } 167 168 @Override 169 public void setColorFilter(ColorFilter colorFilter) { 170 final ArrayList<ChildDrawable> children = mState.mChildren; 171 for (int i = 0; i < children.size(); i++) { 172 children.get(i).mDrawable.setColorFilter(colorFilter); 173 } 174 } 175 176 @Override 177 public int getOpacity() { 178 return PixelFormat.UNKNOWN; 179 } 180 181 @Override 182 public void setAlpha(int alpha) { 183 final ArrayList<ChildDrawable> children = mState.mChildren; 184 for (int i = 0; i < children.size(); i++) { 185 children.get(i).mDrawable.setAlpha(alpha); 186 } 187 } 188 189 /** 190 * @return Alpha value between 0(inclusive) and 255(inclusive) 191 */ 192 @Override 193 public int getAlpha() { 194 final Drawable dr = getFirstNonNullDrawable(); 195 if (dr != null) { 196 return DrawableCompat.getAlpha(dr); 197 } else { 198 return 0xFF; 199 } 200 } 201 202 final Drawable getFirstNonNullDrawable() { 203 final ArrayList<ChildDrawable> children = mState.mChildren; 204 for (int i = 0, n = children.size(); i < n; i++) { 205 final Drawable dr = children.get(i).mDrawable; 206 if (dr != null) { 207 return dr; 208 } 209 } 210 return null; 211 } 212 213 @Override 214 public void invalidateDrawable(Drawable who) { 215 invalidateSelf(); 216 } 217 218 @Override 219 public void scheduleDrawable(Drawable who, Runnable what, long when) { 220 scheduleSelf(what, when); 221 } 222 223 @Override 224 public void unscheduleDrawable(Drawable who, Runnable what) { 225 unscheduleSelf(what); 226 } 227 228 /** 229 * Updates the bounds based on the {@link BoundsRule}. 230 */ 231 void updateBounds(Rect bounds) { 232 final ArrayList<ChildDrawable> children = mState.mChildren; 233 for (int i = 0; i < children.size(); i++) { 234 ChildDrawable childDrawable = children.get(i); 235 childDrawable.updateBounds(bounds); 236 } 237 } 238 239 /** 240 * Wrapper class holding a drawable object and {@link BoundsRule} to update drawable bounds 241 * when parent bound changes. 242 */ 243 public static final class ChildDrawable { 244 private final BoundsRule mBoundsRule; 245 private final Drawable mDrawable; 246 private final Rect adjustedBounds = new Rect(); 247 final CompositeDrawable mParent; 248 249 public ChildDrawable(Drawable drawable, CompositeDrawable parent) { 250 this.mDrawable = drawable; 251 this.mParent = parent; 252 this.mBoundsRule = new BoundsRule(); 253 drawable.setCallback(parent); 254 } 255 256 ChildDrawable(ChildDrawable orig, CompositeDrawable parent, Resources res) { 257 final Drawable dr = orig.mDrawable; 258 final Drawable clone; 259 if (dr != null) { 260 final ConstantState cs = dr.getConstantState(); 261 if (res != null) { 262 clone = cs.newDrawable(res); 263 } else { 264 clone = cs.newDrawable(); 265 } 266 clone.setCallback(parent); 267 DrawableCompat.setLayoutDirection(clone, DrawableCompat.getLayoutDirection(dr)); 268 clone.setBounds(dr.getBounds()); 269 clone.setLevel(dr.getLevel()); 270 } else { 271 clone = null; 272 } 273 if (orig.mBoundsRule != null) { 274 this.mBoundsRule = new BoundsRule(orig.mBoundsRule); 275 } else { 276 this.mBoundsRule = new BoundsRule(); 277 } 278 mDrawable = clone; 279 mParent = parent; 280 } 281 282 /** 283 * Returns the instance of {@link BoundsRule}. 284 */ 285 public BoundsRule getBoundsRule() { 286 return this.mBoundsRule; 287 } 288 289 /** 290 * Returns the {@link Drawable}. 291 */ 292 public Drawable getDrawable() { 293 return mDrawable; 294 } 295 296 /** 297 * Updates the bounds based on the {@link BoundsRule}. 298 */ 299 void updateBounds(Rect bounds) { 300 mBoundsRule.calculateBounds(bounds, adjustedBounds); 301 mDrawable.setBounds(adjustedBounds); 302 } 303 304 /** 305 * After changing the {@link BoundsRule}, user should call this function 306 * for the drawable to recalculate its bounds. 307 */ 308 public void recomputeBounds() { 309 updateBounds(mParent.getBounds()); 310 } 311 312 /** 313 * Implementation of {@link Property} for overrideTop attribute. 314 */ 315 public static final Property<CompositeDrawable.ChildDrawable, Integer> TOP_ABSOLUTE = 316 new Property<CompositeDrawable.ChildDrawable, Integer>( 317 Integer.class, "absoluteTop") { 318 @Override 319 public void set(CompositeDrawable.ChildDrawable obj, Integer value) { 320 if (obj.getBoundsRule().top == null) { 321 obj.getBoundsRule().top = ValueRule.absoluteValue(value); 322 } else { 323 obj.getBoundsRule().top.setAbsoluteValue(value); 324 } 325 326 obj.recomputeBounds(); 327 } 328 329 @Override 330 public Integer get(CompositeDrawable.ChildDrawable obj) { 331 if (obj.getBoundsRule().top == null) { 332 return obj.mParent.getBounds().top; 333 } 334 return obj.getBoundsRule().top.getAbsoluteValue(); 335 } 336 }; 337 338 /** 339 * Implementation of {@link Property} for overrideBottom attribute. 340 */ 341 public static final Property<CompositeDrawable.ChildDrawable, Integer> BOTTOM_ABSOLUTE = 342 new Property<CompositeDrawable.ChildDrawable, Integer>( 343 Integer.class, "absoluteBottom") { 344 @Override 345 public void set(CompositeDrawable.ChildDrawable obj, Integer value) { 346 if (obj.getBoundsRule().bottom == null) { 347 obj.getBoundsRule().bottom = ValueRule.absoluteValue(value); 348 } else { 349 obj.getBoundsRule().bottom.setAbsoluteValue(value); 350 } 351 352 obj.recomputeBounds(); 353 } 354 355 @Override 356 public Integer get(CompositeDrawable.ChildDrawable obj) { 357 if (obj.getBoundsRule().bottom == null) { 358 return obj.mParent.getBounds().bottom; 359 } 360 return obj.getBoundsRule().bottom.getAbsoluteValue(); 361 } 362 }; 363 364 365 /** 366 * Implementation of {@link Property} for overrideLeft attribute. 367 */ 368 public static final Property<CompositeDrawable.ChildDrawable, Integer> LEFT_ABSOLUTE = 369 new Property<CompositeDrawable.ChildDrawable, Integer>( 370 Integer.class, "absoluteLeft") { 371 @Override 372 public void set(CompositeDrawable.ChildDrawable obj, Integer value) { 373 if (obj.getBoundsRule().left == null) { 374 obj.getBoundsRule().left = ValueRule.absoluteValue(value); 375 } else { 376 obj.getBoundsRule().left.setAbsoluteValue(value); 377 } 378 379 obj.recomputeBounds(); 380 } 381 382 @Override 383 public Integer get(CompositeDrawable.ChildDrawable obj) { 384 if (obj.getBoundsRule().left == null) { 385 return obj.mParent.getBounds().left; 386 } 387 return obj.getBoundsRule().left.getAbsoluteValue(); 388 } 389 }; 390 391 /** 392 * Implementation of {@link Property} for overrideRight attribute. 393 */ 394 public static final Property<CompositeDrawable.ChildDrawable, Integer> RIGHT_ABSOLUTE = 395 new Property<CompositeDrawable.ChildDrawable, Integer>( 396 Integer.class, "absoluteRight") { 397 @Override 398 public void set(CompositeDrawable.ChildDrawable obj, Integer value) { 399 if (obj.getBoundsRule().right == null) { 400 obj.getBoundsRule().right = ValueRule.absoluteValue(value); 401 } else { 402 obj.getBoundsRule().right.setAbsoluteValue(value); 403 } 404 405 obj.recomputeBounds(); 406 } 407 408 @Override 409 public Integer get(CompositeDrawable.ChildDrawable obj) { 410 if (obj.getBoundsRule().right == null) { 411 return obj.mParent.getBounds().right; 412 } 413 return obj.getBoundsRule().right.getAbsoluteValue(); 414 } 415 }; 416 417 /** 418 * Implementation of {@link Property} for overwriting the bottom attribute of 419 * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to 420 * change the bounds rules as a percentage of parent size. This is preferable over 421 * {@see PROPERTY_TOP_ABSOLUTE} when the exact start/end position of scroll movement 422 * isn't available at compile time. 423 */ 424 public static final Property<CompositeDrawable.ChildDrawable, Float> TOP_FRACTION = 425 new Property<CompositeDrawable.ChildDrawable, Float>(Float.class, "fractionTop") { 426 @Override 427 public void set(CompositeDrawable.ChildDrawable obj, Float value) { 428 if (obj.getBoundsRule().top == null) { 429 obj.getBoundsRule().top = ValueRule.inheritFromParent(value); 430 } else { 431 obj.getBoundsRule().top.setFraction(value); 432 } 433 434 obj.recomputeBounds(); 435 } 436 437 @Override 438 public Float get(CompositeDrawable.ChildDrawable obj) { 439 if (obj.getBoundsRule().top == null) { 440 return 0f; 441 } 442 return obj.getBoundsRule().top.getFraction(); 443 } 444 }; 445 446 /** 447 * Implementation of {@link Property} for overwriting the bottom attribute of 448 * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to 449 * change the bounds rules as a percentage of parent size. This is preferable over 450 * {@see PROPERTY_BOTTOM_ABSOLUTE} when the exact start/end position of scroll movement 451 * isn't available at compile time. 452 */ 453 public static final Property<CompositeDrawable.ChildDrawable, Float> BOTTOM_FRACTION = 454 new Property<CompositeDrawable.ChildDrawable, Float>( 455 Float.class, "fractionBottom") { 456 @Override 457 public void set(CompositeDrawable.ChildDrawable obj, Float value) { 458 if (obj.getBoundsRule().bottom == null) { 459 obj.getBoundsRule().bottom = ValueRule.inheritFromParent(value); 460 } else { 461 obj.getBoundsRule().bottom.setFraction(value); 462 } 463 464 obj.recomputeBounds(); 465 } 466 467 @Override 468 public Float get(CompositeDrawable.ChildDrawable obj) { 469 if (obj.getBoundsRule().bottom == null) { 470 return 1f; 471 } 472 return obj.getBoundsRule().bottom.getFraction(); 473 } 474 }; 475 476 /** 477 * Implementation of {@link Property} for overwriting the bottom attribute of 478 * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to 479 * change the bounds rules as a percentage of parent size. This is preferable over 480 * {@see PROPERTY_LEFT_ABSOLUTE} when the exact start/end position of scroll movement 481 * isn't available at compile time. 482 */ 483 public static final Property<CompositeDrawable.ChildDrawable, Float> LEFT_FRACTION = 484 new Property<CompositeDrawable.ChildDrawable, Float>(Float.class, "fractionLeft") { 485 @Override 486 public void set(CompositeDrawable.ChildDrawable obj, Float value) { 487 if (obj.getBoundsRule().left == null) { 488 obj.getBoundsRule().left = ValueRule.inheritFromParent(value); 489 } else { 490 obj.getBoundsRule().left.setFraction(value); 491 } 492 493 obj.recomputeBounds(); 494 } 495 496 @Override 497 public Float get(CompositeDrawable.ChildDrawable obj) { 498 if (obj.getBoundsRule().left == null) { 499 return 0f; 500 } 501 return obj.getBoundsRule().left.getFraction(); 502 } 503 }; 504 505 /** 506 * Implementation of {@link Property} for overwriting the bottom attribute of 507 * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to 508 * change the bounds rules as a percentage of parent size. This is preferable over 509 * {@see PROPERTY_RIGHT_ABSOLUTE} when the exact start/end position of scroll movement 510 * isn't available at compile time. 511 */ 512 public static final Property<CompositeDrawable.ChildDrawable, Float> RIGHT_FRACTION = 513 new Property<CompositeDrawable.ChildDrawable, Float>(Float.class, "fractionRight") { 514 @Override 515 public void set(CompositeDrawable.ChildDrawable obj, Float value) { 516 if (obj.getBoundsRule().right == null) { 517 obj.getBoundsRule().right = ValueRule.inheritFromParent(value); 518 } else { 519 obj.getBoundsRule().right.setFraction(value); 520 } 521 522 obj.recomputeBounds(); 523 } 524 525 @Override 526 public Float get(CompositeDrawable.ChildDrawable obj) { 527 if (obj.getBoundsRule().right == null) { 528 return 1f; 529 } 530 return obj.getBoundsRule().right.getFraction(); 531 } 532 }; 533 } 534} 535