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