1/* 2 * Copyright (C) 2013 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.ColorFilter; 22import android.graphics.PorterDuff; 23import android.graphics.drawable.Drawable; 24import android.support.annotation.ColorInt; 25import android.support.annotation.NonNull; 26import android.support.annotation.Nullable; 27import android.support.v4.view.ViewCompat; 28import android.util.AttributeSet; 29import org.xmlpull.v1.XmlPullParser; 30import org.xmlpull.v1.XmlPullParserException; 31 32import java.io.IOException; 33 34/** 35 * Helper for accessing features in {@link android.graphics.drawable.Drawable} 36 * introduced after API level 4 in a backwards compatible fashion. 37 */ 38public final class DrawableCompat { 39 /** 40 * Interface for the full API. 41 */ 42 interface DrawableImpl { 43 void jumpToCurrentState(Drawable drawable); 44 void setAutoMirrored(Drawable drawable, boolean mirrored); 45 boolean isAutoMirrored(Drawable drawable); 46 void setHotspot(Drawable drawable, float x, float y); 47 void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom); 48 void setTint(Drawable drawable, int tint); 49 void setTintList(Drawable drawable, ColorStateList tint); 50 void setTintMode(Drawable drawable, PorterDuff.Mode tintMode); 51 Drawable wrap(Drawable drawable); 52 boolean setLayoutDirection(Drawable drawable, int layoutDirection); 53 int getLayoutDirection(Drawable drawable); 54 int getAlpha(Drawable drawable); 55 void applyTheme(Drawable drawable, Resources.Theme t); 56 boolean canApplyTheme(Drawable drawable); 57 ColorFilter getColorFilter(Drawable drawable); 58 void inflate(Drawable drawable, Resources res, XmlPullParser parser, AttributeSet attrs, 59 Resources.Theme t) throws IOException, XmlPullParserException; 60 } 61 62 /** 63 * Interface implementation that doesn't use anything about v4 APIs. 64 */ 65 static class BaseDrawableImpl implements DrawableImpl { 66 @Override 67 public void jumpToCurrentState(Drawable drawable) { 68 } 69 70 @Override 71 public void setAutoMirrored(Drawable drawable, boolean mirrored) { 72 } 73 74 @Override 75 public boolean isAutoMirrored(Drawable drawable) { 76 return false; 77 } 78 79 @Override 80 public void setHotspot(Drawable drawable, float x, float y) { 81 } 82 83 @Override 84 public void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom) { 85 } 86 87 @Override 88 public void setTint(Drawable drawable, int tint) { 89 DrawableCompatBase.setTint(drawable, tint); 90 } 91 92 @Override 93 public void setTintList(Drawable drawable, ColorStateList tint) { 94 DrawableCompatBase.setTintList(drawable, tint); 95 } 96 97 @Override 98 public void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) { 99 DrawableCompatBase.setTintMode(drawable, tintMode); 100 } 101 102 @Override 103 public Drawable wrap(Drawable drawable) { 104 return DrawableCompatBase.wrapForTinting(drawable); 105 } 106 107 @Override 108 public boolean setLayoutDirection(Drawable drawable, int layoutDirection) { 109 // No op for API < 23 110 return false; 111 } 112 113 @Override 114 public int getLayoutDirection(Drawable drawable) { 115 return ViewCompat.LAYOUT_DIRECTION_LTR; 116 } 117 118 @Override 119 public int getAlpha(Drawable drawable) { 120 return 0; 121 } 122 123 @Override 124 public void applyTheme(Drawable drawable, Resources.Theme t) { 125 } 126 127 @Override 128 public boolean canApplyTheme(Drawable drawable) { 129 return false; 130 } 131 132 @Override 133 public ColorFilter getColorFilter(Drawable drawable) { 134 return null; 135 } 136 137 @Override 138 public void inflate(Drawable drawable, Resources res, XmlPullParser parser, 139 AttributeSet attrs, Resources.Theme t) 140 throws IOException, XmlPullParserException { 141 DrawableCompatBase.inflate(drawable, res, parser, attrs, t); 142 } 143 } 144 145 /** 146 * Interface implementation for devices with at least v5 APIs. 147 */ 148 static class EclairDrawableImpl extends BaseDrawableImpl { 149 @Override 150 public Drawable wrap(Drawable drawable) { 151 return DrawableCompatEclair.wrapForTinting(drawable); 152 } 153 } 154 155 /** 156 * Interface implementation for devices with at least v11 APIs. 157 */ 158 static class HoneycombDrawableImpl extends EclairDrawableImpl { 159 @Override 160 public void jumpToCurrentState(Drawable drawable) { 161 DrawableCompatHoneycomb.jumpToCurrentState(drawable); 162 } 163 164 @Override 165 public Drawable wrap(Drawable drawable) { 166 return DrawableCompatHoneycomb.wrapForTinting(drawable); 167 } 168 } 169 170 static class JellybeanMr1DrawableImpl extends HoneycombDrawableImpl { 171 @Override 172 public boolean setLayoutDirection(Drawable drawable, int layoutDirection) { 173 return DrawableCompatJellybeanMr1.setLayoutDirection(drawable, layoutDirection); 174 } 175 176 @Override 177 public int getLayoutDirection(Drawable drawable) { 178 final int dir = DrawableCompatJellybeanMr1.getLayoutDirection(drawable); 179 return dir >= 0 ? dir : ViewCompat.LAYOUT_DIRECTION_LTR; 180 } 181 } 182 183 /** 184 * Interface implementation for devices with at least KitKat APIs. 185 */ 186 static class KitKatDrawableImpl extends JellybeanMr1DrawableImpl { 187 @Override 188 public void setAutoMirrored(Drawable drawable, boolean mirrored) { 189 DrawableCompatKitKat.setAutoMirrored(drawable, mirrored); 190 } 191 192 @Override 193 public boolean isAutoMirrored(Drawable drawable) { 194 return DrawableCompatKitKat.isAutoMirrored(drawable); 195 } 196 197 @Override 198 public Drawable wrap(Drawable drawable) { 199 return DrawableCompatKitKat.wrapForTinting(drawable); 200 } 201 202 @Override 203 public int getAlpha(Drawable drawable) { 204 return DrawableCompatKitKat.getAlpha(drawable); 205 } 206 } 207 208 /** 209 * Interface implementation for devices with at least L APIs. 210 */ 211 static class LollipopDrawableImpl extends KitKatDrawableImpl { 212 @Override 213 public void setHotspot(Drawable drawable, float x, float y) { 214 DrawableCompatLollipop.setHotspot(drawable, x, y); 215 } 216 217 @Override 218 public void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom) { 219 DrawableCompatLollipop.setHotspotBounds(drawable, left, top, right, bottom); 220 } 221 222 @Override 223 public void setTint(Drawable drawable, int tint) { 224 DrawableCompatLollipop.setTint(drawable, tint); 225 } 226 227 @Override 228 public void setTintList(Drawable drawable, ColorStateList tint) { 229 DrawableCompatLollipop.setTintList(drawable, tint); 230 } 231 232 @Override 233 public void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) { 234 DrawableCompatLollipop.setTintMode(drawable, tintMode); 235 } 236 237 @Override 238 public Drawable wrap(Drawable drawable) { 239 return DrawableCompatLollipop.wrapForTinting(drawable); 240 } 241 242 @Override 243 public void applyTheme(Drawable drawable, Resources.Theme t) { 244 DrawableCompatLollipop.applyTheme(drawable, t); 245 } 246 247 @Override 248 public boolean canApplyTheme(Drawable drawable) { 249 return DrawableCompatLollipop.canApplyTheme(drawable); 250 } 251 252 @Override 253 public ColorFilter getColorFilter(Drawable drawable) { 254 return DrawableCompatLollipop.getColorFilter(drawable); 255 } 256 257 @Override 258 public void inflate(Drawable drawable, Resources res, XmlPullParser parser, 259 AttributeSet attrs, Resources.Theme t) 260 throws IOException, XmlPullParserException { 261 DrawableCompatLollipop.inflate(drawable, res, parser, attrs, t); 262 } 263 } 264 265 /** 266 * Interface implementation for devices with at least M APIs. 267 */ 268 static class MDrawableImpl extends LollipopDrawableImpl { 269 @Override 270 public boolean setLayoutDirection(Drawable drawable, int layoutDirection) { 271 return DrawableCompatApi23.setLayoutDirection(drawable, layoutDirection); 272 } 273 274 @Override 275 public int getLayoutDirection(Drawable drawable) { 276 return DrawableCompatApi23.getLayoutDirection(drawable); 277 } 278 279 @Override 280 public Drawable wrap(Drawable drawable) { 281 // No need to wrap on M+ 282 return drawable; 283 } 284 } 285 286 /** 287 * Select the correct implementation to use for the current platform. 288 */ 289 static final DrawableImpl IMPL; 290 static { 291 final int version = android.os.Build.VERSION.SDK_INT; 292 if (version >= 23) { 293 IMPL = new MDrawableImpl(); 294 } else if (version >= 21) { 295 IMPL = new LollipopDrawableImpl(); 296 } else if (version >= 19) { 297 IMPL = new KitKatDrawableImpl(); 298 } else if (version >= 17) { 299 IMPL = new JellybeanMr1DrawableImpl(); 300 } else if (version >= 11) { 301 IMPL = new HoneycombDrawableImpl(); 302 } else if (version >= 5) { 303 IMPL = new EclairDrawableImpl(); 304 } else { 305 IMPL = new BaseDrawableImpl(); 306 } 307 } 308 309 /** 310 * Call {@link Drawable#jumpToCurrentState() Drawable.jumpToCurrentState()}. 311 * <p> 312 * If running on a pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} 313 * device this method does nothing. 314 * 315 * @param drawable The Drawable against which to invoke the method. 316 */ 317 public static void jumpToCurrentState(@NonNull Drawable drawable) { 318 IMPL.jumpToCurrentState(drawable); 319 } 320 321 /** 322 * Set whether this Drawable is automatically mirrored when its layout 323 * direction is RTL (right-to left). See 324 * {@link android.util.LayoutDirection}. 325 * <p> 326 * If running on a pre-{@link android.os.Build.VERSION_CODES#KITKAT} device 327 * this method does nothing. 328 * 329 * @param drawable The Drawable against which to invoke the method. 330 * @param mirrored Set to true if the Drawable should be mirrored, false if 331 * not. 332 */ 333 public static void setAutoMirrored(@NonNull Drawable drawable, boolean mirrored) { 334 IMPL.setAutoMirrored(drawable, mirrored); 335 } 336 337 /** 338 * Tells if this Drawable will be automatically mirrored when its layout 339 * direction is RTL right-to-left. See {@link android.util.LayoutDirection}. 340 * <p> 341 * If running on a pre-{@link android.os.Build.VERSION_CODES#KITKAT} device 342 * this method returns false. 343 * 344 * @param drawable The Drawable against which to invoke the method. 345 * @return boolean Returns true if this Drawable will be automatically 346 * mirrored. 347 */ 348 public static boolean isAutoMirrored(@NonNull Drawable drawable) { 349 return IMPL.isAutoMirrored(drawable); 350 } 351 352 /** 353 * Specifies the hotspot's location within the drawable. 354 * 355 * @param drawable The Drawable against which to invoke the method. 356 * @param x The X coordinate of the center of the hotspot 357 * @param y The Y coordinate of the center of the hotspot 358 */ 359 public static void setHotspot(@NonNull Drawable drawable, float x, float y) { 360 IMPL.setHotspot(drawable, x, y); 361 } 362 363 /** 364 * Sets the bounds to which the hotspot is constrained, if they should be 365 * different from the drawable bounds. 366 * 367 * @param drawable The Drawable against which to invoke the method. 368 */ 369 public static void setHotspotBounds(@NonNull Drawable drawable, int left, int top, 370 int right, int bottom) { 371 IMPL.setHotspotBounds(drawable, left, top, right, bottom); 372 } 373 374 /** 375 * Specifies a tint for {@code drawable}. 376 * 377 * @param drawable The Drawable against which to invoke the method. 378 * @param tint Color to use for tinting this drawable 379 */ 380 public static void setTint(@NonNull Drawable drawable, @ColorInt int tint) { 381 IMPL.setTint(drawable, tint); 382 } 383 384 /** 385 * Specifies a tint for {@code drawable} as a color state list. 386 * 387 * @param drawable The Drawable against which to invoke the method. 388 * @param tint Color state list to use for tinting this drawable, or null to clear the tint 389 */ 390 public static void setTintList(@NonNull Drawable drawable, @Nullable ColorStateList tint) { 391 IMPL.setTintList(drawable, tint); 392 } 393 394 /** 395 * Specifies a tint blending mode for {@code drawable}. 396 * 397 * @param drawable The Drawable against which to invoke the method. 398 * @param tintMode A Porter-Duff blending mode 399 */ 400 public static void setTintMode(@NonNull Drawable drawable, @Nullable PorterDuff.Mode tintMode) { 401 IMPL.setTintMode(drawable, tintMode); 402 } 403 404 /** 405 * Get the alpha value of the {@code drawable}. 406 * 0 means fully transparent, 255 means fully opaque. 407 * 408 * @param drawable The Drawable against which to invoke the method. 409 */ 410 public static int getAlpha(@NonNull Drawable drawable) { 411 return IMPL.getAlpha(drawable); 412 } 413 414 /** 415 * Applies the specified theme to this Drawable and its children. 416 */ 417 public static void applyTheme(Drawable drawable, Resources.Theme t) { 418 IMPL.applyTheme(drawable, t); 419 } 420 421 /** 422 * Whether a theme can be applied to this Drawable and its children. 423 */ 424 public static boolean canApplyTheme(Drawable drawable) { 425 return IMPL.canApplyTheme(drawable); 426 } 427 428 /** 429 * Returns the current color filter, or {@code null} if none set. 430 * 431 * @return the current color filter, or {@code null} if none set 432 */ 433 public static ColorFilter getColorFilter(Drawable drawable) { 434 return IMPL.getColorFilter(drawable); 435 } 436 437 /** 438 * Inflate this Drawable from an XML resource optionally styled by a theme. 439 * 440 * @param res Resources used to resolve attribute values 441 * @param parser XML parser from which to inflate this Drawable 442 * @param attrs Base set of attribute values 443 * @param theme Theme to apply, may be null 444 * @throws XmlPullParserException 445 * @throws IOException 446 */ 447 public static void inflate(Drawable drawable, Resources res, XmlPullParser parser, 448 AttributeSet attrs, Resources.Theme theme) 449 throws XmlPullParserException, IOException { 450 IMPL.inflate(drawable, res, parser, attrs, theme); 451 } 452 453 /** 454 * Potentially wrap {@code drawable} so that it may be used for tinting across the 455 * different API levels, via the tinting methods in this class. 456 * 457 * <p>You must use the result of this call. If the given drawable is being used by a view 458 * (as it's background for instance), you must replace the original drawable with 459 * the result of this call:</p> 460 * 461 * <pre> 462 * Drawable bg = DrawableCompat.wrap(view.getBackground()); 463 * // Need to set the background with the wrapped drawable 464 * view.setBackground(bg); 465 * 466 * // You can now tint the drawable 467 * DrawableCompat.setTint(bg, ...); 468 * </pre> 469 * 470 * <p>If you need to get hold of the original {@link android.graphics.drawable.Drawable} again, 471 * you can use the value returned from {@link #unwrap(Drawable)}.</p> 472 * 473 * @param drawable The Drawable to process 474 * @return A drawable capable of being tinted across all API levels. 475 * 476 * @see #setTint(Drawable, int) 477 * @see #setTintList(Drawable, ColorStateList) 478 * @see #setTintMode(Drawable, PorterDuff.Mode) 479 * @see #unwrap(Drawable) 480 */ 481 public static Drawable wrap(@NonNull Drawable drawable) { 482 return IMPL.wrap(drawable); 483 } 484 485 /** 486 * Unwrap {@code drawable} if it is the result of a call to {@link #wrap(Drawable)}. If 487 * the {@code drawable} is not the result of a call to {@link #wrap(Drawable)} then 488 * {@code drawable} is returned as-is. 489 * 490 * @param drawable The drawable to unwrap 491 * @return the unwrapped {@link Drawable} or {@code drawable} if it hasn't been wrapped. 492 * 493 * @see #wrap(Drawable) 494 */ 495 public static <T extends Drawable> T unwrap(@NonNull Drawable drawable) { 496 if (drawable instanceof DrawableWrapper) { 497 return (T) ((DrawableWrapper) drawable).getWrappedDrawable(); 498 } 499 return (T) drawable; 500 } 501 502 /** 503 * Set the layout direction for this drawable. Should be a resolved 504 * layout direction, as the Drawable has no capacity to do the resolution on 505 * its own. 506 * 507 * @param layoutDirection the resolved layout direction for the drawable, 508 * either {@link ViewCompat#LAYOUT_DIRECTION_LTR} 509 * or {@link ViewCompat#LAYOUT_DIRECTION_RTL} 510 * @return {@code true} if the layout direction change has caused the 511 * appearance of the drawable to change such that it needs to be 512 * re-drawn, {@code false} otherwise 513 * @see #getLayoutDirection(Drawable) 514 */ 515 public static boolean setLayoutDirection(@NonNull Drawable drawable, int layoutDirection) { 516 return IMPL.setLayoutDirection(drawable, layoutDirection); 517 } 518 519 /** 520 * Returns the resolved layout direction for this Drawable. 521 * 522 * @return One of {@link ViewCompat#LAYOUT_DIRECTION_LTR}, 523 * {@link ViewCompat#LAYOUT_DIRECTION_RTL} 524 * @see #setLayoutDirection(Drawable, int) 525 */ 526 public static int getLayoutDirection(@NonNull Drawable drawable) { 527 return IMPL.getLayoutDirection(drawable); 528 } 529 530 private DrawableCompat() {} 531} 532