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