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 17 package android.graphics.drawable; 18 19 import org.xmlpull.v1.XmlPullParser; 20 import org.xmlpull.v1.XmlPullParserException; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.content.res.ColorStateList; 25 import android.content.res.Resources; 26 import android.content.res.TypedArray; 27 import android.graphics.Bitmap; 28 import android.graphics.Canvas; 29 import android.graphics.ColorFilter; 30 import android.graphics.Insets; 31 import android.graphics.Outline; 32 import android.graphics.PixelFormat; 33 import android.graphics.PorterDuff; 34 import android.graphics.Rect; 35 import android.util.AttributeSet; 36 import android.view.View; 37 38 import java.io.IOException; 39 import java.util.Collection; 40 41 /** 42 * Drawable container with only one child element. 43 */ 44 public abstract class DrawableWrapper extends Drawable implements Drawable.Callback { 45 private DrawableWrapperState mState; 46 private Drawable mDrawable; 47 private boolean mMutated; 48 DrawableWrapper(DrawableWrapperState state, Resources res)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 */ DrawableWrapper(@ullable Drawable dr)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 */ updateLocalState(Resources res)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 */ setDrawable(@ullable Drawable dr)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 getDrawable()111 public Drawable getDrawable() { 112 return mDrawable; 113 } 114 updateStateFromTypedArray(TypedArray a)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 applyTheme(Resources.Theme t)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 canApplyTheme()145 public boolean canApplyTheme() { 146 return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); 147 } 148 149 @Override invalidateDrawable(Drawable who)150 public void invalidateDrawable(Drawable who) { 151 final Callback callback = getCallback(); 152 if (callback != null) { 153 callback.invalidateDrawable(this); 154 } 155 } 156 157 @Override scheduleDrawable(Drawable who, Runnable what, long when)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 unscheduleDrawable(Drawable who, Runnable what)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 draw(@onNull Canvas canvas)174 public void draw(@NonNull Canvas canvas) { 175 if (mDrawable != null) { 176 mDrawable.draw(canvas); 177 } 178 } 179 180 @Override getChangingConfigurations()181 public int getChangingConfigurations() { 182 return super.getChangingConfigurations() 183 | (mState != null ? mState.getChangingConfigurations() : 0) 184 | mDrawable.getChangingConfigurations(); 185 } 186 187 @Override getPadding(@onNull Rect padding)188 public boolean getPadding(@NonNull Rect padding) { 189 return mDrawable != null && mDrawable.getPadding(padding); 190 } 191 192 /** @hide */ 193 @Override getOpticalInsets()194 public Insets getOpticalInsets() { 195 return mDrawable != null ? mDrawable.getOpticalInsets() : Insets.NONE; 196 } 197 198 @Override setHotspot(float x, float y)199 public void setHotspot(float x, float y) { 200 if (mDrawable != null) { 201 mDrawable.setHotspot(x, y); 202 } 203 } 204 205 @Override setHotspotBounds(int left, int top, int right, int bottom)206 public void setHotspotBounds(int left, int top, int right, int bottom) { 207 if (mDrawable != null) { 208 mDrawable.setHotspotBounds(left, top, right, bottom); 209 } 210 } 211 212 @Override getHotspotBounds(@onNull Rect outRect)213 public void getHotspotBounds(@NonNull Rect outRect) { 214 if (mDrawable != null) { 215 mDrawable.getHotspotBounds(outRect); 216 } else { 217 outRect.set(getBounds()); 218 } 219 } 220 221 @Override setVisible(boolean visible, boolean restart)222 public boolean setVisible(boolean visible, boolean restart) { 223 final boolean superChanged = super.setVisible(visible, restart); 224 final boolean changed = mDrawable != null && mDrawable.setVisible(visible, restart); 225 return superChanged | changed; 226 } 227 228 @Override setAlpha(int alpha)229 public void setAlpha(int alpha) { 230 if (mDrawable != null) { 231 mDrawable.setAlpha(alpha); 232 } 233 } 234 235 @Override getAlpha()236 public int getAlpha() { 237 return mDrawable != null ? mDrawable.getAlpha() : 255; 238 } 239 240 @Override setColorFilter(@ullable ColorFilter colorFilter)241 public void setColorFilter(@Nullable ColorFilter colorFilter) { 242 if (mDrawable != null) { 243 mDrawable.setColorFilter(colorFilter); 244 } 245 } 246 247 @Override setTintList(@ullable ColorStateList tint)248 public void setTintList(@Nullable ColorStateList tint) { 249 if (mDrawable != null) { 250 mDrawable.setTintList(tint); 251 } 252 } 253 254 @Override setTintMode(@ullable PorterDuff.Mode tintMode)255 public void setTintMode(@Nullable PorterDuff.Mode tintMode) { 256 if (mDrawable != null) { 257 mDrawable.setTintMode(tintMode); 258 } 259 } 260 261 @Override onLayoutDirectionChanged(@iew.ResolvedLayoutDir int layoutDirection)262 public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) { 263 return mDrawable != null && mDrawable.setLayoutDirection(layoutDirection); 264 } 265 266 @Override getOpacity()267 public int getOpacity() { 268 return mDrawable != null ? mDrawable.getOpacity() : PixelFormat.TRANSPARENT; 269 } 270 271 @Override isStateful()272 public boolean isStateful() { 273 return mDrawable != null && mDrawable.isStateful(); 274 } 275 276 @Override onStateChange(int[] state)277 protected boolean onStateChange(int[] state) { 278 if (mDrawable != null && mDrawable.isStateful()) { 279 final boolean changed = mDrawable.setState(state); 280 if (changed) { 281 onBoundsChange(getBounds()); 282 } 283 return changed; 284 } 285 return false; 286 } 287 288 @Override onLevelChange(int level)289 protected boolean onLevelChange(int level) { 290 return mDrawable != null && mDrawable.setLevel(level); 291 } 292 293 @Override onBoundsChange(@onNull Rect bounds)294 protected void onBoundsChange(@NonNull Rect bounds) { 295 if (mDrawable != null) { 296 mDrawable.setBounds(bounds); 297 } 298 } 299 300 @Override getIntrinsicWidth()301 public int getIntrinsicWidth() { 302 return mDrawable != null ? mDrawable.getIntrinsicWidth() : -1; 303 } 304 305 @Override getIntrinsicHeight()306 public int getIntrinsicHeight() { 307 return mDrawable != null ? mDrawable.getIntrinsicHeight() : -1; 308 } 309 310 @Override getOutline(@onNull Outline outline)311 public void getOutline(@NonNull Outline outline) { 312 if (mDrawable != null) { 313 mDrawable.getOutline(outline); 314 } else { 315 super.getOutline(outline); 316 } 317 } 318 319 @Override 320 @Nullable getConstantState()321 public ConstantState getConstantState() { 322 if (mState != null && mState.canConstantState()) { 323 mState.mChangingConfigurations = getChangingConfigurations(); 324 return mState; 325 } 326 return null; 327 } 328 329 @Override 330 @NonNull mutate()331 public Drawable mutate() { 332 if (!mMutated && super.mutate() == this) { 333 mState = mutateConstantState(); 334 if (mDrawable != null) { 335 mDrawable.mutate(); 336 } 337 if (mState != null) { 338 mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null; 339 } 340 mMutated = true; 341 } 342 return this; 343 } 344 345 /** 346 * Mutates the constant state and returns the new state. Responsible for 347 * updating any local copy. 348 * <p> 349 * This method should never call the super implementation; it should always 350 * mutate and return its own constant state. 351 * 352 * @return the new state 353 */ mutateConstantState()354 DrawableWrapperState mutateConstantState() { 355 return mState; 356 } 357 358 /** 359 * @hide Only used by the framework for pre-loading resources. 360 */ clearMutated()361 public void clearMutated() { 362 super.clearMutated(); 363 if (mDrawable != null) { 364 mDrawable.clearMutated(); 365 } 366 mMutated = false; 367 } 368 369 /** 370 * Called during inflation to inflate the child element. The last valid 371 * child element will take precedence over any other child elements or 372 * explicit drawable attribute. 373 */ inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs, Resources.Theme theme)374 void inflateChildDrawable(Resources r, XmlPullParser parser, AttributeSet attrs, 375 Resources.Theme theme) throws XmlPullParserException, IOException { 376 // Seek to the first child element. 377 Drawable dr = null; 378 int type; 379 final int outerDepth = parser.getDepth(); 380 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 381 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 382 if (type == XmlPullParser.START_TAG) { 383 dr = Drawable.createFromXmlInner(r, parser, attrs, theme); 384 } 385 } 386 387 if (dr != null) { 388 setDrawable(dr); 389 } 390 } 391 392 abstract static class DrawableWrapperState extends Drawable.ConstantState { 393 int[] mThemeAttrs; 394 int mChangingConfigurations; 395 396 Drawable.ConstantState mDrawableState; 397 DrawableWrapperState(DrawableWrapperState orig)398 DrawableWrapperState(DrawableWrapperState orig) { 399 if (orig != null) { 400 mThemeAttrs = orig.mThemeAttrs; 401 mChangingConfigurations = orig.mChangingConfigurations; 402 mDrawableState = orig.mDrawableState; 403 } 404 } 405 406 @Override canApplyTheme()407 public boolean canApplyTheme() { 408 return mThemeAttrs != null 409 || (mDrawableState != null && mDrawableState.canApplyTheme()) 410 || super.canApplyTheme(); 411 } 412 413 @Override addAtlasableBitmaps(Collection<Bitmap> atlasList)414 public int addAtlasableBitmaps(Collection<Bitmap> atlasList) { 415 final Drawable.ConstantState state = mDrawableState; 416 if (state != null) { 417 return state.addAtlasableBitmaps(atlasList); 418 } 419 return 0; 420 } 421 422 @Override newDrawable()423 public Drawable newDrawable() { 424 return newDrawable(null); 425 } 426 427 @Override newDrawable(Resources res)428 public abstract Drawable newDrawable(Resources res); 429 430 @Override getChangingConfigurations()431 public int getChangingConfigurations() { 432 return mChangingConfigurations 433 | (mDrawableState != null ? mDrawableState.getChangingConfigurations() : 0); 434 } 435 canConstantState()436 public boolean canConstantState() { 437 return mDrawableState != null; 438 } 439 } 440 } 441