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 17 package android.support.v4.graphics.drawable; 18 19 import android.content.res.ColorStateList; 20 import android.graphics.PorterDuff; 21 import android.graphics.drawable.Drawable; 22 import android.support.v4.view.ViewCompat; 23 24 /** 25 * Helper for accessing features in {@link android.graphics.drawable.Drawable} 26 * introduced after API level 4 in a backwards compatible fashion. 27 */ 28 public class DrawableCompat { 29 /** 30 * Interface for the full API. 31 */ 32 interface DrawableImpl { jumpToCurrentState(Drawable drawable)33 void jumpToCurrentState(Drawable drawable); setAutoMirrored(Drawable drawable, boolean mirrored)34 void setAutoMirrored(Drawable drawable, boolean mirrored); isAutoMirrored(Drawable drawable)35 boolean isAutoMirrored(Drawable drawable); setHotspot(Drawable drawable, float x, float y)36 void setHotspot(Drawable drawable, float x, float y); setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom)37 void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom); setTint(Drawable drawable, int tint)38 void setTint(Drawable drawable, int tint); setTintList(Drawable drawable, ColorStateList tint)39 void setTintList(Drawable drawable, ColorStateList tint); setTintMode(Drawable drawable, PorterDuff.Mode tintMode)40 void setTintMode(Drawable drawable, PorterDuff.Mode tintMode); wrap(Drawable drawable)41 Drawable wrap(Drawable drawable); setLayoutDirection(Drawable drawable, int layoutDirection)42 void setLayoutDirection(Drawable drawable, int layoutDirection); getLayoutDirection(Drawable drawable)43 int getLayoutDirection(Drawable drawable); 44 } 45 46 /** 47 * Interface implementation that doesn't use anything about v4 APIs. 48 */ 49 static class BaseDrawableImpl implements DrawableImpl { 50 @Override jumpToCurrentState(Drawable drawable)51 public void jumpToCurrentState(Drawable drawable) { 52 } 53 54 @Override setAutoMirrored(Drawable drawable, boolean mirrored)55 public void setAutoMirrored(Drawable drawable, boolean mirrored) { 56 } 57 58 @Override isAutoMirrored(Drawable drawable)59 public boolean isAutoMirrored(Drawable drawable) { 60 return false; 61 } 62 63 @Override setHotspot(Drawable drawable, float x, float y)64 public void setHotspot(Drawable drawable, float x, float y) { 65 } 66 67 @Override setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom)68 public void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom) { 69 } 70 71 @Override setTint(Drawable drawable, int tint)72 public void setTint(Drawable drawable, int tint) { 73 DrawableCompatBase.setTint(drawable, tint); 74 } 75 76 @Override setTintList(Drawable drawable, ColorStateList tint)77 public void setTintList(Drawable drawable, ColorStateList tint) { 78 DrawableCompatBase.setTintList(drawable, tint); 79 } 80 81 @Override setTintMode(Drawable drawable, PorterDuff.Mode tintMode)82 public void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) { 83 DrawableCompatBase.setTintMode(drawable, tintMode); 84 } 85 86 @Override wrap(Drawable drawable)87 public Drawable wrap(Drawable drawable) { 88 return DrawableCompatBase.wrapForTinting(drawable); 89 } 90 91 @Override setLayoutDirection(Drawable drawable, int layoutDirection)92 public void setLayoutDirection(Drawable drawable, int layoutDirection) { 93 // No op for API < 23 94 } 95 96 @Override getLayoutDirection(Drawable drawable)97 public int getLayoutDirection(Drawable drawable) { 98 return ViewCompat.LAYOUT_DIRECTION_LTR; 99 } 100 } 101 102 /** 103 * Interface implementation for devices with at least v11 APIs. 104 */ 105 static class HoneycombDrawableImpl extends BaseDrawableImpl { 106 @Override jumpToCurrentState(Drawable drawable)107 public void jumpToCurrentState(Drawable drawable) { 108 DrawableCompatHoneycomb.jumpToCurrentState(drawable); 109 } 110 111 @Override wrap(Drawable drawable)112 public Drawable wrap(Drawable drawable) { 113 return DrawableCompatHoneycomb.wrapForTinting(drawable); 114 } 115 } 116 117 static class JellybeanMr1DrawableImpl extends HoneycombDrawableImpl { 118 @Override setLayoutDirection(Drawable drawable, int layoutDirection)119 public void setLayoutDirection(Drawable drawable, int layoutDirection) { 120 DrawableCompatJellybeanMr1.setLayoutDirection(drawable, layoutDirection); 121 } 122 123 @Override getLayoutDirection(Drawable drawable)124 public int getLayoutDirection(Drawable drawable) { 125 final int dir = DrawableCompatJellybeanMr1.getLayoutDirection(drawable); 126 return dir < 0 ? dir : ViewCompat.LAYOUT_DIRECTION_LTR; 127 } 128 } 129 130 /** 131 * Interface implementation for devices with at least KitKat APIs. 132 */ 133 static class KitKatDrawableImpl extends JellybeanMr1DrawableImpl { 134 @Override setAutoMirrored(Drawable drawable, boolean mirrored)135 public void setAutoMirrored(Drawable drawable, boolean mirrored) { 136 DrawableCompatKitKat.setAutoMirrored(drawable, mirrored); 137 } 138 139 @Override isAutoMirrored(Drawable drawable)140 public boolean isAutoMirrored(Drawable drawable) { 141 return DrawableCompatKitKat.isAutoMirrored(drawable); 142 } 143 144 @Override wrap(Drawable drawable)145 public Drawable wrap(Drawable drawable) { 146 return DrawableCompatKitKat.wrapForTinting(drawable); 147 } 148 } 149 150 /** 151 * Interface implementation for devices with at least L APIs. 152 */ 153 static class LollipopDrawableImpl extends KitKatDrawableImpl { 154 @Override setHotspot(Drawable drawable, float x, float y)155 public void setHotspot(Drawable drawable, float x, float y) { 156 DrawableCompatLollipop.setHotspot(drawable, x, y); 157 } 158 159 @Override setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom)160 public void setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom) { 161 DrawableCompatLollipop.setHotspotBounds(drawable, left, top, right, bottom); 162 } 163 164 @Override setTint(Drawable drawable, int tint)165 public void setTint(Drawable drawable, int tint) { 166 DrawableCompatLollipop.setTint(drawable, tint); 167 } 168 169 @Override setTintList(Drawable drawable, ColorStateList tint)170 public void setTintList(Drawable drawable, ColorStateList tint) { 171 DrawableCompatLollipop.setTintList(drawable, tint); 172 } 173 174 @Override setTintMode(Drawable drawable, PorterDuff.Mode tintMode)175 public void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) { 176 DrawableCompatLollipop.setTintMode(drawable, tintMode); 177 } 178 179 @Override wrap(Drawable drawable)180 public Drawable wrap(Drawable drawable) { 181 return DrawableCompatLollipop.wrapForTinting(drawable); 182 } 183 } 184 185 /** 186 * Interface implementation for devices with at least L APIs. 187 */ 188 static class LollipopMr1DrawableImpl extends LollipopDrawableImpl { 189 @Override wrap(Drawable drawable)190 public Drawable wrap(Drawable drawable) { 191 return DrawableCompatApi22.wrapForTinting(drawable); 192 } 193 } 194 195 /** 196 * Interface implementation for devices with at least M APIs. 197 */ 198 static class MDrawableImpl extends LollipopMr1DrawableImpl { 199 @Override setLayoutDirection(Drawable drawable, int layoutDirection)200 public void setLayoutDirection(Drawable drawable, int layoutDirection) { 201 DrawableCompatApi23.setLayoutDirection(drawable, layoutDirection); 202 } 203 204 @Override getLayoutDirection(Drawable drawable)205 public int getLayoutDirection(Drawable drawable) { 206 return DrawableCompatApi23.getLayoutDirection(drawable); 207 } 208 } 209 210 /** 211 * Select the correct implementation to use for the current platform. 212 */ 213 static final DrawableImpl IMPL; 214 static { 215 final int version = android.os.Build.VERSION.SDK_INT; 216 if (version >= 23) { 217 IMPL = new MDrawableImpl(); 218 } else if (version >= 22) { 219 IMPL = new LollipopMr1DrawableImpl(); 220 } else if (version >= 21) { 221 IMPL = new LollipopDrawableImpl(); 222 } else if (version >= 19) { 223 IMPL = new KitKatDrawableImpl(); 224 } else if (version >= 17) { 225 IMPL = new JellybeanMr1DrawableImpl(); 226 } else if (version >= 11) { 227 IMPL = new HoneycombDrawableImpl(); 228 } else { 229 IMPL = new BaseDrawableImpl(); 230 } 231 } 232 233 /** 234 * Call {@link Drawable#jumpToCurrentState() Drawable.jumpToCurrentState()}. 235 * <p> 236 * If running on a pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} 237 * device this method does nothing. 238 * 239 * @param drawable The Drawable against which to invoke the method. 240 */ jumpToCurrentState(Drawable drawable)241 public static void jumpToCurrentState(Drawable drawable) { 242 IMPL.jumpToCurrentState(drawable); 243 } 244 245 /** 246 * Set whether this Drawable is automatically mirrored when its layout 247 * direction is RTL (right-to left). See 248 * {@link android.util.LayoutDirection}. 249 * <p> 250 * If running on a pre-{@link android.os.Build.VERSION_CODES#KITKAT} device 251 * this method does nothing. 252 * 253 * @param drawable The Drawable against which to invoke the method. 254 * @param mirrored Set to true if the Drawable should be mirrored, false if 255 * not. 256 */ setAutoMirrored(Drawable drawable, boolean mirrored)257 public static void setAutoMirrored(Drawable drawable, boolean mirrored) { 258 IMPL.setAutoMirrored(drawable, mirrored); 259 } 260 261 /** 262 * Tells if this Drawable will be automatically mirrored when its layout 263 * direction is RTL right-to-left. See {@link android.util.LayoutDirection}. 264 * <p> 265 * If running on a pre-{@link android.os.Build.VERSION_CODES#KITKAT} device 266 * this method returns false. 267 * 268 * @param drawable The Drawable against which to invoke the method. 269 * @return boolean Returns true if this Drawable will be automatically 270 * mirrored. 271 */ isAutoMirrored(Drawable drawable)272 public static boolean isAutoMirrored(Drawable drawable) { 273 return IMPL.isAutoMirrored(drawable); 274 } 275 276 /** 277 * Specifies the hotspot's location within the drawable. 278 * 279 * @param drawable The Drawable against which to invoke the method. 280 * @param x The X coordinate of the center of the hotspot 281 * @param y The Y coordinate of the center of the hotspot 282 */ setHotspot(Drawable drawable, float x, float y)283 public static void setHotspot(Drawable drawable, float x, float y) { 284 IMPL.setHotspot(drawable, x, y); 285 } 286 287 /** 288 * Sets the bounds to which the hotspot is constrained, if they should be 289 * different from the drawable bounds. 290 * 291 * @param drawable The Drawable against which to invoke the method. 292 */ setHotspotBounds(Drawable drawable, int left, int top, int right, int bottom)293 public static void setHotspotBounds(Drawable drawable, int left, int top, 294 int right, int bottom) { 295 IMPL.setHotspotBounds(drawable, left, top, right, bottom); 296 } 297 298 /** 299 * Specifies a tint for {@code drawable}. 300 * 301 * @param drawable The Drawable against which to invoke the method. 302 * @param tint Color to use for tinting this drawable 303 */ setTint(Drawable drawable, int tint)304 public static void setTint(Drawable drawable, int tint) { 305 IMPL.setTint(drawable, tint); 306 } 307 308 /** 309 * Specifies a tint for {@code drawable} as a color state list. 310 * 311 * @param drawable The Drawable against which to invoke the method. 312 * @param tint Color state list to use for tinting this drawable, or null to clear the tint 313 */ setTintList(Drawable drawable, ColorStateList tint)314 public static void setTintList(Drawable drawable, ColorStateList tint) { 315 IMPL.setTintList(drawable, tint); 316 } 317 318 /** 319 * Specifies a tint blending mode for {@code drawable}. 320 * 321 * @param drawable The Drawable against which to invoke the method. 322 * @param tintMode A Porter-Duff blending mode 323 */ setTintMode(Drawable drawable, PorterDuff.Mode tintMode)324 public static void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) { 325 IMPL.setTintMode(drawable, tintMode); 326 } 327 328 /** 329 * Potentially wrap {@code drawable} so that it may be used for tinting across the 330 * different API levels, via the tinting methods in this class. 331 * <p> 332 * If you need to get hold of the original {@link android.graphics.drawable.Drawable} again, 333 * you can use the value returned from {@link #unwrap(Drawable)}. 334 * 335 * @param drawable The Drawable to process 336 * @return A drawable capable of being tinted across all API levels. 337 * 338 * @see #setTint(Drawable, int) 339 * @see #setTintList(Drawable, ColorStateList) 340 * @see #setTintMode(Drawable, PorterDuff.Mode) 341 * @see #unwrap(Drawable) 342 */ wrap(Drawable drawable)343 public static Drawable wrap(Drawable drawable) { 344 return IMPL.wrap(drawable); 345 } 346 347 /** 348 * Unwrap {@code drawable} if it is the result of a call to {@link #wrap(Drawable)}. If 349 * the {@code drawable} is not the result of a call to {@link #wrap(Drawable)} then 350 * {@code drawable} is returned as-is. 351 * 352 * @param drawable The drawable to unwrap 353 * @return the unwrapped {@link Drawable} or {@code drawable} if it hasn't been wrapped. 354 * 355 * @see #wrap(Drawable) 356 */ unwrap(Drawable drawable)357 public static <T extends Drawable> T unwrap(Drawable drawable) { 358 if (drawable instanceof DrawableWrapper) { 359 return (T) ((DrawableWrapper) drawable).getWrappedDrawable(); 360 } 361 return (T) drawable; 362 } 363 364 /** 365 * Set the layout direction for this drawable. Should be a resolved 366 * layout direction, as the Drawable has no capacity to do the resolution on 367 * its own. 368 * 369 * @param layoutDirection the resolved layout direction for the drawable, 370 * either {@link ViewCompat#LAYOUT_DIRECTION_LTR} 371 * or {@link ViewCompat#LAYOUT_DIRECTION_RTL} 372 * @see #getLayoutDirection(Drawable) 373 */ setLayoutDirection(Drawable drawable, int layoutDirection)374 public static void setLayoutDirection(Drawable drawable, int layoutDirection) { 375 IMPL.setLayoutDirection(drawable, layoutDirection); 376 } 377 378 /** 379 * Returns the resolved layout direction for this Drawable. 380 * 381 * @return One of {@link ViewCompat#LAYOUT_DIRECTION_LTR}, 382 * {@link ViewCompat#LAYOUT_DIRECTION_RTL} 383 * @see #setLayoutDirection(Drawable, int) 384 */ getLayoutDirection(Drawable drawable)385 public static int getLayoutDirection(Drawable drawable) { 386 return IMPL.getLayoutDirection(drawable); 387 } 388 } 389