ClipDrawable.java revision 39e33621a725bcdaa21a723866e53c6ea3356169
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 ClipDrawable() { 59 this(null, null); 60 } 61 62 /** 63 * @param orientation Bitwise-or of {@link #HORIZONTAL} and/or {@link #VERTICAL} 64 */ 65 public ClipDrawable(Drawable drawable, int gravity, int orientation) { 66 this(null, null); 67 68 mClipState.mDrawable = drawable; 69 mClipState.mGravity = gravity; 70 mClipState.mOrientation = orientation; 71 72 if (drawable != null) { 73 drawable.setCallback(this); 74 } 75 } 76 77 @Override 78 public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) 79 throws XmlPullParserException, IOException { 80 super.inflate(r, parser, attrs, theme); 81 82 int type; 83 84 TypedArray a = obtainAttributes( 85 r, theme, attrs, com.android.internal.R.styleable.ClipDrawable); 86 87 int orientation = a.getInt( 88 com.android.internal.R.styleable.ClipDrawable_clipOrientation, 89 HORIZONTAL); 90 int g = a.getInt(com.android.internal.R.styleable.ClipDrawable_gravity, Gravity.LEFT); 91 Drawable dr = a.getDrawable(com.android.internal.R.styleable.ClipDrawable_drawable); 92 93 a.recycle(); 94 95 final int outerDepth = parser.getDepth(); 96 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 97 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 98 if (type != XmlPullParser.START_TAG) { 99 continue; 100 } 101 dr = Drawable.createFromXmlInner(r, parser, attrs, theme); 102 } 103 104 if (dr == null) { 105 throw new IllegalArgumentException("No drawable specified for <clip>"); 106 } 107 108 mClipState.mDrawable = dr; 109 mClipState.mOrientation = orientation; 110 mClipState.mGravity = g; 111 112 dr.setCallback(this); 113 } 114 115 // overrides from Drawable.Callback 116 117 @Override 118 public void invalidateDrawable(Drawable who) { 119 final Callback callback = getCallback(); 120 if (callback != null) { 121 callback.invalidateDrawable(this); 122 } 123 } 124 125 @Override 126 public void scheduleDrawable(Drawable who, Runnable what, long when) { 127 final Callback callback = getCallback(); 128 if (callback != null) { 129 callback.scheduleDrawable(this, what, when); 130 } 131 } 132 133 @Override 134 public void unscheduleDrawable(Drawable who, Runnable what) { 135 final Callback callback = getCallback(); 136 if (callback != null) { 137 callback.unscheduleDrawable(this, what); 138 } 139 } 140 141 // overrides from Drawable 142 143 @Override 144 public int getChangingConfigurations() { 145 return super.getChangingConfigurations() 146 | mClipState.mChangingConfigurations 147 | mClipState.mDrawable.getChangingConfigurations(); 148 } 149 150 @Override 151 public boolean getPadding(Rect padding) { 152 // XXX need to adjust padding! 153 return mClipState.mDrawable.getPadding(padding); 154 } 155 156 @Override 157 public boolean setVisible(boolean visible, boolean restart) { 158 mClipState.mDrawable.setVisible(visible, restart); 159 return super.setVisible(visible, restart); 160 } 161 162 @Override 163 public void setAlpha(int alpha) { 164 mClipState.mDrawable.setAlpha(alpha); 165 } 166 167 @Override 168 public int getAlpha() { 169 return mClipState.mDrawable.getAlpha(); 170 } 171 172 @Override 173 public void setColorFilter(ColorFilter cf) { 174 mClipState.mDrawable.setColorFilter(cf); 175 } 176 177 @Override 178 public void setTintList(ColorStateList tint) { 179 mClipState.mDrawable.setTintList(tint); 180 } 181 182 @Override 183 public void setTintMode(Mode tintMode) { 184 mClipState.mDrawable.setTintMode(tintMode); 185 } 186 187 @Override 188 public int getOpacity() { 189 return mClipState.mDrawable.getOpacity(); 190 } 191 192 @Override 193 public boolean isStateful() { 194 return mClipState.mDrawable.isStateful(); 195 } 196 197 @Override 198 protected boolean onStateChange(int[] state) { 199 return mClipState.mDrawable.setState(state); 200 } 201 202 @Override 203 protected boolean onLevelChange(int level) { 204 mClipState.mDrawable.setLevel(level); 205 invalidateSelf(); 206 return true; 207 } 208 209 @Override 210 protected void onBoundsChange(Rect bounds) { 211 mClipState.mDrawable.setBounds(bounds); 212 } 213 214 @Override 215 public void draw(Canvas canvas) { 216 217 if (mClipState.mDrawable.getLevel() == 0) { 218 return; 219 } 220 221 final Rect r = mTmpRect; 222 final Rect bounds = getBounds(); 223 int level = getLevel(); 224 int w = bounds.width(); 225 final int iw = 0; //mClipState.mDrawable.getIntrinsicWidth(); 226 if ((mClipState.mOrientation & HORIZONTAL) != 0) { 227 w -= (w - iw) * (10000 - level) / 10000; 228 } 229 int h = bounds.height(); 230 final int ih = 0; //mClipState.mDrawable.getIntrinsicHeight(); 231 if ((mClipState.mOrientation & VERTICAL) != 0) { 232 h -= (h - ih) * (10000 - level) / 10000; 233 } 234 final int layoutDirection = getLayoutDirection(); 235 Gravity.apply(mClipState.mGravity, w, h, bounds, r, layoutDirection); 236 237 if (w > 0 && h > 0) { 238 canvas.save(); 239 canvas.clipRect(r); 240 mClipState.mDrawable.draw(canvas); 241 canvas.restore(); 242 } 243 } 244 245 @Override 246 public int getIntrinsicWidth() { 247 return mClipState.mDrawable.getIntrinsicWidth(); 248 } 249 250 @Override 251 public int getIntrinsicHeight() { 252 return mClipState.mDrawable.getIntrinsicHeight(); 253 } 254 255 @Override 256 public ConstantState getConstantState() { 257 if (mClipState.canConstantState()) { 258 mClipState.mChangingConfigurations = getChangingConfigurations(); 259 return mClipState; 260 } 261 return null; 262 } 263 264 /** @hide */ 265 @Override 266 public void setLayoutDirection(int layoutDirection) { 267 mClipState.mDrawable.setLayoutDirection(layoutDirection); 268 super.setLayoutDirection(layoutDirection); 269 } 270 271 final static class ClipState extends ConstantState { 272 Drawable mDrawable; 273 int mChangingConfigurations; 274 int mOrientation; 275 int mGravity; 276 277 private boolean mCheckedConstantState; 278 private boolean mCanConstantState; 279 280 ClipState(ClipState orig, ClipDrawable owner, Resources res) { 281 if (orig != null) { 282 if (res != null) { 283 mDrawable = orig.mDrawable.getConstantState().newDrawable(res); 284 } else { 285 mDrawable = orig.mDrawable.getConstantState().newDrawable(); 286 } 287 mDrawable.setCallback(owner); 288 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection()); 289 mDrawable.setBounds(orig.mDrawable.getBounds()); 290 mDrawable.setLevel(orig.mDrawable.getLevel()); 291 mOrientation = orig.mOrientation; 292 mGravity = orig.mGravity; 293 mCheckedConstantState = mCanConstantState = true; 294 } 295 } 296 297 @Override 298 public Drawable newDrawable() { 299 return new ClipDrawable(this, null); 300 } 301 302 @Override 303 public Drawable newDrawable(Resources res) { 304 return new ClipDrawable(this, res); 305 } 306 307 @Override 308 public int getChangingConfigurations() { 309 return mChangingConfigurations; 310 } 311 312 boolean canConstantState() { 313 if (!mCheckedConstantState) { 314 mCanConstantState = mDrawable.getConstantState() != null; 315 mCheckedConstantState = true; 316 } 317 318 return mCanConstantState; 319 } 320 } 321 322 private ClipDrawable(ClipState state, Resources res) { 323 mClipState = new ClipState(state, this, res); 324 } 325} 326 327