CompositeDrawable.java revision 00f780c9e3cca0b2e364c61e936147e09877c43f
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 private 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>(Integer.class, "absoluteTop") { 306 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 return obj.getBoundsRule().mTop.getAbsoluteValue(); 321 } 322 }; 323 324 325 /** 326 * Implementation of {@link Property} for overrideBottom attribute. 327 */ 328 public static final Property<CompositeDrawable.ChildDrawable, Integer> BOTTOM_ABSOLUTE 329 = new Property<CompositeDrawable.ChildDrawable, Integer>( 330 Integer.class, "absoluteBottom") { 331 332 @Override 333 public void set(CompositeDrawable.ChildDrawable obj, Integer value) { 334 if (obj.getBoundsRule().mBottom == null) { 335 obj.getBoundsRule().mBottom = BoundsRule.absoluteValue(value); 336 } else { 337 obj.getBoundsRule().mBottom.setAbsoluteValue(value); 338 } 339 340 obj.recomputeBounds(); 341 } 342 343 @Override 344 public Integer get(CompositeDrawable.ChildDrawable obj) { 345 return obj.getBoundsRule().mBottom.getAbsoluteValue(); 346 } 347 }; 348 349 350 /** 351 * Implementation of {@link Property} for overrideLeft attribute. 352 */ 353 public static final Property<CompositeDrawable.ChildDrawable, Integer> LEFT_ABSOLUTE 354 = new Property<CompositeDrawable.ChildDrawable, Integer>( 355 Integer.class, "absoluteLeft") { 356 357 @Override 358 public void set(CompositeDrawable.ChildDrawable obj, Integer value) { 359 if (obj.getBoundsRule().mLeft == null) { 360 obj.getBoundsRule().mLeft = BoundsRule.absoluteValue(value); 361 } else { 362 obj.getBoundsRule().mLeft.setAbsoluteValue(value); 363 } 364 365 obj.recomputeBounds(); 366 } 367 368 @Override 369 public Integer get(CompositeDrawable.ChildDrawable obj) { 370 return obj.getBoundsRule().mLeft.getAbsoluteValue(); 371 } 372 }; 373 374 /** 375 * Implementation of {@link Property} for overrideRight attribute. 376 */ 377 public static final Property<CompositeDrawable.ChildDrawable, Integer> RIGHT_ABSOLUTE 378 = new Property<CompositeDrawable.ChildDrawable, Integer>( 379 Integer.class, "absoluteRight") { 380 381 @Override 382 public void set(CompositeDrawable.ChildDrawable obj, Integer value) { 383 if (obj.getBoundsRule().mRight == null) { 384 obj.getBoundsRule().mRight = BoundsRule.absoluteValue(value); 385 } else { 386 obj.getBoundsRule().mRight.setAbsoluteValue(value); 387 } 388 389 obj.recomputeBounds(); 390 } 391 392 @Override 393 public Integer get(CompositeDrawable.ChildDrawable obj) { 394 return obj.getBoundsRule().mRight.getAbsoluteValue(); 395 } 396 }; 397 398 /** 399 * Implementation of {@link Property} for overwriting the bottom attribute of 400 * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to 401 * change the bounds rules as a percentage of parent size. This is preferable over 402 * {@see PROPERTY_TOP_ABSOLUTE} when the exact start/end position of scroll movement 403 * isn't available at compile time. 404 */ 405 public static final Property<CompositeDrawable.ChildDrawable, Float> TOP_FRACTION 406 = new Property<CompositeDrawable.ChildDrawable, Float>(Float.class, "fractionTop") { 407 408 @Override 409 public void set(CompositeDrawable.ChildDrawable obj, Float value) { 410 if (obj.getBoundsRule().mTop == null) { 411 obj.getBoundsRule().mTop = BoundsRule.inheritFromParent(value); 412 } else { 413 obj.getBoundsRule().mTop.setFraction(value); 414 } 415 416 obj.recomputeBounds(); 417 } 418 419 @Override 420 public Float get(CompositeDrawable.ChildDrawable obj) { 421 return obj.getBoundsRule().mTop.getFraction(); 422 } 423 }; 424 425 /** 426 * Implementation of {@link Property} for overwriting the bottom attribute of 427 * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to 428 * change the bounds rules as a percentage of parent size. This is preferable over 429 * {@see PROPERTY_BOTTOM_ABSOLUTE} when the exact start/end position of scroll movement 430 * isn't available at compile time. 431 */ 432 public static final Property<CompositeDrawable.ChildDrawable, Float> BOTTOM_FRACTION 433 = new Property<CompositeDrawable.ChildDrawable, Float>( 434 Float.class, "fractionBottom") { 435 436 @Override 437 public void set(CompositeDrawable.ChildDrawable obj, Float value) { 438 if (obj.getBoundsRule().mBottom == null) { 439 obj.getBoundsRule().mBottom = BoundsRule.inheritFromParent(value); 440 } else { 441 obj.getBoundsRule().mBottom.setFraction(value); 442 } 443 444 obj.recomputeBounds(); 445 } 446 447 @Override 448 public Float get(CompositeDrawable.ChildDrawable obj) { 449 return obj.getBoundsRule().mBottom.getFraction(); 450 } 451 }; 452 453 /** 454 * Implementation of {@link Property} for overwriting the bottom attribute of 455 * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to 456 * change the bounds rules as a percentage of parent size. This is preferable over 457 * {@see PROPERTY_LEFT_ABSOLUTE} when the exact start/end position of scroll movement 458 * isn't available at compile time. 459 */ 460 public static final Property<CompositeDrawable.ChildDrawable, Float> LEFT_FRACTION 461 = new Property<CompositeDrawable.ChildDrawable, Float>(Float.class, "fractionLeft") { 462 463 @Override 464 public void set(CompositeDrawable.ChildDrawable obj, Float value) { 465 if (obj.getBoundsRule().mLeft == null) { 466 obj.getBoundsRule().mLeft = BoundsRule.inheritFromParent(value); 467 } else { 468 obj.getBoundsRule().mLeft.setFraction(value); 469 } 470 471 obj.recomputeBounds(); 472 } 473 474 @Override 475 public Float get(CompositeDrawable.ChildDrawable obj) { 476 return obj.getBoundsRule().mLeft.getFraction(); 477 } 478 }; 479 480 /** 481 * Implementation of {@link Property} for overwriting the bottom attribute of 482 * {@link BoundsRule} associated with this {@link ChildDrawable}. This allows users to 483 * change the bounds rules as a percentage of parent size. This is preferable over 484 * {@see PROPERTY_RIGHT_ABSOLUTE} when the exact start/end position of scroll movement 485 * isn't available at compile time. 486 */ 487 public static final Property<CompositeDrawable.ChildDrawable, Float> RIGHT_FRACTION 488 = new Property<CompositeDrawable.ChildDrawable, Float>( 489 Float.class, "fractoinRight") { 490 491 @Override 492 public void set(CompositeDrawable.ChildDrawable obj, Float value) { 493 if (obj.getBoundsRule().mRight == null) { 494 obj.getBoundsRule().mRight = BoundsRule.inheritFromParent(value); 495 } else { 496 obj.getBoundsRule().mRight.setFraction(value); 497 } 498 499 obj.recomputeBounds(); 500 } 501 502 @Override 503 public Float get(CompositeDrawable.ChildDrawable obj) { 504 return obj.getBoundsRule().mRight.getFraction(); 505 } 506 }; 507 } 508} 509