ClipDrawable.java revision 3da32b768899e7dabe3a16333edf5eca2b9ebe93
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 com.android.internal.R; 20 21import org.xmlpull.v1.XmlPullParser; 22import org.xmlpull.v1.XmlPullParserException; 23 24import android.content.res.Resources; 25import android.content.res.TypedArray; 26import android.content.res.Resources.Theme; 27import android.graphics.*; 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 DrawableWrapper { 52 public static final int HORIZONTAL = 1; 53 public static final int VERTICAL = 2; 54 55 private final Rect mTmpRect = new Rect(); 56 57 private ClipState mState; 58 59 ClipDrawable() { 60 this(new ClipState(null), null); 61 } 62 63 /** 64 * Creates a new clip drawable with the specified gravity and orientation. 65 * 66 * @param drawable the drawable to clip 67 * @param gravity gravity constant (see {@link Gravity} used to position 68 * the clipped drawable within the parent container 69 * @param orientation bitwise-or of {@link #HORIZONTAL} and/or 70 * {@link #VERTICAL} 71 */ 72 public ClipDrawable(Drawable drawable, int gravity, int orientation) { 73 this(new ClipState(null), null); 74 75 mState.mGravity = gravity; 76 mState.mOrientation = orientation; 77 78 setDrawable(drawable); 79 } 80 81 @Override 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 updateStateFromTypedArray(a); 88 inflateChildDrawable(r, parser, attrs, theme); 89 verifyRequiredAttributes(a); 90 a.recycle(); 91 } 92 93 private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException { 94 // If we're not waiting on a theme, verify required attributes. 95 if (getDrawable() == null && (mState.mThemeAttrs == null 96 || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) { 97 throw new XmlPullParserException(a.getPositionDescription() 98 + ": <clip> tag requires a 'drawable' attribute or " 99 + "child tag defining a drawable"); 100 } 101 } 102 103 @Override 104 void updateStateFromTypedArray(TypedArray a) { 105 super.updateStateFromTypedArray(a); 106 107 final ClipState state = mState; 108 state.mOrientation = a.getInt( 109 R.styleable.ClipDrawable_clipOrientation, state.mOrientation); 110 state.mGravity = a.getInt( 111 R.styleable.ClipDrawable_gravity, state.mGravity); 112 113 final Drawable dr = a.getDrawable(R.styleable.ClipDrawable_drawable); 114 if (dr != null) { 115 setDrawable(dr); 116 } 117 } 118 119 @Override 120 public void applyTheme(Theme t) { 121 final ClipState state = mState; 122 if (state == null) { 123 return; 124 } 125 126 if (state.mThemeAttrs != null) { 127 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable); 128 try { 129 updateStateFromTypedArray(a); 130 verifyRequiredAttributes(a); 131 } catch (XmlPullParserException e) { 132 throw new RuntimeException(e); 133 } finally { 134 a.recycle(); 135 } 136 } 137 138 // The drawable may have changed as a result of applying the theme, so 139 // apply the theme to the wrapped drawable last. 140 super.applyTheme(t); 141 } 142 143 @Override 144 protected boolean onLevelChange(float level) { 145 super.onLevelChange(level); 146 invalidateSelf(); 147 return true; 148 } 149 150 @Override 151 public int getOpacity() { 152 final Drawable dr = getDrawable(); 153 final int opacity = dr.getOpacity(); 154 if (opacity == PixelFormat.TRANSPARENT || dr.getLevelFloat() == 0) { 155 return PixelFormat.TRANSPARENT; 156 } 157 158 final float level = getLevelFloat(); 159 if (level >= MAX_LEVEL_FLOAT) { 160 return dr.getOpacity(); 161 } 162 163 // Some portion of non-transparent drawable is showing. 164 return PixelFormat.TRANSLUCENT; 165 } 166 167 @Override 168 public void draw(Canvas canvas) { 169 final Drawable dr = getDrawable(); 170 if (dr.getLevelFloat() == 0) { 171 return; 172 } 173 174 final Rect r = mTmpRect; 175 final Rect bounds = getBounds(); 176 final float level = getLevelFloat(); 177 178 int w = bounds.width(); 179 final int iw = 0; 180 if ((mState.mOrientation & HORIZONTAL) != 0) { 181 w -= Math.round((w - iw) * (MAX_LEVEL_FLOAT - level) / MAX_LEVEL_FLOAT); 182 } 183 184 int h = bounds.height(); 185 final int ih = 0; 186 if ((mState.mOrientation & VERTICAL) != 0) { 187 h -= Math.round((h - ih) * (MAX_LEVEL_FLOAT - level) / MAX_LEVEL_FLOAT); 188 } 189 190 final int layoutDirection = getLayoutDirection(); 191 Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection); 192 193 if (w > 0 && h > 0) { 194 canvas.save(); 195 canvas.clipRect(r); 196 dr.draw(canvas); 197 canvas.restore(); 198 } 199 } 200 201 static final class ClipState extends DrawableWrapper.DrawableWrapperState { 202 int mOrientation = HORIZONTAL; 203 int mGravity = Gravity.LEFT; 204 205 ClipState(ClipState orig) { 206 super(orig); 207 208 if (orig != null) { 209 mOrientation = orig.mOrientation; 210 mGravity = orig.mGravity; 211 } 212 } 213 214 @Override 215 public Drawable newDrawable(Resources res) { 216 return new ClipDrawable(this, res); 217 } 218 } 219 220 private ClipDrawable(ClipState state, Resources res) { 221 super(state, res); 222 223 mState = state; 224 } 225} 226 227