DrawableWrapper.java revision e922f49b627912c113250bd8506830bb69943025
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 */ 16 17package android.graphics.drawable; 18 19import org.xmlpull.v1.XmlPullParser; 20import org.xmlpull.v1.XmlPullParserException; 21 22import android.annotation.NonNull; 23import android.annotation.Nullable; 24import android.content.res.ColorStateList; 25import android.content.res.Resources; 26import android.content.res.TypedArray; 27import android.graphics.Bitmap; 28import android.graphics.Canvas; 29import android.graphics.ColorFilter; 30import android.graphics.Insets; 31import android.graphics.Outline; 32import android.graphics.PixelFormat; 33import android.graphics.PorterDuff; 34import android.graphics.Rect; 35import android.util.AttributeSet; 36import android.view.View; 37 38import java.io.IOException; 39import java.util.Collection; 40 41/** 42 * Drawable container with only one child element. 43 */ 44public abstract class DrawableWrapper extends Drawable implements Drawable.Callback { 45 private DrawableWrapperState mState; 46 private Drawable mDrawable; 47 private boolean mMutated; 48 49 DrawableWrapper(DrawableWrapperState state, Resources res) { 50 mState = state; 51 52 updateLocalState(res); 53 } 54 55 /** 56 * Creates a new wrapper around the specified drawable. 57 * 58 * @param dr the drawable to wrap 59 */ 60 public DrawableWrapper(@Nullable Drawable dr) { 61 mState = null; 62 mDrawable = dr; 63 } 64 65 /** 66 * Initializes local dynamic properties from state. This should be called 67 * after significant state changes, e.g. from the One True Constructor and 68 * after inflating or applying a theme. 69 */ 70 private void updateLocalState(Resources res) { 71 if (mState != null && mState.mDrawableState != null) { 72 final Drawable dr = mState.mDrawableState.newDrawable(res); 73 setDrawable(dr); 74 } 75 } 76 77 /** 78 * Sets the wrapped drawable. 79 * 80 * @param dr the wrapped drawable 81 */ 82 public void setDrawable(@Nullable Drawable dr) { 83 if (mDrawable != null) { 84 mDrawable.setCallback(null); 85 } 86 87 mDrawable = dr; 88 89 if (dr != null) { 90 dr.setCallback(this); 91 92 // Only call setters for data that's stored in the base Drawable. 93 dr.setVisible(isVisible(), true); 94 dr.setState(getState()); 95 dr.setLevel(getLevel()); 96 dr.setBounds(getBounds()); 97 dr.setLayoutDirection(getLayoutDirection()); 98 99 if (mState != null) { 100 mState.mDrawableState = dr.getConstantState(); 101 } 102 } 103 104 invalidateSelf(); 105 } 106 107 /** 108 * @return the wrapped drawable 109 */ 110 @Nullable 111 public Drawable getDrawable() { 112 return mDrawable; 113 } 114 115 void updateStateFromTypedArray(TypedArray a) { 116 final DrawableWrapperState state = mState; 117 if (state == null) { 118 return; 119 } 120 121 // Account for any configuration changes. 122 state.mChangingConfigurations |= a.getChangingConfigurations(); 123 124 // Extract the theme attributes, if any. 125 state.mThemeAttrs = a.extractThemeAttrs(); 126 127 // TODO: Consider using R.styleable.DrawableWrapper_drawable 128 } 129 130 @Override 131 public void applyTheme(Resources.Theme t) { 132 super.applyTheme(t); 133 134 final DrawableWrapperState state = mState; 135 if (state == null) { 136 return; 137 } 138 139 if (mDrawable != null && mDrawable.canApplyTheme()) { 140 mDrawable.applyTheme(t); 141 } 142 } 143 144 @Override 145 public boolean canApplyTheme() { 146 return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); 147 } 148 149 @Override 150 public void invalidateDrawable(Drawable who) { 151 final Callback callback = getCallback(); 152 if (callback != null) { 153 callback.invalidateDrawable(this); 154 } 155 } 156 157 @Override 158 public void scheduleDrawable(Drawable who, Runnable what, long when) { 159 final Callback callback = getCallback(); 160 if (callback != null) { 161 callback.scheduleDrawable(this, what, when); 162 } 163 } 164 165 @Override 166 public void unscheduleDrawable(Drawable who, Runnable what) { 167 final Callback callback = getCallback(); 168 if (callback != null) { 169 callback.unscheduleDrawable(this, what); 170 } 171 } 172 173 @Override 174 public void draw(@NonNull Canvas canvas) { 175 if (mDrawable != null) { 176 mDrawable.draw(canvas); 177 } 178 } 179 180 @Override 181 public int getChangingConfigurations() { 182 return super.getChangingConfigurations() 183 | (mState != null ? mState.getChangingConfigurations() : 0); 184 } 185 186 @Override 187 public boolean getPadding(@NonNull Rect padding) { 188 return mDrawable != null && mDrawable.getPadding(padding); 189 } 190 191 /** @hide */ 192 @Override 193 public Insets getOpticalInsets() { 194 return mDrawable != null ? mDrawable.getOpticalInsets() : Insets.NONE; 195 } 196 197 @Override 198 public void setHotspot(float x, float y) { 199 if (mDrawable != null) { 200 mDrawable.setHotspot(x, y); 201 } 202 } 203 204 @Override 205 public void setHotspotBounds(int left, int top, int right, int bottom) { 206 if (mDrawable != null) { 207 mDrawable.setHotspotBounds(left, top, right, bottom); 208 } 209 } 210 211 @Override 212 public void getHotspotBounds(@NonNull Rect outRect) { 213 if (mDrawable != null) { 214 mDrawable.getHotspotBounds(outRect); 215 } else { 216 outRect.set(getBounds()); 217 } 218 } 219 220 @Override 221 public boolean setVisible(boolean visible, boolean restart) { 222 final boolean superChanged = super.setVisible(visible, restart); 223 final boolean changed = mDrawable != null && mDrawable.setVisible(visible, restart); 224 return superChanged | changed; 225 } 226 227 @Override 228 public void setAlpha(int alpha) { 229 if (mDrawable != null) { 230 mDrawable.setAlpha(alpha); 231 } 232 } 233 234 @Override 235 public int getAlpha() { 236 return mDrawable != null ? mDrawable.getAlpha() : 255; 237 } 238 239 @Override 240 public void setColorFilter(@Nullable ColorFilter colorFilter) { 241 if (mDrawable != null) { 242 mDrawable.setColorFilter(colorFilter); 243 } 244 } 245 246 @Override 247 public void setTintList(@Nullable ColorStateList tint) { 248 if (mDrawable != null) { 249 mDrawable.setTintList(tint); 250 } 251 } 252 253 @Override 254 public void setTintMode(@Nullable PorterDuff.Mode tintMode) { 255 if (mDrawable != null) { 256 mDrawable.setTintMode(tintMode); 257 } 258 } 259 260 @Override 261 public boolean onLayoutDirectionChange(@View.ResolvedLayoutDir int layoutDirection) { 262 return mDrawable != null && mDrawable.setLayoutDirection(layoutDirection); 263 } 264 265 @Override 266 public int getOpacity() { 267 return mDrawable != null ? mDrawable.getOpacity() : PixelFormat.TRANSPARENT; 268 } 269 270 @Override 271 public boolean isStateful() { 272 return mDrawable != null && mDrawable.isStateful(); 273 } 274 275 @Override 276 protected boolean onStateChange(int[] state) { 277 if (mDrawable != null && mDrawable.isStateful()) { 278 final boolean changed = mDrawable.setState(state); 279 if (changed) { 280 onBoundsChange(getBounds()); 281 } 282 return changed; 283 } 284 return false; 285 } 286 287 @Override 288 protected boolean onLevelChange(int level) { 289 return mDrawable != null && mDrawable.setLevel(level); 290 } 291 292 @Override 293 protected void onBoundsChange(@NonNull Rect bounds) { 294 if (mDrawable != null) { 295 mDrawable.setBounds(bounds); 296 } 297 } 298 299 @Override 300 public int getIntrinsicWidth() { 301 return mDrawable != null ? mDrawable.getIntrinsicWidth() : -1; 302 } 303 304 @Override 305 public int getIntrinsicHeight() { 306 return mDrawable != null ? mDrawable.getIntrinsicHeight() : -1; 307 } 308 309 @Override 310 public void getOutline(@NonNull Outline outline) { 311 if (mDrawable != null) { 312 mDrawable.getOutline(outline); 313 } else { 314 super.getOutline(outline); 315 } 316 } 317 318 @Override 319 @Nullable 320 public ConstantState getConstantState() { 321 if (mState != null && mState.canConstantState()) { 322 mState.mChangingConfigurations = getChangingConfigurations(); 323 return mState; 324 } 325 return null; 326 } 327 328 @Override 329 @NonNull 330 public Drawable mutate() { 331 if (!mMutated && super.mutate() == this) { 332 mState = mutateConstantState(); 333 if (mDrawable != null) { 334 mDrawable.mutate(); 335 } 336 if (mState != null) { 337 mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null; 338 } 339 mMutated = true; 340 } 341 return this; 342 } 343 344 /** 345 * Mutates the constant state and returns the new state. Responsible for 346 * updating any local copy. 347 * <p> 348 * This method should never call the super implementation; it should always 349 * mutate and return its own constant state. 350 * 351 * @return the new state 352 */ 353 DrawableWrapperState mutateConstantState() { 354 return mState; 355 } 356 357 /** 358 * @hide Only used by the framework for pre-loading resources. 359 */ 360 public void clearMutated() { 361 super.clearMutated(); 362 if (mDrawable != null) { 363 mDrawable.clearMutated(); 364 } 365 mMutated = false; 366 } 367 368 /** 369 * Called during inflation to inflate the child element. 370 */ 371 void inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs, 372 Resources.Theme theme) throws XmlPullParserException, IOException { 373 // Drawable specified on the root element takes precedence. 374 if (getDrawable() != null) { 375 return; 376 } 377 378 // Seek to the first child element. 379 Drawable dr = null; 380 int type; 381 final int outerDepth = parser.getDepth(); 382 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 383 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 384 if (type == XmlPullParser.START_TAG) { 385 dr = Drawable.createFromXmlInner(r, parser, attrs, theme); 386 break; 387 } 388 } 389 390 if (dr != null) { 391 setDrawable(dr); 392 } 393 } 394 395 abstract static class DrawableWrapperState extends Drawable.ConstantState { 396 int[] mThemeAttrs; 397 int mChangingConfigurations; 398 399 Drawable.ConstantState mDrawableState; 400 401 DrawableWrapperState(DrawableWrapperState orig) { 402 if (orig != null) { 403 mThemeAttrs = orig.mThemeAttrs; 404 mChangingConfigurations = orig.mChangingConfigurations; 405 mDrawableState = orig.mDrawableState; 406 } 407 } 408 409 @Override 410 public boolean canApplyTheme() { 411 return mThemeAttrs != null 412 || (mDrawableState != null && mDrawableState.canApplyTheme()) 413 || super.canApplyTheme(); 414 } 415 416 @Override 417 public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { 418 final Drawable.ConstantState state = mDrawableState; 419 if (state != null) { 420 return state.addAtlasableBitmaps(atlasList); 421 } 422 return 0; 423 } 424 425 @Override 426 public Drawable newDrawable() { 427 return newDrawable(null); 428 } 429 430 @Override 431 public abstract Drawable newDrawable(Resources res); 432 433 @Override 434 public int getChangingConfigurations() { 435 return mChangingConfigurations 436 | (mDrawableState != null ? mDrawableState.getChangingConfigurations() : 0); 437 } 438 439 public boolean canConstantState() { 440 return mDrawableState != null; 441 } 442 } 443} 444