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