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