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.support.v4.graphics.drawable; 18 19import android.content.res.ColorStateList; 20import android.content.res.Resources; 21import android.graphics.Canvas; 22import android.graphics.ColorFilter; 23import android.graphics.PorterDuff; 24import android.graphics.Rect; 25import android.graphics.Region; 26import android.graphics.drawable.Drawable; 27import android.support.annotation.NonNull; 28import android.support.annotation.Nullable; 29 30/** 31 * Drawable which delegates all calls to it's wrapped {@link android.graphics.drawable.Drawable}. 32 * <p> 33 * Also allows backward compatible tinting via a color or {@link ColorStateList}. 34 * This functionality is accessed via static methods in {@code DrawableCompat}. 35 */ 36class DrawableWrapperDonut extends Drawable 37 implements Drawable.Callback, DrawableWrapper, TintAwareDrawable { 38 39 static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN; 40 41 private int mCurrentColor; 42 private PorterDuff.Mode mCurrentMode; 43 private boolean mColorFilterSet; 44 45 DrawableWrapperState mState; 46 private boolean mMutated; 47 48 Drawable mDrawable; 49 50 DrawableWrapperDonut(@NonNull DrawableWrapperState state, @Nullable Resources res) { 51 mState = state; 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 DrawableWrapperDonut(@Nullable Drawable dr) { 61 mState = mutateConstantState(); 62 // Now set the drawable... 63 setWrappedDrawable(dr); 64 } 65 66 /** 67 * Initializes local dynamic properties from state. This should be called 68 * after significant state changes, e.g. from the One True Constructor and 69 * after inflating or applying a theme. 70 */ 71 private void updateLocalState(@Nullable Resources res) { 72 if (mState != null && mState.mDrawableState != null) { 73 final Drawable dr = newDrawableFromState(mState.mDrawableState, res); 74 setWrappedDrawable(dr); 75 } 76 } 77 78 /** 79 * Allows us to call ConstantState.newDrawable(*) is a API safe way 80 */ 81 protected Drawable newDrawableFromState(@NonNull Drawable.ConstantState state, 82 @Nullable Resources res) { 83 return state.newDrawable(); 84 } 85 86 @Override 87 public void draw(Canvas canvas) { 88 mDrawable.draw(canvas); 89 } 90 91 @Override 92 protected void onBoundsChange(Rect bounds) { 93 if (mDrawable != null) { 94 mDrawable.setBounds(bounds); 95 } 96 } 97 98 @Override 99 public void setChangingConfigurations(int configs) { 100 mDrawable.setChangingConfigurations(configs); 101 } 102 103 @Override 104 public int getChangingConfigurations() { 105 return super.getChangingConfigurations() 106 | (mState != null ? mState.getChangingConfigurations() : 0) 107 | mDrawable.getChangingConfigurations(); 108 } 109 110 @Override 111 public void setDither(boolean dither) { 112 mDrawable.setDither(dither); 113 } 114 115 @Override 116 public void setFilterBitmap(boolean filter) { 117 mDrawable.setFilterBitmap(filter); 118 } 119 120 @Override 121 public void setAlpha(int alpha) { 122 mDrawable.setAlpha(alpha); 123 } 124 125 @Override 126 public void setColorFilter(ColorFilter cf) { 127 mDrawable.setColorFilter(cf); 128 } 129 130 @Override 131 public boolean isStateful() { 132 final ColorStateList tintList = (isCompatTintEnabled() && mState != null) 133 ? mState.mTint 134 : null; 135 return (tintList != null && tintList.isStateful()) || mDrawable.isStateful(); 136 } 137 138 @Override 139 public boolean setState(final int[] stateSet) { 140 boolean handled = mDrawable.setState(stateSet); 141 handled = updateTint(stateSet) || handled; 142 return handled; 143 } 144 145 @Override 146 public int[] getState() { 147 return mDrawable.getState(); 148 } 149 150 @Override 151 public Drawable getCurrent() { 152 return mDrawable.getCurrent(); 153 } 154 155 @Override 156 public boolean setVisible(boolean visible, boolean restart) { 157 return super.setVisible(visible, restart) || mDrawable.setVisible(visible, restart); 158 } 159 160 @Override 161 public int getOpacity() { 162 return mDrawable.getOpacity(); 163 } 164 165 @Override 166 public Region getTransparentRegion() { 167 return mDrawable.getTransparentRegion(); 168 } 169 170 @Override 171 public int getIntrinsicWidth() { 172 return mDrawable.getIntrinsicWidth(); 173 } 174 175 @Override 176 public int getIntrinsicHeight() { 177 return mDrawable.getIntrinsicHeight(); 178 } 179 180 @Override 181 public int getMinimumWidth() { 182 return mDrawable.getMinimumWidth(); 183 } 184 185 @Override 186 public int getMinimumHeight() { 187 return mDrawable.getMinimumHeight(); 188 } 189 190 @Override 191 public boolean getPadding(Rect padding) { 192 return mDrawable.getPadding(padding); 193 } 194 195 @Override 196 @Nullable 197 public ConstantState getConstantState() { 198 if (mState != null && mState.canConstantState()) { 199 mState.mChangingConfigurations = getChangingConfigurations(); 200 return mState; 201 } 202 return null; 203 } 204 205 @Override 206 public Drawable mutate() { 207 if (!mMutated && super.mutate() == this) { 208 mState = mutateConstantState(); 209 if (mDrawable != null) { 210 mDrawable.mutate(); 211 } 212 if (mState != null) { 213 mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null; 214 } 215 mMutated = true; 216 } 217 return this; 218 } 219 220 /** 221 * Mutates the constant state and returns the new state. 222 * <p> 223 * This method should never call the super implementation; it should always 224 * mutate and return its own constant state. 225 * 226 * @return the new state 227 */ 228 @NonNull 229 DrawableWrapperState mutateConstantState() { 230 return new DrawableWrapperStateDonut(mState, null); 231 } 232 233 /** 234 * {@inheritDoc} 235 */ 236 public void invalidateDrawable(Drawable who) { 237 invalidateSelf(); 238 } 239 240 /** 241 * {@inheritDoc} 242 */ 243 public void scheduleDrawable(Drawable who, Runnable what, long when) { 244 scheduleSelf(what, when); 245 } 246 247 /** 248 * {@inheritDoc} 249 */ 250 public void unscheduleDrawable(Drawable who, Runnable what) { 251 unscheduleSelf(what); 252 } 253 254 @Override 255 protected boolean onLevelChange(int level) { 256 return mDrawable.setLevel(level); 257 } 258 259 @Override 260 public void setTint(int tint) { 261 setTintList(ColorStateList.valueOf(tint)); 262 } 263 264 @Override 265 public void setTintList(ColorStateList tint) { 266 mState.mTint = tint; 267 updateTint(getState()); 268 } 269 270 @Override 271 public void setTintMode(PorterDuff.Mode tintMode) { 272 mState.mTintMode = tintMode; 273 updateTint(getState()); 274 } 275 276 private boolean updateTint(int[] state) { 277 if (!isCompatTintEnabled()) { 278 // If compat tinting is not enabled, fail fast 279 return false; 280 } 281 282 final ColorStateList tintList = mState.mTint; 283 final PorterDuff.Mode tintMode = mState.mTintMode; 284 285 if (tintList != null && tintMode != null) { 286 final int color = tintList.getColorForState(state, tintList.getDefaultColor()); 287 if (!mColorFilterSet || color != mCurrentColor || tintMode != mCurrentMode) { 288 setColorFilter(color, tintMode); 289 mCurrentColor = color; 290 mCurrentMode = tintMode; 291 mColorFilterSet = true; 292 return true; 293 } 294 } else { 295 mColorFilterSet = false; 296 clearColorFilter(); 297 } 298 return false; 299 } 300 301 /** 302 * Returns the wrapped {@link Drawable} 303 */ 304 public final Drawable getWrappedDrawable() { 305 return mDrawable; 306 } 307 308 /** 309 * Sets the current wrapped {@link Drawable} 310 */ 311 public final void setWrappedDrawable(Drawable dr) { 312 if (mDrawable != null) { 313 mDrawable.setCallback(null); 314 } 315 316 mDrawable = dr; 317 318 if (dr != null) { 319 dr.setCallback(this); 320 // Only call setters for data that's stored in the base Drawable. 321 dr.setVisible(isVisible(), true); 322 dr.setState(getState()); 323 dr.setLevel(getLevel()); 324 dr.setBounds(getBounds()); 325 if (mState != null) { 326 mState.mDrawableState = dr.getConstantState(); 327 } 328 } 329 330 invalidateSelf(); 331 } 332 333 protected boolean isCompatTintEnabled() { 334 // It's enabled by default on Donut 335 return true; 336 } 337 338 protected static abstract class DrawableWrapperState extends Drawable.ConstantState { 339 int mChangingConfigurations; 340 Drawable.ConstantState mDrawableState; 341 342 ColorStateList mTint = null; 343 PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE; 344 345 DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) { 346 if (orig != null) { 347 mChangingConfigurations = orig.mChangingConfigurations; 348 mDrawableState = orig.mDrawableState; 349 mTint = orig.mTint; 350 mTintMode = orig.mTintMode; 351 } 352 } 353 354 @Override 355 public Drawable newDrawable() { 356 return newDrawable(null); 357 } 358 359 public abstract Drawable newDrawable(@Nullable Resources res); 360 361 @Override 362 public int getChangingConfigurations() { 363 return mChangingConfigurations 364 | (mDrawableState != null ? mDrawableState.getChangingConfigurations() : 0); 365 } 366 367 boolean canConstantState() { 368 return mDrawableState != null; 369 } 370 } 371 372 private static class DrawableWrapperStateDonut extends DrawableWrapperState { 373 DrawableWrapperStateDonut( 374 @Nullable DrawableWrapperState orig, @Nullable Resources res) { 375 super(orig, res); 376 } 377 378 @Override 379 public Drawable newDrawable(@Nullable Resources res) { 380 return new DrawableWrapperDonut(this, res); 381 } 382 } 383} 384