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