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