ClipDrawable.java revision 17cd4dfe3a05c2eddbcbc76066ff3b13fc3f2c8b
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 org.xmlpull.v1.XmlPullParser; 20import org.xmlpull.v1.XmlPullParserException; 21 22import android.content.res.ColorStateList; 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.content.res.Resources.Theme; 26import android.graphics.*; 27import android.graphics.PorterDuff.Mode; 28import android.view.Gravity; 29import android.util.AttributeSet; 30 31import java.io.IOException; 32 33/** 34 * A Drawable that clips another Drawable based on this Drawable's current 35 * level value. You can control how much the child Drawable gets clipped in width 36 * and height based on the level, as well as a gravity to control where it is 37 * placed in its overall container. Most often used to implement things like 38 * progress bars, by increasing the drawable's level with {@link 39 * android.graphics.drawable.Drawable#setLevel(int) setLevel()}. 40 * <p class="note"><strong>Note:</strong> The drawable is clipped completely and not visible when 41 * the level is 0 and fully revealed when the level is 10,000.</p> 42 * 43 * <p>It can be defined in an XML file with the <code><clip></code> element. For more 44 * information, see the guide to <a 45 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 46 * 47 * @attr ref android.R.styleable#ClipDrawable_clipOrientation 48 * @attr ref android.R.styleable#ClipDrawable_gravity 49 * @attr ref android.R.styleable#ClipDrawable_drawable 50 */ 51public class ClipDrawable extends Drawable implements Drawable.Callback { 52 private ClipState mClipState; 53 private final Rect mTmpRect = new Rect(); 54 55 public static final int HORIZONTAL = 1; 56 public static final int VERTICAL = 2; 57 58 private boolean mMutated; 59 60 ClipDrawable() { 61 this(null, null); 62 } 63 64 /** 65 * @param orientation Bitwise-or of {@link #HORIZONTAL} and/or {@link #VERTICAL} 66 */ 67 public ClipDrawable(Drawable drawable, int gravity, int orientation) { 68 this(null, null); 69 70 mClipState.mDrawable = drawable; 71 mClipState.mGravity = gravity; 72 mClipState.mOrientation = orientation; 73 74 if (drawable != null) { 75 drawable.setCallback(this); 76 } 77 } 78 79 @Override 80 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 81 throws XmlPullParserException, IOException { 82 super.inflate(r, parser, attrs, theme); 83 84 int type; 85 86 TypedArray a = obtainAttributes( 87 r, theme, attrs, com.android.internal.R.styleable.ClipDrawable); 88 89 int orientation = a.getInt( 90 com.android.internal.R.styleable.ClipDrawable_clipOrientation, 91 HORIZONTAL); 92 int g = a.getInt(com.android.internal.R.styleable.ClipDrawable_gravity, Gravity.LEFT); 93 Drawable dr = a.getDrawable(com.android.internal.R.styleable.ClipDrawable_drawable); 94 95 a.recycle(); 96 97 final int outerDepth = parser.getDepth(); 98 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 99 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 100 if (type != XmlPullParser.START_TAG) { 101 continue; 102 } 103 dr = Drawable.createFromXmlInner(r, parser, attrs, theme); 104 } 105 106 if (dr == null) { 107 throw new IllegalArgumentException("No drawable specified for <clip>"); 108 } 109 110 mClipState.mDrawable = dr; 111 mClipState.mOrientation = orientation; 112 mClipState.mGravity = g; 113 114 dr.setCallback(this); 115 } 116 117 @Override 118 public void applyTheme(Theme t) { 119 super.applyTheme(t); 120 121 final ClipState state = mClipState; 122 if (state == null) { 123 return; 124 } 125 126 if (state.mDrawable != null) { 127 state.mDrawable.applyTheme(t); 128 } 129 } 130 131 @Override 132 public boolean canApplyTheme() { 133 final ClipState state = mClipState; 134 return state != null && state.mDrawable != null && state.mDrawable.canApplyTheme(); 135 } 136 137 // overrides from Drawable.Callback 138 139 @Override 140 public void invalidateDrawable(Drawable who) { 141 final Callback callback = getCallback(); 142 if (callback != null) { 143 callback.invalidateDrawable(this); 144 } 145 } 146 147 @Override 148 public void scheduleDrawable(Drawable who, Runnable what, long when) { 149 final Callback callback = getCallback(); 150 if (callback != null) { 151 callback.scheduleDrawable(this, what, when); 152 } 153 } 154 155 @Override 156 public void unscheduleDrawable(Drawable who, Runnable what) { 157 final Callback callback = getCallback(); 158 if (callback != null) { 159 callback.unscheduleDrawable(this, what); 160 } 161 } 162 163 // overrides from Drawable 164 165 @Override 166 public int getChangingConfigurations() { 167 return super.getChangingConfigurations() 168 | mClipState.mChangingConfigurations 169 | mClipState.mDrawable.getChangingConfigurations(); 170 } 171 172 @Override 173 public boolean getPadding(Rect padding) { 174 // XXX need to adjust padding! 175 return mClipState.mDrawable.getPadding(padding); 176 } 177 178 @Override 179 public boolean setVisible(boolean visible, boolean restart) { 180 mClipState.mDrawable.setVisible(visible, restart); 181 return super.setVisible(visible, restart); 182 } 183 184 @Override 185 public void setAlpha(int alpha) { 186 mClipState.mDrawable.setAlpha(alpha); 187 } 188 189 @Override 190 public int getAlpha() { 191 return mClipState.mDrawable.getAlpha(); 192 } 193 194 @Override 195 public void setColorFilter(ColorFilter cf) { 196 mClipState.mDrawable.setColorFilter(cf); 197 } 198 199 @Override 200 public void setTintList(ColorStateList tint) { 201 mClipState.mDrawable.setTintList(tint); 202 } 203 204 @Override 205 public void setTintMode(Mode tintMode) { 206 mClipState.mDrawable.setTintMode(tintMode); 207 } 208 209 @Override 210 public int getOpacity() { 211 return mClipState.mDrawable.getOpacity(); 212 } 213 214 @Override 215 public boolean isStateful() { 216 return mClipState.mDrawable.isStateful(); 217 } 218 219 @Override 220 protected boolean onStateChange(int[] state) { 221 return mClipState.mDrawable.setState(state); 222 } 223 224 @Override 225 protected boolean onLevelChange(int level) { 226 mClipState.mDrawable.setLevel(level); 227 invalidateSelf(); 228 return true; 229 } 230 231 @Override 232 protected void onBoundsChange(Rect bounds) { 233 mClipState.mDrawable.setBounds(bounds); 234 } 235 236 @Override 237 public void draw(Canvas canvas) { 238 239 if (mClipState.mDrawable.getLevel() == 0) { 240 return; 241 } 242 243 final Rect r = mTmpRect; 244 final Rect bounds = getBounds(); 245 int level = getLevel(); 246 int w = bounds.width(); 247 final int iw = 0; //mClipState.mDrawable.getIntrinsicWidth(); 248 if ((mClipState.mOrientation & HORIZONTAL) != 0) { 249 w -= (w - iw) * (10000 - level) / 10000; 250 } 251 int h = bounds.height(); 252 final int ih = 0; //mClipState.mDrawable.getIntrinsicHeight(); 253 if ((mClipState.mOrientation & VERTICAL) != 0) { 254 h -= (h - ih) * (10000 - level) / 10000; 255 } 256 final int layoutDirection = getLayoutDirection(); 257 Gravity.apply(mClipState.mGravity, w, h, bounds, r, layoutDirection); 258 259 if (w > 0 && h > 0) { 260 canvas.save(); 261 canvas.clipRect(r); 262 mClipState.mDrawable.draw(canvas); 263 canvas.restore(); 264 } 265 } 266 267 @Override 268 public int getIntrinsicWidth() { 269 return mClipState.mDrawable.getIntrinsicWidth(); 270 } 271 272 @Override 273 public int getIntrinsicHeight() { 274 return mClipState.mDrawable.getIntrinsicHeight(); 275 } 276 277 @Override 278 public ConstantState getConstantState() { 279 if (mClipState.canConstantState()) { 280 mClipState.mChangingConfigurations = getChangingConfigurations(); 281 return mClipState; 282 } 283 return null; 284 } 285 286 /** @hide */ 287 @Override 288 public void setLayoutDirection(int layoutDirection) { 289 mClipState.mDrawable.setLayoutDirection(layoutDirection); 290 super.setLayoutDirection(layoutDirection); 291 } 292 293 @Override 294 public Drawable mutate() { 295 if (!mMutated && super.mutate() == this) { 296 mClipState.mDrawable.mutate(); 297 mMutated = true; 298 } 299 return this; 300 } 301 302 /** 303 * @hide 304 */ 305 public void clearMutated() { 306 super.clearMutated(); 307 mClipState.mDrawable.clearMutated(); 308 mMutated = false; 309 } 310 311 final static class ClipState extends ConstantState { 312 Drawable mDrawable; 313 int mChangingConfigurations; 314 int mOrientation; 315 int mGravity; 316 317 private boolean mCheckedConstantState; 318 private boolean mCanConstantState; 319 320 ClipState(ClipState orig, ClipDrawable owner, Resources res) { 321 if (orig != null) { 322 if (res != null) { 323 mDrawable = orig.mDrawable.getConstantState().newDrawable(res); 324 } else { 325 mDrawable = orig.mDrawable.getConstantState().newDrawable(); 326 } 327 mDrawable.setCallback(owner); 328 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); 329 mDrawable.setBounds(orig.mDrawable.getBounds()); 330 mDrawable.setLevel(orig.mDrawable.getLevel()); 331 mOrientation = orig.mOrientation; 332 mGravity = orig.mGravity; 333 mCheckedConstantState = mCanConstantState = true; 334 } 335 } 336 337 @Override 338 public Drawable newDrawable() { 339 return new ClipDrawable(this, null); 340 } 341 342 @Override 343 public Drawable newDrawable(Resources res) { 344 return new ClipDrawable(this, res); 345 } 346 347 @Override 348 public int getChangingConfigurations() { 349 return mChangingConfigurations; 350 } 351 352 boolean canConstantState() { 353 if (!mCheckedConstantState) { 354 mCanConstantState = mDrawable.getConstantState() != null; 355 mCheckedConstantState = true; 356 } 357 358 return mCanConstantState; 359 } 360 } 361 362 private ClipDrawable(ClipState state, Resources res) { 363 mClipState = new ClipState(state, this, res); 364 } 365} 366 367