NinePatchDrawable.java revision 684385ddde2ac0c26de0862390ad713aff3fb149
1/* 2 * Copyright (C) 2006 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.graphics.drawable; 18 19import android.content.res.Resources; 20import android.content.res.TypedArray; 21import android.graphics.Bitmap; 22import android.graphics.BitmapFactory; 23import android.graphics.Canvas; 24import android.graphics.ColorFilter; 25import android.graphics.Insets; 26import android.graphics.NinePatch; 27import android.graphics.Paint; 28import android.graphics.PixelFormat; 29import android.graphics.Rect; 30import android.graphics.Region; 31import android.util.AttributeSet; 32import android.util.DisplayMetrics; 33import android.util.TypedValue; 34import org.xmlpull.v1.XmlPullParser; 35import org.xmlpull.v1.XmlPullParserException; 36 37import java.io.IOException; 38import java.io.InputStream; 39 40/** 41 * 42 * A resizeable bitmap, with stretchable areas that you define. This type of image 43 * is defined in a .png file with a special format. 44 * 45 * <div class="special reference"> 46 * <h3>Developer Guides</h3> 47 * <p>For more information about how to use a NinePatchDrawable, read the 48 * <a href="{@docRoot}guide/topics/graphics/2d-graphics.html#nine-patch"> 49 * Canvas and Drawables</a> developer guide. For information about creating a NinePatch image 50 * file using the draw9patch tool, see the 51 * <a href="{@docRoot}guide/developing/tools/draw9patch.html">Draw 9-patch</a> tool guide.</p></div> 52 */ 53public class NinePatchDrawable extends Drawable { 54 // dithering helps a lot, and is pretty cheap, so default is true 55 private static final boolean DEFAULT_DITHER = true; 56 private NinePatchState mNinePatchState; 57 private NinePatch mNinePatch; 58 private Rect mPadding; 59 private Insets mLayoutInsets = Insets.NONE; 60 private Paint mPaint; 61 private boolean mMutated; 62 63 private int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 64 65 // These are scaled to match the target density. 66 private int mBitmapWidth; 67 private int mBitmapHeight; 68 69 NinePatchDrawable() { 70 } 71 72 /** 73 * Create drawable from raw nine-patch data, not dealing with density. 74 * @deprecated Use {@link #NinePatchDrawable(Resources, Bitmap, byte[], Rect, String)} 75 * to ensure that the drawable has correctly set its target density. 76 */ 77 @Deprecated 78 public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) { 79 this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null); 80 } 81 82 /** 83 * Create drawable from raw nine-patch data, setting initial target density 84 * based on the display metrics of the resources. 85 */ 86 public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, 87 Rect padding, String srcName) { 88 this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res); 89 mNinePatchState.mTargetDensity = mTargetDensity; 90 } 91 92 /** 93 * Create drawable from raw nine-patch data, setting initial target density 94 * based on the display metrics of the resources. 95 * 96 * @hide 97 */ 98 public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, 99 Rect padding, Rect layoutInsets, String srcName) { 100 this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, layoutInsets), res); 101 mNinePatchState.mTargetDensity = mTargetDensity; 102 } 103 104 /** 105 * Create drawable from existing nine-patch, not dealing with density. 106 * @deprecated Use {@link #NinePatchDrawable(Resources, NinePatch)} 107 * to ensure that the drawable has correctly set its target density. 108 */ 109 @Deprecated 110 public NinePatchDrawable(NinePatch patch) { 111 this(new NinePatchState(patch, new Rect()), null); 112 } 113 114 /** 115 * Create drawable from existing nine-patch, setting initial target density 116 * based on the display metrics of the resources. 117 */ 118 public NinePatchDrawable(Resources res, NinePatch patch) { 119 this(new NinePatchState(patch, new Rect()), res); 120 mNinePatchState.mTargetDensity = mTargetDensity; 121 } 122 123 private void setNinePatchState(NinePatchState state, Resources res) { 124 mNinePatchState = state; 125 mNinePatch = state.mNinePatch; 126 mPadding = state.mPadding; 127 mTargetDensity = res != null ? res.getDisplayMetrics().densityDpi 128 : state.mTargetDensity; 129 //noinspection PointlessBooleanExpression 130 if (state.mDither != DEFAULT_DITHER) { 131 // avoid calling the setter unless we need to, since it does a 132 // lazy allocation of a paint 133 setDither(state.mDither); 134 } 135 if (mNinePatch != null) { 136 computeBitmapSize(); 137 } 138 } 139 140 /** 141 * Set the density scale at which this drawable will be rendered. This 142 * method assumes the drawable will be rendered at the same density as the 143 * specified canvas. 144 * 145 * @param canvas The Canvas from which the density scale must be obtained. 146 * 147 * @see android.graphics.Bitmap#setDensity(int) 148 * @see android.graphics.Bitmap#getDensity() 149 */ 150 public void setTargetDensity(Canvas canvas) { 151 setTargetDensity(canvas.getDensity()); 152 } 153 154 /** 155 * Set the density scale at which this drawable will be rendered. 156 * 157 * @param metrics The DisplayMetrics indicating the density scale for this drawable. 158 * 159 * @see android.graphics.Bitmap#setDensity(int) 160 * @see android.graphics.Bitmap#getDensity() 161 */ 162 public void setTargetDensity(DisplayMetrics metrics) { 163 setTargetDensity(metrics.densityDpi); 164 } 165 166 /** 167 * Set the density at which this drawable will be rendered. 168 * 169 * @param density The density scale for this drawable. 170 * 171 * @see android.graphics.Bitmap#setDensity(int) 172 * @see android.graphics.Bitmap#getDensity() 173 */ 174 public void setTargetDensity(int density) { 175 if (density != mTargetDensity) { 176 mTargetDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; 177 if (mNinePatch != null) { 178 computeBitmapSize(); 179 } 180 invalidateSelf(); 181 } 182 } 183 184 private static Insets scaleFromDensity(Insets insets, int sdensity, int tdensity) { 185 int left = Bitmap.scaleFromDensity(insets.left, sdensity, tdensity); 186 int top = Bitmap.scaleFromDensity(insets.top, sdensity, tdensity); 187 int right = Bitmap.scaleFromDensity(insets.right, sdensity, tdensity); 188 int bottom = Bitmap.scaleFromDensity(insets.bottom, sdensity, tdensity); 189 return Insets.of(left, top, right, bottom); 190 } 191 192 private void computeBitmapSize() { 193 final int sdensity = mNinePatch.getDensity(); 194 final int tdensity = mTargetDensity; 195 if (sdensity == tdensity) { 196 mBitmapWidth = mNinePatch.getWidth(); 197 mBitmapHeight = mNinePatch.getHeight(); 198 mLayoutInsets = mNinePatchState.mLayoutInsets; 199 } else { 200 mBitmapWidth = Bitmap.scaleFromDensity(mNinePatch.getWidth(), 201 sdensity, tdensity); 202 mBitmapHeight = Bitmap.scaleFromDensity(mNinePatch.getHeight(), 203 sdensity, tdensity); 204 if (mNinePatchState.mPadding != null && mPadding != null) { 205 Rect dest = mPadding; 206 Rect src = mNinePatchState.mPadding; 207 if (dest == src) { 208 mPadding = dest = new Rect(src); 209 } 210 dest.left = Bitmap.scaleFromDensity(src.left, sdensity, tdensity); 211 dest.top = Bitmap.scaleFromDensity(src.top, sdensity, tdensity); 212 dest.right = Bitmap.scaleFromDensity(src.right, sdensity, tdensity); 213 dest.bottom = Bitmap.scaleFromDensity(src.bottom, sdensity, tdensity); 214 } 215 mLayoutInsets = scaleFromDensity(mNinePatchState.mLayoutInsets, sdensity, tdensity); 216 } 217 } 218 219 @Override 220 public void draw(Canvas canvas) { 221 mNinePatch.draw(canvas, getBounds(), mPaint); 222 } 223 224 @Override 225 public int getChangingConfigurations() { 226 return super.getChangingConfigurations() | mNinePatchState.mChangingConfigurations; 227 } 228 229 @Override 230 public boolean getPadding(Rect padding) { 231 padding.set(mPadding); 232 return true; 233 } 234 235 /** 236 * @hide 237 */ 238 @Override 239 public Insets getLayoutInsets() { 240 return mLayoutInsets; 241 } 242 243 @Override 244 public void setAlpha(int alpha) { 245 if (mPaint == null && alpha == 0xFF) { 246 // Fast common case -- leave at normal alpha. 247 return; 248 } 249 getPaint().setAlpha(alpha); 250 invalidateSelf(); 251 } 252 253 @Override 254 public void setColorFilter(ColorFilter cf) { 255 if (mPaint == null && cf == null) { 256 // Fast common case -- leave at no color filter. 257 return; 258 } 259 getPaint().setColorFilter(cf); 260 invalidateSelf(); 261 } 262 263 @Override 264 public void setDither(boolean dither) { 265 if (mPaint == null && dither == DEFAULT_DITHER) { 266 // Fast common case -- leave at default dither. 267 return; 268 } 269 getPaint().setDither(dither); 270 invalidateSelf(); 271 } 272 273 @Override 274 public void setFilterBitmap(boolean filter) { 275 getPaint().setFilterBitmap(filter); 276 invalidateSelf(); 277 } 278 279 @Override 280 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) 281 throws XmlPullParserException, IOException { 282 super.inflate(r, parser, attrs); 283 284 TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.NinePatchDrawable); 285 286 final int id = a.getResourceId(com.android.internal.R.styleable.NinePatchDrawable_src, 0); 287 if (id == 0) { 288 throw new XmlPullParserException(parser.getPositionDescription() + 289 ": <nine-patch> requires a valid src attribute"); 290 } 291 292 final boolean dither = a.getBoolean( 293 com.android.internal.R.styleable.NinePatchDrawable_dither, 294 DEFAULT_DITHER); 295 final BitmapFactory.Options options = new BitmapFactory.Options(); 296 if (dither) { 297 options.inDither = false; 298 } 299 options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; 300 301 final Rect padding = new Rect(); 302 final Rect layoutInsets = new Rect(); 303 Bitmap bitmap = null; 304 305 try { 306 final TypedValue value = new TypedValue(); 307 final InputStream is = r.openRawResource(id, value); 308 309 bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); 310 311 is.close(); 312 } catch (IOException e) { 313 // Ignore 314 } 315 316 if (bitmap == null) { 317 throw new XmlPullParserException(parser.getPositionDescription() + 318 ": <nine-patch> requires a valid src attribute"); 319 } else if (bitmap.getNinePatchChunk() == null) { 320 throw new XmlPullParserException(parser.getPositionDescription() + 321 ": <nine-patch> requires a valid 9-patch source image"); 322 } 323 324 setNinePatchState(new NinePatchState( 325 new NinePatch(bitmap, bitmap.getNinePatchChunk(), "XML 9-patch"), 326 padding, layoutInsets, dither), r); 327 mNinePatchState.mTargetDensity = mTargetDensity; 328 329 a.recycle(); 330 } 331 332 public Paint getPaint() { 333 if (mPaint == null) { 334 mPaint = new Paint(); 335 mPaint.setDither(DEFAULT_DITHER); 336 } 337 return mPaint; 338 } 339 340 /** 341 * Retrieves the width of the source .png file (before resizing). 342 */ 343 @Override 344 public int getIntrinsicWidth() { 345 return mBitmapWidth; 346 } 347 348 /** 349 * Retrieves the height of the source .png file (before resizing). 350 */ 351 @Override 352 public int getIntrinsicHeight() { 353 return mBitmapHeight; 354 } 355 356 @Override 357 public int getMinimumWidth() { 358 return mBitmapWidth; 359 } 360 361 @Override 362 public int getMinimumHeight() { 363 return mBitmapHeight; 364 } 365 366 /** 367 * Returns a {@link android.graphics.PixelFormat graphics.PixelFormat} 368 * value of OPAQUE or TRANSLUCENT. 369 */ 370 @Override 371 public int getOpacity() { 372 return mNinePatch.hasAlpha() || (mPaint != null && mPaint.getAlpha() < 255) ? 373 PixelFormat.TRANSLUCENT : PixelFormat.OPAQUE; 374 } 375 376 @Override 377 public Region getTransparentRegion() { 378 return mNinePatch.getTransparentRegion(getBounds()); 379 } 380 381 @Override 382 public ConstantState getConstantState() { 383 mNinePatchState.mChangingConfigurations = getChangingConfigurations(); 384 return mNinePatchState; 385 } 386 387 @Override 388 public Drawable mutate() { 389 if (!mMutated && super.mutate() == this) { 390 mNinePatchState = new NinePatchState(mNinePatchState); 391 mNinePatch = mNinePatchState.mNinePatch; 392 mMutated = true; 393 } 394 return this; 395 } 396 397 private final static class NinePatchState extends ConstantState { 398 final NinePatch mNinePatch; 399 final Rect mPadding; 400 final Insets mLayoutInsets; 401 final boolean mDither; 402 int mChangingConfigurations; 403 int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; 404 405 NinePatchState(NinePatch ninePatch, Rect padding) { 406 this(ninePatch, padding, new Rect(), DEFAULT_DITHER); 407 } 408 409 NinePatchState(NinePatch ninePatch, Rect padding, Rect layoutInsets) { 410 this(ninePatch, padding, layoutInsets, DEFAULT_DITHER); 411 } 412 413 NinePatchState(NinePatch ninePatch, Rect rect, Rect layoutInsets, boolean dither) { 414 mNinePatch = ninePatch; 415 mPadding = rect; 416 mLayoutInsets = Insets.of(layoutInsets); 417 mDither = dither; 418 } 419 420 // Copy constructor 421 422 NinePatchState(NinePatchState state) { 423 mNinePatch = new NinePatch(state.mNinePatch); 424 // Note we don't copy the padding because it is immutable. 425 mPadding = state.mPadding; 426 mLayoutInsets = state.mLayoutInsets; 427 mDither = state.mDither; 428 mChangingConfigurations = state.mChangingConfigurations; 429 mTargetDensity = state.mTargetDensity; 430 } 431 432 @Override 433 public Drawable newDrawable() { 434 return new NinePatchDrawable(this, null); 435 } 436 437 @Override 438 public Drawable newDrawable(Resources res) { 439 return new NinePatchDrawable(this, res); 440 } 441 442 @Override 443 public int getChangingConfigurations() { 444 return mChangingConfigurations; 445 } 446 } 447 448 private NinePatchDrawable(NinePatchState state, Resources res) { 449 setNinePatchState(state, res); 450 } 451} 452