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