/* * Copyright (C) 2018 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 androidx.appcompat.graphics.drawable; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.LOLLIPOP; import static androidx.core.content.res.TypedArrayUtils.obtainAttributes; import android.content.Context; import android.content.res.Resources; import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.StateSet; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.RequiresApi; import androidx.appcompat.resources.Compatibility; import androidx.appcompat.resources.R; import androidx.appcompat.widget.ResourceManagerInternal; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.Arrays; /** * Lets you assign a number of graphic images to a single Drawable and swap out the visible item by * a string ID value. *
* It can be defined in an XML file with the <selector>
element.
* Each state Drawable is defined in a nested <item>
element. For more
* information, see the guide to
* Drawable Resources.
*
* {@link android.R.attr#visible}
* {@link android.R.attr#variablePadding}
* {@link android.R.attr#constantSize}
* {@link android.R.attr#state_focused}
* {@link android.R.attr#state_window_focused}
* {@link android.R.attr#state_enabled}
* {@link android.R.attr#state_checkable}
* {@link android.R.attr#state_checked}
* {@link android.R.attr#state_selected}
* {@link android.R.attr#state_activated}
* {@link android.R.attr#state_active}
* {@link android.R.attr#state_single}
* {@link android.R.attr#state_first}
* {@link android.R.attr#state_middle}
* {@link android.R.attr#state_last}
* {@link android.R.attr#state_pressed}
*
* Adapted from platform class; altered with API level checks as necessary & uses
* {@code ResourceManagerInternal} for Drawable
inflation.
*/
public class StateListDrawableCompat extends DrawableContainerCompat {
private static final String TAG = "StateListDrawableCompat";
private static final boolean DEBUG = false;
private StateListState mStateListState;
private boolean mMutated;
/**
* Creates an empty state list drawable.
*/
public StateListDrawableCompat() {
this(null, null);
}
/**
* Add a new image/string ID to the set of images.
*
* @param stateSet - An array of resource Ids to associate with the image.
* Switch to this image by calling setState().
* @param drawable -The image to show.
*/
public void addState(int[] stateSet, Drawable drawable) {
if (drawable != null) {
mStateListState.addStateSet(stateSet, drawable);
// in case the new state matches our current state...
onStateChange(getState());
}
}
@Override
public boolean isStateful() {
return true;
}
@Override
protected boolean onStateChange(@NonNull int[] stateSet) {
final boolean changed = super.onStateChange(stateSet);
int idx = mStateListState.indexOfStateSet(stateSet);
if (DEBUG) {
android.util.Log.i(TAG, "onStateChange " + this + " states "
+ Arrays.toString(stateSet) + " found " + idx);
}
if (idx < 0) {
idx = mStateListState.indexOfStateSet(StateSet.WILD_CARD);
}
return selectDrawable(idx) || changed;
}
/**
* Inflate this Drawable from an XML resource optionally styled by a theme.
* This can't be called more than once for each Drawable.
*
* @param r Resources used to resolve attribute values
* @param parser XML parser from which to inflate this Drawable
* @param attrs Base set of attribute values
* @param theme Theme to apply, may be null
* @throws XmlPullParserException
* @throws IOException
*/
public void inflate(
@NonNull Context context,
@NonNull Resources r,
@NonNull XmlPullParser parser,
@NonNull AttributeSet attrs,
@Nullable Theme theme)
throws XmlPullParserException, IOException {
final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.StateListDrawable);
setVisible(a.getBoolean(R.styleable.StateListDrawable_android_visible, true), true);
updateStateFromTypedArray(a);
updateDensity(r);
a.recycle();
inflateChildElements(context, r, parser, attrs, theme);
onStateChange(getState());
}
/**
* Updates the constant state from the values in the typed array.
*/
private void updateStateFromTypedArray(TypedArray a) {
final StateListState state = mStateListState;
// Account for any configuration changes.
if (SDK_INT >= LOLLIPOP) {
state.mChangingConfigurations |= Compatibility.Api21Impl.getChangingConfigurations(a);
}
state.mVariablePadding = a.getBoolean(
R.styleable.StateListDrawable_android_variablePadding, state.mVariablePadding);
state.mConstantSize = a.getBoolean(
R.styleable.StateListDrawable_android_constantSize, state.mConstantSize);
state.mEnterFadeDuration = a.getInt(
R.styleable.StateListDrawable_android_enterFadeDuration, state.mEnterFadeDuration);
state.mExitFadeDuration = a.getInt(
R.styleable.StateListDrawable_android_exitFadeDuration, state.mExitFadeDuration);
state.mDither = a.getBoolean(
R.styleable.StateListDrawable_android_dither, state.mDither);
}
/**
* Inflates child elements from XML.
*/
private void inflateChildElements(Context context, Resources r,
XmlPullParser parser, AttributeSet attrs,
Theme theme) throws XmlPullParserException, IOException {
final StateListState state = mStateListState;
final int innerDepth = parser.getDepth() + 1;
int type;
int depth;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& ((depth = parser.getDepth()) >= innerDepth
|| type != XmlPullParser.END_TAG)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
if (depth > innerDepth || !parser.getName().equals("item")) {
continue;
}
// This allows state list drawable item elements to be themed at
// inflation time but does NOT make them work for Zygote preload.
final TypedArray a = obtainAttributes(r, theme, attrs,
R.styleable.StateListDrawableItem);
Drawable dr = null;
final int drawableId = a.getResourceId(
R.styleable.StateListDrawableItem_android_drawable, -1);
if (drawableId > 0) {
dr = ResourceManagerInternal.get().getDrawable(context, drawableId);
}
a.recycle();
final int[] states = extractStateSet(attrs);
// Loading child elements modifies the state of the AttributeSet's
// underlying parser, so it needs to happen after obtaining
// attributes and extracting states.
if (dr == null) {
while ((type = parser.next()) == XmlPullParser.TEXT) {
// no-op
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException(
parser.getPositionDescription()
+ ":