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