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.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.annotation.UnsupportedAppUsage; 27 import android.content.res.Resources; 28 import android.content.res.TypedArray; 29 import android.content.res.Resources.Theme; 30 import android.graphics.*; 31 import android.view.Gravity; 32 import android.util.AttributeSet; 33 34 import java.io.IOException; 35 36 /** 37 * A Drawable that clips another Drawable based on this Drawable's current 38 * level value. You can control how much the child Drawable gets clipped in width 39 * and height based on the level, as well as a gravity to control where it is 40 * placed in its overall container. Most often used to implement things like 41 * progress bars, by increasing the drawable's level with {@link 42 * android.graphics.drawable.Drawable#setLevel(int) setLevel()}. 43 * <p class="note"><strong>Note:</strong> The drawable is clipped completely and not visible when 44 * the level is 0 and fully revealed when the level is 10,000.</p> 45 * 46 * <p>It can be defined in an XML file with the <code><clip></code> element. For more 47 * information, see the guide to <a 48 * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p> 49 * 50 * @attr ref android.R.styleable#ClipDrawable_clipOrientation 51 * @attr ref android.R.styleable#ClipDrawable_gravity 52 * @attr ref android.R.styleable#ClipDrawable_drawable 53 */ 54 public class ClipDrawable extends DrawableWrapper { 55 public static final int HORIZONTAL = 1; 56 public static final int VERTICAL = 2; 57 58 private static final int MAX_LEVEL = 10000; 59 60 private final Rect mTmpRect = new Rect(); 61 62 @UnsupportedAppUsage 63 private ClipState mState; 64 ClipDrawable()65 ClipDrawable() { 66 this(new ClipState(null, null), null); 67 } 68 69 /** 70 * Creates a new clip drawable with the specified gravity and orientation. 71 * 72 * @param drawable the drawable to clip 73 * @param gravity gravity constant (see {@link Gravity} used to position 74 * the clipped drawable within the parent container 75 * @param orientation bitwise-or of {@link #HORIZONTAL} and/or 76 * {@link #VERTICAL} 77 */ ClipDrawable(Drawable drawable, int gravity, int orientation)78 public ClipDrawable(Drawable drawable, int gravity, int orientation) { 79 this(new ClipState(null, null), null); 80 81 mState.mGravity = gravity; 82 mState.mOrientation = orientation; 83 84 setDrawable(drawable); 85 } 86 87 @Override inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)88 public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 89 @NonNull AttributeSet attrs, @Nullable Theme theme) 90 throws XmlPullParserException, IOException { 91 final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable); 92 93 // Inflation will advance the XmlPullParser and AttributeSet. 94 super.inflate(r, parser, attrs, theme); 95 96 updateStateFromTypedArray(a); 97 verifyRequiredAttributes(a); 98 a.recycle(); 99 } 100 101 @Override applyTheme(@onNull Theme t)102 public void applyTheme(@NonNull Theme t) { 103 super.applyTheme(t); 104 105 final ClipState state = mState; 106 if (state == null) { 107 return; 108 } 109 110 if (state.mThemeAttrs != null) { 111 final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable); 112 try { 113 updateStateFromTypedArray(a); 114 verifyRequiredAttributes(a); 115 } catch (XmlPullParserException e) { 116 rethrowAsRuntimeException(e); 117 } finally { 118 a.recycle(); 119 } 120 } 121 } 122 verifyRequiredAttributes(@onNull TypedArray a)123 private void verifyRequiredAttributes(@NonNull TypedArray a) throws XmlPullParserException { 124 // If we're not waiting on a theme, verify required attributes. 125 if (getDrawable() == null && (mState.mThemeAttrs == null 126 || mState.mThemeAttrs[R.styleable.ClipDrawable_drawable] == 0)) { 127 throw new XmlPullParserException(a.getPositionDescription() 128 + ": <clip> tag requires a 'drawable' attribute or " 129 + "child tag defining a drawable"); 130 } 131 } 132 updateStateFromTypedArray(@onNull TypedArray a)133 private void updateStateFromTypedArray(@NonNull TypedArray a) { 134 final ClipState state = mState; 135 if (state == null) { 136 return; 137 } 138 139 // Account for any configuration changes. 140 state.mChangingConfigurations |= a.getChangingConfigurations(); 141 142 // Extract the theme attributes, if any. 143 state.mThemeAttrs = a.extractThemeAttrs(); 144 145 state.mOrientation = a.getInt( 146 R.styleable.ClipDrawable_clipOrientation, state.mOrientation); 147 state.mGravity = a.getInt( 148 R.styleable.ClipDrawable_gravity, state.mGravity); 149 } 150 151 @Override onLevelChange(int level)152 protected boolean onLevelChange(int level) { 153 super.onLevelChange(level); 154 invalidateSelf(); 155 return true; 156 } 157 158 @Override getOpacity()159 public int getOpacity() { 160 final Drawable dr = getDrawable(); 161 final int opacity = dr.getOpacity(); 162 if (opacity == PixelFormat.TRANSPARENT || dr.getLevel() == 0) { 163 return PixelFormat.TRANSPARENT; 164 } 165 166 final int level = getLevel(); 167 if (level >= MAX_LEVEL) { 168 return dr.getOpacity(); 169 } 170 171 // Some portion of non-transparent drawable is showing. 172 return PixelFormat.TRANSLUCENT; 173 } 174 175 @Override draw(Canvas canvas)176 public void draw(Canvas canvas) { 177 final Drawable dr = getDrawable(); 178 if (dr.getLevel() == 0) { 179 return; 180 } 181 182 final Rect r = mTmpRect; 183 final Rect bounds = getBounds(); 184 final int level = getLevel(); 185 186 int w = bounds.width(); 187 final int iw = 0; //mState.mDrawable.getIntrinsicWidth(); 188 if ((mState.mOrientation & HORIZONTAL) != 0) { 189 w -= (w - iw) * (MAX_LEVEL - level) / MAX_LEVEL; 190 } 191 192 int h = bounds.height(); 193 final int ih = 0; //mState.mDrawable.getIntrinsicHeight(); 194 if ((mState.mOrientation & VERTICAL) != 0) { 195 h -= (h - ih) * (MAX_LEVEL - level) / MAX_LEVEL; 196 } 197 198 final int layoutDirection = getLayoutDirection(); 199 Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection); 200 201 if (w > 0 && h > 0) { 202 canvas.save(); 203 canvas.clipRect(r); 204 dr.draw(canvas); 205 canvas.restore(); 206 } 207 } 208 209 @Override mutateConstantState()210 DrawableWrapperState mutateConstantState() { 211 mState = new ClipState(mState, null); 212 return mState; 213 } 214 215 static final class ClipState extends DrawableWrapper.DrawableWrapperState { 216 private int[] mThemeAttrs; 217 218 int mOrientation = HORIZONTAL; 219 int mGravity = Gravity.LEFT; 220 ClipState(ClipState orig, Resources res)221 ClipState(ClipState orig, Resources res) { 222 super(orig, res); 223 224 if (orig != null) { 225 mOrientation = orig.mOrientation; 226 mGravity = orig.mGravity; 227 } 228 } 229 230 @Override newDrawable(Resources res)231 public Drawable newDrawable(Resources res) { 232 return new ClipDrawable(this, res); 233 } 234 } 235 ClipDrawable(ClipState state, Resources res)236 private ClipDrawable(ClipState state, Resources res) { 237 super(state, res); 238 239 mState = state; 240 } 241 } 242 243