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