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