From df03617f139d62950f936213882fe20f40f90103 Mon Sep 17 00:00:00 2001 From: Mis012 Date: Sat, 15 Feb 2025 21:22:17 +0100 Subject: [PATCH] api-impl: add ClipDrawable from AOSP taken from AOSP master (commit ed8c91e410671fa153f1587f61d05b303bf94f95) --- .../graphics/drawable/ClipDrawable.java | 238 ++++++++ .../android/graphics/drawable/Drawable.java | 8 + .../graphics/drawable/DrawableWrapper.java | 560 +++++++++++++++++- 3 files changed, 800 insertions(+), 6 deletions(-) create mode 100644 src/api-impl/android/graphics/drawable/ClipDrawable.java diff --git a/src/api-impl/android/graphics/drawable/ClipDrawable.java b/src/api-impl/android/graphics/drawable/ClipDrawable.java new file mode 100644 index 00000000..d35f3ace --- /dev/null +++ b/src/api-impl/android/graphics/drawable/ClipDrawable.java @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2006 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 android.annotation.NonNull; +import android.annotation.Nullable; +//import android.compat.annotation.UnsupportedAppUsage; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.Gravity; +import com.android.internal.R; +import java.io.IOException; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +/** + * A Drawable that clips another Drawable based on this Drawable's current + * level value. You can control how much the child Drawable gets clipped in width + * and height based on the level, as well as a gravity to control where it is + * placed in its overall container. Most often used to implement things like + * progress bars, by increasing the drawable's level with {@link + * android.graphics.drawable.Drawable#setLevel(int) setLevel()}. + *

Note: The drawable is clipped completely and not visible when + * the level is 0 and fully revealed when the level is 10,000.

+ * + *

It can be defined in an XML file with the <clip> element. For more + * information, see the guide to Drawable Resources.

+ * + * @attr ref android.R.styleable#ClipDrawable_clipOrientation + * @attr ref android.R.styleable#ClipDrawable_gravity + * @attr ref android.R.styleable#ClipDrawable_drawable + */ +public class ClipDrawable extends DrawableWrapper { + public static final int HORIZONTAL = 1; + public static final int VERTICAL = 2; + + private static final int MAX_LEVEL = 10000; + + private final Rect mTmpRect = new Rect(); + + //@UnsupportedAppUsage + private ClipState mState; + + ClipDrawable() { + this(new ClipState(null, null), null); + } + + /** + * Creates a new clip drawable with the specified gravity and orientation. + * + * @param drawable the drawable to clip + * @param gravity gravity constant (see {@link Gravity} used to position + * the clipped drawable within the parent container + * @param orientation bitwise-or of {@link #HORIZONTAL} and/or + * {@link #VERTICAL} + */ + public ClipDrawable(Drawable drawable, int gravity, int orientation) { + this(new ClipState(null, null), null); + + mState.mGravity = gravity; + mState.mOrientation = orientation; + + setDrawable(drawable); + } + + /*@Override + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ClipDrawable); + + // Inflation will advance the XmlPullParser and AttributeSet. + super.inflate(r, parser, attrs, theme); + + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + a.recycle(); + }*/ + + /*@Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + final ClipState state = mState; + if (state == null) { + return; + } + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.ClipDrawable); + try { + updateStateFromTypedArray(a); + verifyRequiredAttributes(a); + } catch (XmlPullParserException e) { + rethrowAsRuntimeException(e); + } finally { + a.recycle(); + } + } + }*/ + + private void verifyRequiredAttributes(@NonNull 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.ClipDrawable_drawable] == 0)) { + throw new XmlPullParserException(a.getPositionDescription() + ": tag requires a 'drawable' attribute or " + + "child tag defining a drawable"); + } + } + + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final ClipState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + //state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + state.mOrientation = a.getInt( + R.styleable.ClipDrawable_clipOrientation, state.mOrientation); + state.mGravity = a.getInt( + R.styleable.ClipDrawable_gravity, state.mGravity); + } + + /*@Override + protected boolean onLevelChange(int level) { + super.onLevelChange(level); + invalidateSelf(); + return true; + }*/ + + /*@Override + public int getOpacity() { + final Drawable dr = getDrawable(); + final int opacity = dr.getOpacity(); + if (opacity == PixelFormat.TRANSPARENT || dr.getLevel() == 0) { + return PixelFormat.TRANSPARENT; + } + + final int level = getLevel(); + if (level >= MAX_LEVEL) { + return dr.getOpacity(); + } + + // Some portion of non-transparent drawable is showing. + return PixelFormat.TRANSLUCENT; + }*/ + + @Override + public void draw(Canvas canvas) { + final Drawable dr = getDrawable(); + if (dr.getLevel() == 0) { + return; + } + + final Rect r = mTmpRect; + final Rect bounds = getBounds(); + final int level = getLevel(); + + int w = bounds.width(); + final int iw = 0; // mState.mDrawable.getIntrinsicWidth(); + if ((mState.mOrientation & HORIZONTAL) != 0) { + w -= (w - iw) * (MAX_LEVEL - level) / MAX_LEVEL; + } + + int h = bounds.height(); + final int ih = 0; // mState.mDrawable.getIntrinsicHeight(); + if ((mState.mOrientation & VERTICAL) != 0) { + h -= (h - ih) * (MAX_LEVEL - level) / MAX_LEVEL; + } + + //final int layoutDirection = getLayoutDirection(); + //Gravity.apply(mState.mGravity, w, h, bounds, r, layoutDirection); + + if (w > 0 && h > 0) { + canvas.save(); + canvas.clipRect(r); + dr.draw(canvas); + canvas.restore(); + } + } + + /*@Override + DrawableWrapperState mutateConstantState() { + mState = new ClipState(mState, null); + return mState; + }*/ + + static final class ClipState extends DrawableWrapper.DrawableWrapperState { + private int[] mThemeAttrs; + + int mOrientation = HORIZONTAL; + int mGravity = Gravity.LEFT; + + ClipState(ClipState orig, Resources res) { + super(orig, res); + + if (orig != null) { + mOrientation = orig.mOrientation; + mGravity = orig.mGravity; + } + } + + @Override + public Drawable newDrawable(Resources res) { + return new ClipDrawable(this, res); + } + } + + private ClipDrawable(ClipState state, Resources res) { + super(state, res); + + mState = state; + } +} diff --git a/src/api-impl/android/graphics/drawable/Drawable.java b/src/api-impl/android/graphics/drawable/Drawable.java index 0dfd5b4a..8825a3b1 100644 --- a/src/api-impl/android/graphics/drawable/Drawable.java +++ b/src/api-impl/android/graphics/drawable/Drawable.java @@ -268,6 +268,14 @@ public class Drawable { return new Drawable(paintable); } + public static Drawable createFromPath(String path) { + if (path == null) + return null; + + long paintable = native_paintable_from_path(path); + return new Drawable(paintable); + } + protected boolean onStateChange(int[] stateSet) { return false; } diff --git a/src/api-impl/android/graphics/drawable/DrawableWrapper.java b/src/api-impl/android/graphics/drawable/DrawableWrapper.java index 9990622e..fb1ce1d3 100644 --- a/src/api-impl/android/graphics/drawable/DrawableWrapper.java +++ b/src/api-impl/android/graphics/drawable/DrawableWrapper.java @@ -1,15 +1,563 @@ +/* + * Copyright (C) 2015 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 android.graphics.drawable.Drawable; +import android.annotation.NonNull; +import android.annotation.Nullable; +//import android.compat.annotation.UnsupportedAppUsage; +//import android.content.pm.ActivityInfo.Config; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.Resources.Theme; +import android.content.res.TypedArray; +//import android.graphics.BlendMode; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +//import android.graphics.Insets; +import android.graphics.Outline; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Xfermode; +import android.os.Build; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.view.View; +import com.android.internal.R; +import java.io.IOException; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; -public class DrawableWrapper extends Drawable { - private Drawable drawable; +/** + * Drawable container with only one child element. + */ +public abstract class DrawableWrapper extends Drawable implements Drawable.Callback { + //@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) + private DrawableWrapperState mState; + private Drawable mDrawable; + private boolean mMutated; - public DrawableWrapper(Drawable drawable) { - this.drawable = drawable; + DrawableWrapper(DrawableWrapperState state, Resources res) { + mState = state; + + updateLocalState(res); } + /** + * Creates a new wrapper around the specified drawable. + * + * @param dr the drawable to wrap + */ + public DrawableWrapper(@Nullable Drawable dr) { + mState = null; + setDrawable(dr); + } + + /** + * Initializes local dynamic properties from state. This should be called + * after significant state changes, e.g. from the One True Constructor and + * after inflating or applying a theme. + */ + private void updateLocalState(Resources res) { + if (mState != null && mState.mDrawableState != null) { + final Drawable dr = mState.mDrawableState.newDrawable(res); + setDrawable(dr); + } + } + + /** + * @hide + */ + /*@Override + public void setXfermode(Xfermode mode) { + if (mDrawable != null) { + mDrawable.setXfermode(mode); + } + }*/ + + /** + * Sets the wrapped drawable. + * + * @param dr the wrapped drawable + */ + public void setDrawable(@Nullable Drawable dr) { + if (mDrawable != null) { + mDrawable.setCallback(null); + } + + mDrawable = dr; + + if (dr != null) { + dr.setCallback(this); + + // Only call setters for data that's stored in the base Drawable. + dr.setVisible(isVisible(), true); + dr.setState(getState()); + dr.setLevel(getLevel()); + dr.setBounds(getBounds()); + //dr.setLayoutDirection(getLayoutDirection()); + + if (mState != null) { + mState.mDrawableState = dr.getConstantState(); + } + } + + invalidateSelf(); + } + + /** + * @return the wrapped drawable + */ + @Nullable public Drawable getDrawable() { - return drawable; + return mDrawable; + } + + /*@Override + public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + super.inflate(r, parser, attrs, theme); + + final DrawableWrapperState state = mState; + if (state == null) { + return; + } + + // The density may have changed since the last update. This will + // apply scaling to any existing constant state properties. + final int densityDpi = r.getDisplayMetrics().densityDpi; + final int targetDensity = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + state.setDensity(targetDensity); + state.mSrcDensityOverride = mSrcDensityOverride; + + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.DrawableWrapper); + updateStateFromTypedArray(a); + a.recycle(); + + inflateChildDrawable(r, parser, attrs, theme); + }*/ + + /*@Override + public void applyTheme(@NonNull Theme t) { + super.applyTheme(t); + + // If we load the drawable later as part of updating from the typed + // array, it will already be themed correctly. So, we can theme the + // local drawable first. + if (mDrawable != null && mDrawable.canApplyTheme()) { + mDrawable.applyTheme(t); + } + + final DrawableWrapperState state = mState; + if (state == null) { + return; + } + + final int densityDpi = t.getResources().getDisplayMetrics().densityDpi; + final int density = densityDpi == 0 ? DisplayMetrics.DENSITY_DEFAULT : densityDpi; + state.setDensity(density); + + if (state.mThemeAttrs != null) { + final TypedArray a = t.resolveAttributes( + state.mThemeAttrs, R.styleable.DrawableWrapper); + updateStateFromTypedArray(a); + a.recycle(); + } + }*/ + + /** + * Updates constant state properties from the provided typed array. + *

+ * Implementing subclasses should call through to the super method first. + * + * @param a the typed array rom which properties should be read + */ + private void updateStateFromTypedArray(@NonNull TypedArray a) { + final DrawableWrapperState state = mState; + if (state == null) { + return; + } + + // Account for any configuration changes. + state.mChangingConfigurations |= a.getChangingConfigurations(); + + // Extract the theme attributes, if any. + state.mThemeAttrs = a.extractThemeAttrs(); + + if (a.hasValueOrEmpty(R.styleable.DrawableWrapper_drawable)) { + setDrawable(a.getDrawable(R.styleable.DrawableWrapper_drawable)); + } + } + + /*@Override + public boolean canApplyTheme() { + return (mState != null && mState.canApplyTheme()) || super.canApplyTheme(); + }*/ + + @Override + public void invalidateDrawable(@NonNull Drawable who) { + final Callback callback = getCallback(); + if (callback != null) { + callback.invalidateDrawable(this); + } + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + final Callback callback = getCallback(); + if (callback != null) { + callback.scheduleDrawable(this, what, when); + } + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + final Callback callback = getCallback(); + if (callback != null) { + callback.unscheduleDrawable(this, what); + } + } + + @Override + public void draw(@NonNull Canvas canvas) { + if (mDrawable != null) { + mDrawable.draw(canvas); + } + } + + @Override + public /*@Config*/ int getChangingConfigurations() { + return super.getChangingConfigurations() | (mState != null ? mState.getChangingConfigurations() : 0) | mDrawable.getChangingConfigurations(); + } + + @Override + public boolean getPadding(@NonNull Rect padding) { + return mDrawable != null && mDrawable.getPadding(padding); + } + + /*@Override + public Insets getOpticalInsets() { + return mDrawable != null ? mDrawable.getOpticalInsets() : Insets.NONE; + }*/ + + @Override + public void setHotspot(float x, float y) { + if (mDrawable != null) { + mDrawable.setHotspot(x, y); + } + } + + /*@Override + public void setHotspotBounds(int left, int top, int right, int bottom) { + if (mDrawable != null) { + mDrawable.setHotspotBounds(left, top, right, bottom); + } + }*/ + + /*@Override + public void getHotspotBounds(@NonNull Rect outRect) { + if (mDrawable != null) { + mDrawable.getHotspotBounds(outRect); + } else { + outRect.set(getBounds()); + } + }*/ + + @Override + public boolean setVisible(boolean visible, boolean restart) { + final boolean superChanged = super.setVisible(visible, restart); + final boolean changed = mDrawable != null && mDrawable.setVisible(visible, restart); + return superChanged | changed; + } + + @Override + public void setAlpha(int alpha) { + if (mDrawable != null) { + mDrawable.setAlpha(alpha); + } + } + + /*@Override + public int getAlpha() { + return mDrawable != null ? mDrawable.getAlpha() : 255; + }*/ + + @Override + public void setColorFilter(@Nullable ColorFilter colorFilter) { + if (mDrawable != null) { + mDrawable.setColorFilter(colorFilter); + } + } + + /*@Override + public ColorFilter getColorFilter() { + final Drawable drawable = getDrawable(); + if (drawable != null) { + return drawable.getColorFilter(); + } + return super.getColorFilter(); + }*/ + + @Override + public void setTintList(@Nullable ColorStateList tint) { + if (mDrawable != null) { + mDrawable.setTintList(tint); + } + } + + /*@Override + public void setTintBlendMode(@NonNull BlendMode blendMode) { + if (mDrawable != null) { + mDrawable.setTintBlendMode(blendMode); + } + }*/ + + /*@Override + public boolean onLayoutDirectionChanged(@View.ResolvedLayoutDir int layoutDirection) { + return mDrawable != null && mDrawable.setLayoutDirection(layoutDirection); + }*/ + + /*@Override + public int getOpacity() { + return mDrawable != null ? mDrawable.getOpacity() : PixelFormat.TRANSPARENT; + }*/ + + @Override + public boolean isStateful() { + return mDrawable != null && mDrawable.isStateful(); + } + + /*@Override + public boolean hasFocusStateSpecified() { + return mDrawable != null && mDrawable.hasFocusStateSpecified(); + }*/ + + @Override + protected boolean onStateChange(@NonNull int[] state) { + if (mDrawable != null && mDrawable.isStateful()) { + final boolean changed = mDrawable.setState(state); + if (changed) { + onBoundsChange(getBounds()); + } + return changed; + } + return false; + } + + @Override + public void jumpToCurrentState() { + if (mDrawable != null) { + mDrawable.jumpToCurrentState(); + } + } + + /*@Override + protected boolean onLevelChange(int level) { + return mDrawable != null && mDrawable.setLevel(level); + }*/ + + @Override + protected void onBoundsChange(@NonNull Rect bounds) { + if (mDrawable != null) { + mDrawable.setBounds(bounds); + } + } + + @Override + public int getIntrinsicWidth() { + return mDrawable != null ? mDrawable.getIntrinsicWidth() : -1; + } + + @Override + public int getIntrinsicHeight() { + return mDrawable != null ? mDrawable.getIntrinsicHeight() : -1; + } + + /*@Override + public void getOutline(@NonNull Outline outline) { + if (mDrawable != null) { + mDrawable.getOutline(outline); + } else { + super.getOutline(outline); + } + }*/ + + /*@Override + @Nullable + public ConstantState getConstantState() { + if (mState != null && mState.canConstantState()) { + mState.mChangingConfigurations = getChangingConfigurations(); + return mState; + } + return null; + }*/ + + @Override + @NonNull + public Drawable mutate() { + if (!mMutated && super.mutate() == this) { + mState = mutateConstantState(); + if (mDrawable != null) { + mDrawable.mutate(); + } + if (mState != null) { + mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null; + } + mMutated = true; + } + return this; + } + + /** + * Mutates the constant state and returns the new state. Responsible for + * updating any local copy. + *

+ * This method should never call the super implementation; it should always + * mutate and return its own constant state. + * + * @return the new state + */ + DrawableWrapperState mutateConstantState() { + return mState; + } + + /** + * @hide Only used by the framework for pre-loading resources. + */ + /*public void clearMutated() { + super.clearMutated(); + if (mDrawable != null) { + mDrawable.clearMutated(); + } + mMutated = false; + }*/ + + /** + * Called during inflation to inflate the child element. The last valid + * child element will take precedence over any other child elements or + * explicit drawable attribute. + */ + /* void inflateChildDrawable(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { + // Seek to the first child element. + Drawable dr = null; + int type; + final int outerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.START_TAG) { + dr = Drawable.createFromXmlInnerForDensity(r, parser, attrs, + mState.mSrcDensityOverride, theme); + } + } + + if (dr != null) { + setDrawable(dr); + } + }*/ + + abstract static class DrawableWrapperState extends Drawable.ConstantState { + private int[] mThemeAttrs; + + //@Config + int mChangingConfigurations; + int mDensity = DisplayMetrics.DENSITY_DEFAULT; + + /** + * The density to use when looking up resources from + * {@link Resources#getDrawableForDensity(int, int, Theme)}. + * A value of 0 means there is no override and the system density will be used. + * @hide + */ + int mSrcDensityOverride = 0; + + Drawable.ConstantState mDrawableState; + + DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) { + if (orig != null) { + mThemeAttrs = orig.mThemeAttrs; + mChangingConfigurations = orig.mChangingConfigurations; + mDrawableState = orig.mDrawableState; + mSrcDensityOverride = orig.mSrcDensityOverride; + } + + final int density; + if (res != null) { + density = res.getDisplayMetrics().densityDpi; + } else if (orig != null) { + density = orig.mDensity; + } else { + density = 0; + } + + mDensity = density == 0 ? DisplayMetrics.DENSITY_DEFAULT : density; + } + + /** + * Sets the constant state density. + *

+ * If the density has been previously set, dispatches the change to + * subclasses so that density-dependent properties may be scaled as + * necessary. + * + * @param targetDensity the new constant state density + */ + public final void setDensity(int targetDensity) { + if (mDensity != targetDensity) { + final int sourceDensity = mDensity; + mDensity = targetDensity; + + onDensityChanged(sourceDensity, targetDensity); + } + } + + /** + * Called when the constant state density changes. + *

+ * Subclasses with density-dependent constant state properties should + * override this method and scale their properties as necessary. + * + * @param sourceDensity the previous constant state density + * @param targetDensity the new constant state density + */ + void onDensityChanged(int sourceDensity, int targetDensity) { + // Stub method. + } + + /*@Override + public boolean canApplyTheme() { + return mThemeAttrs != null || (mDrawableState != null && mDrawableState.canApplyTheme()) || super.canApplyTheme(); + }*/ + + @Override + public Drawable newDrawable() { + return newDrawable(null); + } + + @Override + public abstract Drawable newDrawable(@Nullable Resources res); + + @Override + public /*@Config*/ int getChangingConfigurations() { + return mChangingConfigurations | (mDrawableState != null ? mDrawableState.getChangingConfigurations() : 0); + } + + public boolean canConstantState() { + return mDrawableState != null; + } } }