1/* 2 * Copyright (C) 2006 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 17package android.graphics.drawable; 18 19import android.content.res.Resources; 20import android.graphics.*; 21 22public class DrawableContainer extends Drawable implements Drawable.Callback { 23 24 /** 25 * To be proper, we should have a getter for dither (and alpha, etc.) 26 * so that proxy classes like this can save/restore their delegates' 27 * values, but we don't have getters. Since we do have setters 28 * (e.g. setDither), which this proxy forwards on, we have to have some 29 * default/initial setting. 30 * 31 * The initial setting for dither is now true, since it almost always seems 32 * to improve the quality at negligible cost. 33 */ 34 private static final boolean DEFAULT_DITHER = true; 35 private DrawableContainerState mDrawableContainerState; 36 private Drawable mCurrDrawable; 37 private int mAlpha = 0xFF; 38 private ColorFilter mColorFilter; 39 40 private int mCurIndex = -1; 41 private boolean mMutated; 42 43 // overrides from Drawable 44 45 @Override 46 public void draw(Canvas canvas) { 47 if (mCurrDrawable != null) { 48 mCurrDrawable.draw(canvas); 49 } 50 } 51 52 @Override 53 public int getChangingConfigurations() { 54 return super.getChangingConfigurations() 55 | mDrawableContainerState.mChangingConfigurations 56 | mDrawableContainerState.mChildrenChangingConfigurations; 57 } 58 59 @Override 60 public boolean getPadding(Rect padding) { 61 final Rect r = mDrawableContainerState.getConstantPadding(); 62 if (r != null) { 63 padding.set(r); 64 return true; 65 } 66 if (mCurrDrawable != null) { 67 return mCurrDrawable.getPadding(padding); 68 } else { 69 return super.getPadding(padding); 70 } 71 } 72 73 @Override 74 public void setAlpha(int alpha) { 75 if (mAlpha != alpha) { 76 mAlpha = alpha; 77 if (mCurrDrawable != null) { 78 mCurrDrawable.setAlpha(alpha); 79 } 80 } 81 } 82 83 @Override 84 public void setDither(boolean dither) { 85 if (mDrawableContainerState.mDither != dither) { 86 mDrawableContainerState.mDither = dither; 87 if (mCurrDrawable != null) { 88 mCurrDrawable.setDither(mDrawableContainerState.mDither); 89 } 90 } 91 } 92 93 @Override 94 public void setColorFilter(ColorFilter cf) { 95 if (mColorFilter != cf) { 96 mColorFilter = cf; 97 if (mCurrDrawable != null) { 98 mCurrDrawable.setColorFilter(cf); 99 } 100 } 101 } 102 103 @Override 104 protected void onBoundsChange(Rect bounds) { 105 if (mCurrDrawable != null) { 106 mCurrDrawable.setBounds(bounds); 107 } 108 } 109 110 @Override 111 public boolean isStateful() { 112 return mDrawableContainerState.isStateful(); 113 } 114 115 @Override 116 protected boolean onStateChange(int[] state) { 117 if (mCurrDrawable != null) { 118 return mCurrDrawable.setState(state); 119 } 120 return false; 121 } 122 123 @Override 124 protected boolean onLevelChange(int level) { 125 if (mCurrDrawable != null) { 126 return mCurrDrawable.setLevel(level); 127 } 128 return false; 129 } 130 131 @Override 132 public int getIntrinsicWidth() { 133 if (mDrawableContainerState.isConstantSize()) { 134 return mDrawableContainerState.getConstantWidth(); 135 } 136 return mCurrDrawable != null ? mCurrDrawable.getIntrinsicWidth() : -1; 137 } 138 139 @Override 140 public int getIntrinsicHeight() { 141 if (mDrawableContainerState.isConstantSize()) { 142 return mDrawableContainerState.getConstantHeight(); 143 } 144 return mCurrDrawable != null ? mCurrDrawable.getIntrinsicHeight() : -1; 145 } 146 147 @Override 148 public int getMinimumWidth() { 149 if (mDrawableContainerState.isConstantSize()) { 150 return mDrawableContainerState.getConstantMinimumWidth(); 151 } 152 return mCurrDrawable != null ? mCurrDrawable.getMinimumWidth() : 0; 153 } 154 155 @Override 156 public int getMinimumHeight() { 157 if (mDrawableContainerState.isConstantSize()) { 158 return mDrawableContainerState.getConstantMinimumHeight(); 159 } 160 return mCurrDrawable != null ? mCurrDrawable.getMinimumHeight() : 0; 161 } 162 163 public void invalidateDrawable(Drawable who) 164 { 165 if (who == mCurrDrawable && mCallback != null) { 166 mCallback.invalidateDrawable(this); 167 } 168 } 169 170 public void scheduleDrawable(Drawable who, Runnable what, long when) 171 { 172 if (who == mCurrDrawable && mCallback != null) { 173 mCallback.scheduleDrawable(this, what, when); 174 } 175 } 176 177 public void unscheduleDrawable(Drawable who, Runnable what) 178 { 179 if (who == mCurrDrawable && mCallback != null) { 180 mCallback.unscheduleDrawable(this, what); 181 } 182 } 183 184 @Override 185 public boolean setVisible(boolean visible, boolean restart) { 186 boolean changed = super.setVisible(visible, restart); 187 if (mCurrDrawable != null) { 188 mCurrDrawable.setVisible(visible, restart); 189 } 190 return changed; 191 } 192 193 @Override 194 public int getOpacity() { 195 return mCurrDrawable == null || !mCurrDrawable.isVisible() ? PixelFormat.TRANSPARENT : 196 mDrawableContainerState.getOpacity(); 197 } 198 199 public boolean selectDrawable(int idx) 200 { 201 if (idx == mCurIndex) { 202 return false; 203 } 204 if (idx >= 0 && idx < mDrawableContainerState.mNumChildren) { 205 Drawable d = mDrawableContainerState.mDrawables[idx]; 206 if (mCurrDrawable != null) { 207 mCurrDrawable.setVisible(false, false); 208 } 209 mCurrDrawable = d; 210 mCurIndex = idx; 211 if (d != null) { 212 d.setVisible(isVisible(), true); 213 d.setAlpha(mAlpha); 214 d.setDither(mDrawableContainerState.mDither); 215 d.setColorFilter(mColorFilter); 216 d.setState(getState()); 217 d.setLevel(getLevel()); 218 d.setBounds(getBounds()); 219 } 220 } else { 221 if (mCurrDrawable != null) { 222 mCurrDrawable.setVisible(false, false); 223 } 224 mCurrDrawable = null; 225 mCurIndex = -1; 226 } 227 invalidateSelf(); 228 return true; 229 } 230 231 @Override 232 public Drawable getCurrent() { 233 return mCurrDrawable; 234 } 235 236 @Override 237 public ConstantState getConstantState() { 238 if (mDrawableContainerState.canConstantState()) { 239 mDrawableContainerState.mChangingConfigurations = super.getChangingConfigurations(); 240 return mDrawableContainerState; 241 } 242 return null; 243 } 244 245 @Override 246 public Drawable mutate() { 247 if (!mMutated && super.mutate() == this) { 248 final int N = mDrawableContainerState.getChildCount(); 249 final Drawable[] drawables = mDrawableContainerState.getChildren(); 250 for (int i = 0; i < N; i++) { 251 if (drawables[i] != null) drawables[i].mutate(); 252 } 253 mMutated = true; 254 } 255 return this; 256 } 257 258 public abstract static class DrawableContainerState extends ConstantState { 259 final DrawableContainer mOwner; 260 261 int mChangingConfigurations; 262 int mChildrenChangingConfigurations; 263 264 Drawable[] mDrawables; 265 int mNumChildren; 266 267 boolean mVariablePadding = false; 268 Rect mConstantPadding = null; 269 270 boolean mConstantSize = false; 271 boolean mComputedConstantSize = false; 272 int mConstantWidth; 273 int mConstantHeight; 274 int mConstantMinimumWidth; 275 int mConstantMinimumHeight; 276 277 boolean mHaveOpacity = false; 278 int mOpacity; 279 280 boolean mHaveStateful = false; 281 boolean mStateful; 282 283 boolean mCheckedConstantState; 284 boolean mCanConstantState; 285 286 boolean mPaddingChecked = false; 287 288 boolean mDither = DEFAULT_DITHER; 289 290 DrawableContainerState(DrawableContainerState orig, DrawableContainer owner, 291 Resources res) { 292 mOwner = owner; 293 294 if (orig != null) { 295 mChangingConfigurations = orig.mChangingConfigurations; 296 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 297 298 final Drawable[] origDr = orig.mDrawables; 299 300 mDrawables = new Drawable[origDr.length]; 301 mNumChildren = orig.mNumChildren; 302 303 final int N = mNumChildren; 304 for (int i=0; i<N; i++) { 305 if (res != null) { 306 mDrawables[i] = origDr[i].getConstantState().newDrawable(res); 307 } else { 308 mDrawables[i] = origDr[i].getConstantState().newDrawable(); 309 } 310 mDrawables[i].setCallback(owner); 311 } 312 313 mCheckedConstantState = mCanConstantState = true; 314 mVariablePadding = orig.mVariablePadding; 315 if (orig.mConstantPadding != null) { 316 mConstantPadding = new Rect(orig.mConstantPadding); 317 } 318 mConstantSize = orig.mConstantSize; 319 mComputedConstantSize = orig.mComputedConstantSize; 320 mConstantWidth = orig.mConstantWidth; 321 mConstantHeight = orig.mConstantHeight; 322 323 mHaveOpacity = orig.mHaveOpacity; 324 mOpacity = orig.mOpacity; 325 mHaveStateful = orig.mHaveStateful; 326 mStateful = orig.mStateful; 327 328 mDither = orig.mDither; 329 330 } else { 331 mDrawables = new Drawable[10]; 332 mNumChildren = 0; 333 mCheckedConstantState = mCanConstantState = false; 334 } 335 } 336 337 @Override 338 public int getChangingConfigurations() { 339 return mChangingConfigurations; 340 } 341 342 public final int addChild(Drawable dr) { 343 final int pos = mNumChildren; 344 345 if (pos >= mDrawables.length) { 346 growArray(pos, pos+10); 347 } 348 349 dr.setVisible(false, true); 350 dr.setCallback(mOwner); 351 352 mDrawables[pos] = dr; 353 mNumChildren++; 354 mChildrenChangingConfigurations |= dr.getChangingConfigurations(); 355 mHaveOpacity = false; 356 mHaveStateful = false; 357 358 mConstantPadding = null; 359 mPaddingChecked = false; 360 mComputedConstantSize = false; 361 362 return pos; 363 } 364 365 public final int getChildCount() { 366 return mNumChildren; 367 } 368 369 public final Drawable[] getChildren() { 370 return mDrawables; 371 } 372 373 /** A boolean value indicating whether to use the maximum padding value of 374 * all frames in the set (false), or to use the padding value of the frame 375 * being shown (true). Default value is false. 376 */ 377 public final void setVariablePadding(boolean variable) { 378 mVariablePadding = variable; 379 } 380 381 public final Rect getConstantPadding() { 382 if (mVariablePadding) { 383 return null; 384 } 385 if (mConstantPadding != null || mPaddingChecked) { 386 return mConstantPadding; 387 } 388 389 Rect r = null; 390 final Rect t = new Rect(); 391 final int N = getChildCount(); 392 final Drawable[] drawables = mDrawables; 393 for (int i = 0; i < N; i++) { 394 if (drawables[i].getPadding(t)) { 395 if (r == null) r = new Rect(0, 0, 0, 0); 396 if (t.left > r.left) r.left = t.left; 397 if (t.top > r.top) r.top = t.top; 398 if (t.right > r.right) r.right = t.right; 399 if (t.bottom > r.bottom) r.bottom = t.bottom; 400 } 401 } 402 mPaddingChecked = true; 403 return (mConstantPadding = r); 404 } 405 406 public final void setConstantSize(boolean constant) { 407 mConstantSize = constant; 408 } 409 410 public final boolean isConstantSize() { 411 return mConstantSize; 412 } 413 414 public final int getConstantWidth() { 415 if (!mComputedConstantSize) { 416 computeConstantSize(); 417 } 418 419 return mConstantWidth; 420 } 421 422 public final int getConstantHeight() { 423 if (!mComputedConstantSize) { 424 computeConstantSize(); 425 } 426 427 return mConstantHeight; 428 } 429 430 public final int getConstantMinimumWidth() { 431 if (!mComputedConstantSize) { 432 computeConstantSize(); 433 } 434 435 return mConstantMinimumWidth; 436 } 437 438 public final int getConstantMinimumHeight() { 439 if (!mComputedConstantSize) { 440 computeConstantSize(); 441 } 442 443 return mConstantMinimumHeight; 444 } 445 446 private void computeConstantSize() { 447 mComputedConstantSize = true; 448 449 final int N = getChildCount(); 450 final Drawable[] drawables = mDrawables; 451 mConstantWidth = mConstantHeight = 0; 452 mConstantMinimumWidth = mConstantMinimumHeight = 0; 453 for (int i = 0; i < N; i++) { 454 Drawable dr = drawables[i]; 455 int s = dr.getIntrinsicWidth(); 456 if (s > mConstantWidth) mConstantWidth = s; 457 s = dr.getIntrinsicHeight(); 458 if (s > mConstantHeight) mConstantHeight = s; 459 s = dr.getMinimumWidth(); 460 if (s > mConstantMinimumWidth) mConstantMinimumWidth = s; 461 s = dr.getMinimumHeight(); 462 if (s > mConstantMinimumHeight) mConstantMinimumHeight = s; 463 } 464 } 465 466 public final int getOpacity() { 467 if (mHaveOpacity) { 468 return mOpacity; 469 } 470 471 final int N = getChildCount(); 472 final Drawable[] drawables = mDrawables; 473 int op = N > 0 ? drawables[0].getOpacity() : PixelFormat.TRANSPARENT; 474 for (int i = 1; i < N; i++) { 475 op = Drawable.resolveOpacity(op, drawables[i].getOpacity()); 476 } 477 mOpacity = op; 478 mHaveOpacity = true; 479 return op; 480 } 481 482 public final boolean isStateful() { 483 if (mHaveStateful) { 484 return mStateful; 485 } 486 487 boolean stateful = false; 488 final int N = getChildCount(); 489 for (int i = 0; i < N; i++) { 490 if (mDrawables[i].isStateful()) { 491 stateful = true; 492 break; 493 } 494 } 495 496 mStateful = stateful; 497 mHaveStateful = true; 498 return stateful; 499 } 500 501 public void growArray(int oldSize, int newSize) { 502 Drawable[] newDrawables = new Drawable[newSize]; 503 System.arraycopy(mDrawables, 0, newDrawables, 0, oldSize); 504 mDrawables = newDrawables; 505 } 506 507 public synchronized boolean canConstantState() { 508 if (!mCheckedConstantState) { 509 mCanConstantState = true; 510 final int N = mNumChildren; 511 for (int i=0; i<N; i++) { 512 if (mDrawables[i].getConstantState() == null) { 513 mCanConstantState = false; 514 break; 515 } 516 } 517 mCheckedConstantState = true; 518 } 519 520 return mCanConstantState; 521 } 522 } 523 524 protected void setConstantState(DrawableContainerState state) 525 { 526 mDrawableContainerState = state; 527 } 528} 529