Ensures changing configurations mask is propagated to the host drawable so that it can be properly cleared from cache on configuration changes. Also fixes constant state handling of the mask in the Inset and Rotate drawables. Hides new ColorStateList methods related to theming, since they should only be used during preloading or internally by framework drawables. Fixes bug where the cached versions of themeable ColorStateLists were modified by calling applyTheme() on the host drawable. Also cleans up some docs and naming in GradientDrawable. Bug: 19966397 Change-Id: I8c8d3cabbaf94b488c2b8fe9fd423e07d824c19c
358 lines
12 KiB
Java
358 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2007 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.graphics.drawable;
|
|
|
|
import com.android.internal.R;
|
|
|
|
import org.xmlpull.v1.XmlPullParser;
|
|
import org.xmlpull.v1.XmlPullParserException;
|
|
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Rect;
|
|
import android.content.res.Resources;
|
|
import android.content.res.TypedArray;
|
|
import android.content.res.Resources.Theme;
|
|
import android.util.MathUtils;
|
|
import android.util.TypedValue;
|
|
import android.util.AttributeSet;
|
|
|
|
import java.io.IOException;
|
|
|
|
/**
|
|
* <p>
|
|
* A Drawable that can rotate another Drawable based on the current level value.
|
|
* The start and end angles of rotation can be controlled to map any circular
|
|
* arc to the level values range.
|
|
* <p>
|
|
* It can be defined in an XML file with the <code><rotate></code> element.
|
|
* For more information, see the guide to
|
|
* <a href="{@docRoot}guide/topics/resources/animation-resource.html">Animation Resources</a>.
|
|
*
|
|
* @attr ref android.R.styleable#RotateDrawable_visible
|
|
* @attr ref android.R.styleable#RotateDrawable_fromDegrees
|
|
* @attr ref android.R.styleable#RotateDrawable_toDegrees
|
|
* @attr ref android.R.styleable#RotateDrawable_pivotX
|
|
* @attr ref android.R.styleable#RotateDrawable_pivotY
|
|
* @attr ref android.R.styleable#RotateDrawable_drawable
|
|
*/
|
|
public class RotateDrawable extends DrawableWrapper {
|
|
private static final int MAX_LEVEL = 10000;
|
|
|
|
private RotateState mState;
|
|
|
|
/**
|
|
* Creates a new rotating drawable with no wrapped drawable.
|
|
*/
|
|
public RotateDrawable() {
|
|
this(new RotateState(null), null);
|
|
}
|
|
|
|
@Override
|
|
public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
|
|
throws XmlPullParserException, IOException {
|
|
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.RotateDrawable);
|
|
super.inflateWithAttributes(r, parser, a, R.styleable.RotateDrawable_visible);
|
|
|
|
updateStateFromTypedArray(a);
|
|
inflateChildDrawable(r, parser, attrs, theme);
|
|
verifyRequiredAttributes(a);
|
|
a.recycle();
|
|
}
|
|
|
|
private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
|
|
// If we're not waiting on a theme, verify required attributes.
|
|
if (getDrawable() == null && (mState.mThemeAttrs == null
|
|
|| mState.mThemeAttrs[R.styleable.RotateDrawable_drawable] == 0)) {
|
|
throw new XmlPullParserException(a.getPositionDescription()
|
|
+ ": <rotate> tag requires a 'drawable' attribute or "
|
|
+ "child tag defining a drawable");
|
|
}
|
|
}
|
|
|
|
@Override
|
|
void updateStateFromTypedArray(TypedArray a) {
|
|
super.updateStateFromTypedArray(a);
|
|
|
|
final RotateState state = mState;
|
|
|
|
// Extract the theme attributes, if any.
|
|
state.mThemeAttrs = a.extractThemeAttrs();
|
|
|
|
if (a.hasValue(R.styleable.RotateDrawable_pivotX)) {
|
|
final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotX);
|
|
state.mPivotXRel = tv.type == TypedValue.TYPE_FRACTION;
|
|
state.mPivotX = state.mPivotXRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
|
|
}
|
|
|
|
if (a.hasValue(R.styleable.RotateDrawable_pivotY)) {
|
|
final TypedValue tv = a.peekValue(R.styleable.RotateDrawable_pivotY);
|
|
state.mPivotYRel = tv.type == TypedValue.TYPE_FRACTION;
|
|
state.mPivotY = state.mPivotYRel ? tv.getFraction(1.0f, 1.0f) : tv.getFloat();
|
|
}
|
|
|
|
state.mFromDegrees = a.getFloat(
|
|
R.styleable.RotateDrawable_fromDegrees, state.mFromDegrees);
|
|
state.mToDegrees = a.getFloat(
|
|
R.styleable.RotateDrawable_toDegrees, state.mToDegrees);
|
|
state.mCurrentDegrees = state.mFromDegrees;
|
|
|
|
final Drawable dr = a.getDrawable(R.styleable.RotateDrawable_drawable);
|
|
if (dr != null) {
|
|
setDrawable(dr);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void applyTheme(Theme t) {
|
|
final RotateState state = mState;
|
|
if (state == null) {
|
|
return;
|
|
}
|
|
|
|
if (state.mThemeAttrs != null) {
|
|
final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.RotateDrawable);
|
|
try {
|
|
updateStateFromTypedArray(a);
|
|
verifyRequiredAttributes(a);
|
|
} catch (XmlPullParserException e) {
|
|
throw new RuntimeException(e);
|
|
} finally {
|
|
a.recycle();
|
|
}
|
|
}
|
|
|
|
// The drawable may have changed as a result of applying the theme, so
|
|
// apply the theme to the wrapped drawable last.
|
|
super.applyTheme(t);
|
|
}
|
|
|
|
@Override
|
|
public void draw(Canvas canvas) {
|
|
final Drawable d = getDrawable();
|
|
final Rect bounds = d.getBounds();
|
|
final int w = bounds.right - bounds.left;
|
|
final int h = bounds.bottom - bounds.top;
|
|
final RotateState st = mState;
|
|
final float px = st.mPivotXRel ? (w * st.mPivotX) : st.mPivotX;
|
|
final float py = st.mPivotYRel ? (h * st.mPivotY) : st.mPivotY;
|
|
|
|
final int saveCount = canvas.save();
|
|
canvas.rotate(st.mCurrentDegrees, px + bounds.left, py + bounds.top);
|
|
d.draw(canvas);
|
|
canvas.restoreToCount(saveCount);
|
|
}
|
|
|
|
/**
|
|
* Sets the start angle for rotation.
|
|
*
|
|
* @param fromDegrees starting angle in degrees
|
|
* @see #getFromDegrees()
|
|
* @attr ref android.R.styleable#RotateDrawable_fromDegrees
|
|
*/
|
|
public void setFromDegrees(float fromDegrees) {
|
|
if (mState.mFromDegrees != fromDegrees) {
|
|
mState.mFromDegrees = fromDegrees;
|
|
invalidateSelf();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return starting angle for rotation in degrees
|
|
* @see #setFromDegrees(float)
|
|
* @attr ref android.R.styleable#RotateDrawable_fromDegrees
|
|
*/
|
|
public float getFromDegrees() {
|
|
return mState.mFromDegrees;
|
|
}
|
|
|
|
/**
|
|
* Sets the end angle for rotation.
|
|
*
|
|
* @param toDegrees ending angle in degrees
|
|
* @see #getToDegrees()
|
|
* @attr ref android.R.styleable#RotateDrawable_toDegrees
|
|
*/
|
|
public void setToDegrees(float toDegrees) {
|
|
if (mState.mToDegrees != toDegrees) {
|
|
mState.mToDegrees = toDegrees;
|
|
invalidateSelf();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return ending angle for rotation in degrees
|
|
* @see #setToDegrees(float)
|
|
* @attr ref android.R.styleable#RotateDrawable_toDegrees
|
|
*/
|
|
public float getToDegrees() {
|
|
return mState.mToDegrees;
|
|
}
|
|
|
|
/**
|
|
* Sets the X position around which the drawable is rotated.
|
|
*
|
|
* @param pivotX X position around which to rotate. If the X pivot is
|
|
* relative, the position represents a fraction of the drawable
|
|
* width. Otherwise, the position represents an absolute value in
|
|
* pixels.
|
|
* @see #setPivotXRelative(boolean)
|
|
* @attr ref android.R.styleable#RotateDrawable_pivotX
|
|
*/
|
|
public void setPivotX(float pivotX) {
|
|
if (mState.mPivotX != pivotX) {
|
|
mState.mPivotX = pivotX;
|
|
invalidateSelf();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return X position around which to rotate
|
|
* @see #setPivotX(float)
|
|
* @attr ref android.R.styleable#RotateDrawable_pivotX
|
|
*/
|
|
public float getPivotX() {
|
|
return mState.mPivotX;
|
|
}
|
|
|
|
/**
|
|
* Sets whether the X pivot value represents a fraction of the drawable
|
|
* width or an absolute value in pixels.
|
|
*
|
|
* @param relative true if the X pivot represents a fraction of the drawable
|
|
* width, or false if it represents an absolute value in pixels
|
|
* @see #isPivotXRelative()
|
|
*/
|
|
public void setPivotXRelative(boolean relative) {
|
|
if (mState.mPivotXRel != relative) {
|
|
mState.mPivotXRel = relative;
|
|
invalidateSelf();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return true if the X pivot represents a fraction of the drawable width,
|
|
* or false if it represents an absolute value in pixels
|
|
* @see #setPivotXRelative(boolean)
|
|
*/
|
|
public boolean isPivotXRelative() {
|
|
return mState.mPivotXRel;
|
|
}
|
|
|
|
/**
|
|
* Sets the Y position around which the drawable is rotated.
|
|
*
|
|
* @param pivotY Y position around which to rotate. If the Y pivot is
|
|
* relative, the position represents a fraction of the drawable
|
|
* height. Otherwise, the position represents an absolute value
|
|
* in pixels.
|
|
* @see #getPivotY()
|
|
* @attr ref android.R.styleable#RotateDrawable_pivotY
|
|
*/
|
|
public void setPivotY(float pivotY) {
|
|
if (mState.mPivotY != pivotY) {
|
|
mState.mPivotY = pivotY;
|
|
invalidateSelf();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return Y position around which to rotate
|
|
* @see #setPivotY(float)
|
|
* @attr ref android.R.styleable#RotateDrawable_pivotY
|
|
*/
|
|
public float getPivotY() {
|
|
return mState.mPivotY;
|
|
}
|
|
|
|
/**
|
|
* Sets whether the Y pivot value represents a fraction of the drawable
|
|
* height or an absolute value in pixels.
|
|
*
|
|
* @param relative True if the Y pivot represents a fraction of the drawable
|
|
* height, or false if it represents an absolute value in pixels
|
|
* @see #isPivotYRelative()
|
|
*/
|
|
public void setPivotYRelative(boolean relative) {
|
|
if (mState.mPivotYRel != relative) {
|
|
mState.mPivotYRel = relative;
|
|
invalidateSelf();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @return true if the Y pivot represents a fraction of the drawable height,
|
|
* or false if it represents an absolute value in pixels
|
|
* @see #setPivotYRelative(boolean)
|
|
*/
|
|
public boolean isPivotYRelative() {
|
|
return mState.mPivotYRel;
|
|
}
|
|
|
|
@Override
|
|
protected boolean onLevelChange(int level) {
|
|
super.onLevelChange(level);
|
|
|
|
final float value = level / (float) MAX_LEVEL;
|
|
final float degrees = MathUtils.lerp(mState.mFromDegrees, mState.mToDegrees, value);
|
|
mState.mCurrentDegrees = degrees;
|
|
|
|
invalidateSelf();
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
DrawableWrapperState mutateConstantState() {
|
|
mState = new RotateState(mState);
|
|
return mState;
|
|
}
|
|
|
|
static final class RotateState extends DrawableWrapper.DrawableWrapperState {
|
|
boolean mPivotXRel = true;
|
|
float mPivotX = 0.5f;
|
|
boolean mPivotYRel = true;
|
|
float mPivotY = 0.5f;
|
|
float mFromDegrees = 0.0f;
|
|
float mToDegrees = 360.0f;
|
|
float mCurrentDegrees = 0.0f;
|
|
|
|
RotateState(RotateState orig) {
|
|
super(orig);
|
|
|
|
if (orig != null) {
|
|
mPivotXRel = orig.mPivotXRel;
|
|
mPivotX = orig.mPivotX;
|
|
mPivotYRel = orig.mPivotYRel;
|
|
mPivotY = orig.mPivotY;
|
|
mFromDegrees = orig.mFromDegrees;
|
|
mToDegrees = orig.mToDegrees;
|
|
mCurrentDegrees = orig.mCurrentDegrees;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public Drawable newDrawable(Resources res) {
|
|
return new RotateDrawable(this, res);
|
|
}
|
|
}
|
|
|
|
private RotateDrawable(RotateState state, Resources res) {
|
|
super(state, res);
|
|
|
|
mState = state;
|
|
}
|
|
}
|