Home | History | Annotate | Download | only in drawable
      1 /*
      2  * Copyright (C) 2008 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 android.annotation.NonNull;
     22 
     23 import org.xmlpull.v1.XmlPullParser;
     24 import org.xmlpull.v1.XmlPullParserException;
     25 
     26 import android.content.res.ColorStateList;
     27 import android.content.res.Resources;
     28 import android.content.res.TypedArray;
     29 import android.content.res.Resources.Theme;
     30 import android.graphics.Bitmap;
     31 import android.graphics.Canvas;
     32 import android.graphics.ColorFilter;
     33 import android.graphics.Insets;
     34 import android.graphics.Outline;
     35 import android.graphics.PixelFormat;
     36 import android.graphics.PorterDuff.Mode;
     37 import android.graphics.drawable.Drawable.ConstantState;
     38 import android.graphics.Rect;
     39 import android.util.AttributeSet;
     40 
     41 import java.io.IOException;
     42 import java.util.Collection;
     43 
     44 /**
     45  * A Drawable that insets another Drawable by a specified distance.
     46  * This is used when a View needs a background that is smaller than
     47  * the View's actual bounds.
     48  *
     49  * <p>It can be defined in an XML file with the <code>&lt;inset></code> element. For more
     50  * information, see the guide to <a
     51  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
     52  *
     53  * @attr ref android.R.styleable#InsetDrawable_visible
     54  * @attr ref android.R.styleable#InsetDrawable_drawable
     55  * @attr ref android.R.styleable#InsetDrawable_insetLeft
     56  * @attr ref android.R.styleable#InsetDrawable_insetRight
     57  * @attr ref android.R.styleable#InsetDrawable_insetTop
     58  * @attr ref android.R.styleable#InsetDrawable_insetBottom
     59  */
     60 public class InsetDrawable extends Drawable implements Drawable.Callback {
     61     private final Rect mTmpRect = new Rect();
     62 
     63     private final InsetState mState;
     64 
     65     private boolean mMutated;
     66 
     67     /*package*/ InsetDrawable() {
     68         this(null, null);
     69     }
     70 
     71     public InsetDrawable(Drawable drawable, int inset) {
     72         this(drawable, inset, inset, inset, inset);
     73     }
     74 
     75     public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,
     76                          int insetRight, int insetBottom) {
     77         this(null, null);
     78 
     79         mState.mDrawable = drawable;
     80         mState.mInsetLeft = insetLeft;
     81         mState.mInsetTop = insetTop;
     82         mState.mInsetRight = insetRight;
     83         mState.mInsetBottom = insetBottom;
     84 
     85         if (drawable != null) {
     86             drawable.setCallback(this);
     87         }
     88     }
     89 
     90     @Override
     91     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
     92             throws XmlPullParserException, IOException {
     93         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
     94         super.inflateWithAttributes(r, parser, a, R.styleable.InsetDrawable_visible);
     95 
     96         // Reset mDrawable to preserve old multiple-inflate behavior. This is
     97         // silly, but we have CTS tests that rely on it.
     98         mState.mDrawable = null;
     99 
    100         updateStateFromTypedArray(a);
    101         inflateChildElements(r, parser, attrs, theme);
    102         verifyRequiredAttributes(a);
    103         a.recycle();
    104     }
    105 
    106     private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs,
    107             Theme theme) throws XmlPullParserException, IOException {
    108         // Load inner XML elements.
    109         if (mState.mDrawable == null) {
    110             int type;
    111             while ((type=parser.next()) == XmlPullParser.TEXT) {
    112             }
    113             if (type != XmlPullParser.START_TAG) {
    114                 throw new XmlPullParserException(
    115                         parser.getPositionDescription()
    116                                 + ": <inset> tag requires a 'drawable' attribute or "
    117                                 + "child tag defining a drawable");
    118             }
    119             final Drawable dr = Drawable.createFromXmlInner(r, parser, attrs, theme);
    120             mState.mDrawable = dr;
    121             dr.setCallback(this);
    122         }
    123     }
    124 
    125     private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
    126         // If we're not waiting on a theme, verify required attributes.
    127         if (mState.mDrawable == null && (mState.mThemeAttrs == null
    128                 || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
    129             throw new XmlPullParserException(a.getPositionDescription()
    130                     + ": <inset> tag requires a 'drawable' attribute or "
    131                     + "child tag defining a drawable");
    132         }
    133     }
    134 
    135     private void updateStateFromTypedArray(TypedArray a) throws XmlPullParserException {
    136         final InsetState state = mState;
    137 
    138         // Account for any configuration changes.
    139         state.mChangingConfigurations |= a.getChangingConfigurations();
    140 
    141         // Extract the theme attributes, if any.
    142         state.mThemeAttrs = a.extractThemeAttrs();
    143 
    144         final int N = a.getIndexCount();
    145         for (int i = 0; i < N; i++) {
    146             final int attr = a.getIndex(i);
    147             switch (attr) {
    148                 case R.styleable.InsetDrawable_drawable:
    149                     final Drawable dr = a.getDrawable(attr);
    150                     if (dr != null) {
    151                         state.mDrawable = dr;
    152                         dr.setCallback(this);
    153                     }
    154                     break;
    155                 case R.styleable.InsetDrawable_inset:
    156                     final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE);
    157                     if (inset != Integer.MIN_VALUE) {
    158                         state.mInsetLeft = inset;
    159                         state.mInsetTop = inset;
    160                         state.mInsetRight = inset;
    161                         state.mInsetBottom = inset;
    162                     }
    163                     break;
    164                 case R.styleable.InsetDrawable_insetLeft:
    165                     state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft);
    166                     break;
    167                 case R.styleable.InsetDrawable_insetTop:
    168                     state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop);
    169                     break;
    170                 case R.styleable.InsetDrawable_insetRight:
    171                     state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight);
    172                     break;
    173                 case R.styleable.InsetDrawable_insetBottom:
    174                     state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom);
    175                     break;
    176             }
    177         }
    178     }
    179 
    180     @Override
    181     public void applyTheme(Theme t) {
    182         super.applyTheme(t);
    183 
    184         final InsetState state = mState;
    185         if (state == null) {
    186             return;
    187         }
    188 
    189         if (state.mThemeAttrs != null) {
    190             final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable);
    191             try {
    192                 updateStateFromTypedArray(a);
    193                 verifyRequiredAttributes(a);
    194             } catch (XmlPullParserException e) {
    195                 throw new RuntimeException(e);
    196             } finally {
    197                 a.recycle();
    198             }
    199         }
    200 
    201         if (state.mDrawable != null && state.mDrawable.canApplyTheme()) {
    202             state.mDrawable.applyTheme(t);
    203         }
    204     }
    205 
    206     @Override
    207     public boolean canApplyTheme() {
    208         return (mState != null && mState.canApplyTheme()) || super.canApplyTheme();
    209     }
    210 
    211     @Override
    212     public void invalidateDrawable(Drawable who) {
    213         final Callback callback = getCallback();
    214         if (callback != null) {
    215             callback.invalidateDrawable(this);
    216         }
    217     }
    218 
    219     @Override
    220     public void scheduleDrawable(Drawable who, Runnable what, long when) {
    221         final Callback callback = getCallback();
    222         if (callback != null) {
    223             callback.scheduleDrawable(this, what, when);
    224         }
    225     }
    226 
    227     @Override
    228     public void unscheduleDrawable(Drawable who, Runnable what) {
    229         final Callback callback = getCallback();
    230         if (callback != null) {
    231             callback.unscheduleDrawable(this, what);
    232         }
    233     }
    234 
    235     @Override
    236     public void draw(Canvas canvas) {
    237         mState.mDrawable.draw(canvas);
    238     }
    239 
    240     @Override
    241     public int getChangingConfigurations() {
    242         return super.getChangingConfigurations()
    243                 | mState.mChangingConfigurations
    244                 | mState.mDrawable.getChangingConfigurations();
    245     }
    246 
    247     @Override
    248     public boolean getPadding(Rect padding) {
    249         boolean pad = mState.mDrawable.getPadding(padding);
    250 
    251         padding.left += mState.mInsetLeft;
    252         padding.right += mState.mInsetRight;
    253         padding.top += mState.mInsetTop;
    254         padding.bottom += mState.mInsetBottom;
    255 
    256         return pad || (mState.mInsetLeft | mState.mInsetRight |
    257                 mState.mInsetTop | mState.mInsetBottom) != 0;
    258     }
    259 
    260     /** @hide */
    261     @Override
    262     public Insets getOpticalInsets() {
    263         final Insets contentInsets = super.getOpticalInsets();
    264         return Insets.of(contentInsets.left + mState.mInsetLeft,
    265                 contentInsets.top + mState.mInsetTop,
    266                 contentInsets.right + mState.mInsetRight,
    267                 contentInsets.bottom + mState.mInsetBottom);
    268     }
    269 
    270     @Override
    271     public void setHotspot(float x, float y) {
    272         mState.mDrawable.setHotspot(x, y);
    273     }
    274 
    275     @Override
    276     public void setHotspotBounds(int left, int top, int right, int bottom) {
    277         mState.mDrawable.setHotspotBounds(left, top, right, bottom);
    278     }
    279 
    280     /** @hide */
    281     @Override
    282     public void getHotspotBounds(Rect outRect) {
    283         mState.mDrawable.getHotspotBounds(outRect);
    284     }
    285 
    286     @Override
    287     public boolean setVisible(boolean visible, boolean restart) {
    288         mState.mDrawable.setVisible(visible, restart);
    289         return super.setVisible(visible, restart);
    290     }
    291 
    292     @Override
    293     public void setAlpha(int alpha) {
    294         mState.mDrawable.setAlpha(alpha);
    295     }
    296 
    297     @Override
    298     public int getAlpha() {
    299         return mState.mDrawable.getAlpha();
    300     }
    301 
    302     @Override
    303     public void setColorFilter(ColorFilter cf) {
    304         mState.mDrawable.setColorFilter(cf);
    305     }
    306 
    307     @Override
    308     public void setTintList(ColorStateList tint) {
    309         mState.mDrawable.setTintList(tint);
    310     }
    311 
    312     @Override
    313     public void setTintMode(Mode tintMode) {
    314         mState.mDrawable.setTintMode(tintMode);
    315     }
    316 
    317     /** {@hide} */
    318     @Override
    319     public void setLayoutDirection(int layoutDirection) {
    320         mState.mDrawable.setLayoutDirection(layoutDirection);
    321     }
    322 
    323     @Override
    324     public int getOpacity() {
    325         final InsetState state = mState;
    326         final int opacity = state.mDrawable.getOpacity();
    327         if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0
    328                 || state.mInsetRight > 0 || state.mInsetBottom > 0)) {
    329             return PixelFormat.TRANSLUCENT;
    330         }
    331         return opacity;
    332     }
    333 
    334     @Override
    335     public boolean isStateful() {
    336         return mState.mDrawable.isStateful();
    337     }
    338 
    339     @Override
    340     protected boolean onStateChange(int[] state) {
    341         boolean changed = mState.mDrawable.setState(state);
    342         onBoundsChange(getBounds());
    343         return changed;
    344     }
    345 
    346     @Override
    347     protected boolean onLevelChange(int level) {
    348         return mState.mDrawable.setLevel(level);
    349     }
    350 
    351     @Override
    352     protected void onBoundsChange(Rect bounds) {
    353         final Rect r = mTmpRect;
    354         r.set(bounds);
    355 
    356         r.left += mState.mInsetLeft;
    357         r.top += mState.mInsetTop;
    358         r.right -= mState.mInsetRight;
    359         r.bottom -= mState.mInsetBottom;
    360 
    361         mState.mDrawable.setBounds(r.left, r.top, r.right, r.bottom);
    362     }
    363 
    364     @Override
    365     public int getIntrinsicWidth() {
    366         return mState.mDrawable.getIntrinsicWidth()
    367                 + mState.mInsetLeft + mState.mInsetRight;
    368     }
    369 
    370     @Override
    371     public int getIntrinsicHeight() {
    372         return mState.mDrawable.getIntrinsicHeight()
    373                 + mState.mInsetTop + mState.mInsetBottom;
    374     }
    375 
    376     @Override
    377     public void getOutline(@NonNull Outline outline) {
    378         mState.mDrawable.getOutline(outline);
    379     }
    380 
    381     @Override
    382     public ConstantState getConstantState() {
    383         if (mState.canConstantState()) {
    384             mState.mChangingConfigurations = getChangingConfigurations();
    385             return mState;
    386         }
    387         return null;
    388     }
    389 
    390     @Override
    391     public Drawable mutate() {
    392         if (!mMutated && super.mutate() == this) {
    393             mState.mDrawable.mutate();
    394             mMutated = true;
    395         }
    396         return this;
    397     }
    398 
    399     /**
    400      * @hide
    401      */
    402     public void clearMutated() {
    403         super.clearMutated();
    404         mState.mDrawable.clearMutated();
    405         mMutated = false;
    406     }
    407 
    408     /**
    409      * Returns the drawable wrapped by this InsetDrawable. May be null.
    410      */
    411     public Drawable getDrawable() {
    412         return mState.mDrawable;
    413     }
    414 
    415     final static class InsetState extends ConstantState {
    416         int[] mThemeAttrs;
    417         int mChangingConfigurations;
    418 
    419         Drawable mDrawable;
    420 
    421         int mInsetLeft = 0;
    422         int mInsetTop = 0;
    423         int mInsetRight = 0;
    424         int mInsetBottom = 0;
    425 
    426         private boolean mCheckedConstantState;
    427         private boolean mCanConstantState;
    428 
    429         InsetState(InsetState orig, InsetDrawable owner, Resources res) {
    430             if (orig != null) {
    431                 mThemeAttrs = orig.mThemeAttrs;
    432                 mChangingConfigurations = orig.mChangingConfigurations;
    433                 if (res != null) {
    434                     mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
    435                 } else {
    436                     mDrawable = orig.mDrawable.getConstantState().newDrawable();
    437                 }
    438                 mDrawable.setCallback(owner);
    439                 mDrawable.setLayoutDirection(orig.mDrawable.getLayoutDirection());
    440                 mDrawable.setBounds(orig.mDrawable.getBounds());
    441                 mDrawable.setLevel(orig.mDrawable.getLevel());
    442                 mInsetLeft = orig.mInsetLeft;
    443                 mInsetTop = orig.mInsetTop;
    444                 mInsetRight = orig.mInsetRight;
    445                 mInsetBottom = orig.mInsetBottom;
    446                 mCheckedConstantState = mCanConstantState = true;
    447             }
    448         }
    449 
    450         @Override
    451         public boolean canApplyTheme() {
    452             return mThemeAttrs != null || (mDrawable != null && mDrawable.canApplyTheme())
    453                     || super.canApplyTheme();
    454         }
    455 
    456         @Override
    457         public Drawable newDrawable() {
    458             return new InsetDrawable(this, null);
    459         }
    460 
    461         @Override
    462         public Drawable newDrawable(Resources res) {
    463             return new InsetDrawable(this, res);
    464         }
    465 
    466         @Override
    467         public int getChangingConfigurations() {
    468             return mChangingConfigurations;
    469         }
    470 
    471         boolean canConstantState() {
    472             if (!mCheckedConstantState) {
    473                 mCanConstantState = mDrawable.getConstantState() != null;
    474                 mCheckedConstantState = true;
    475             }
    476 
    477             return mCanConstantState;
    478         }
    479 
    480         @Override
    481         public int addAtlasableBitmaps(Collection<Bitmap> atlasList) {
    482             final ConstantState state = mDrawable.getConstantState();
    483             if (state != null) {
    484                 return state.addAtlasableBitmaps(atlasList);
    485             }
    486             return 0;
    487         }
    488     }
    489 
    490     private InsetDrawable(InsetState state, Resources res) {
    491         mState = new InsetState(state, this, res);
    492     }
    493 }
    494 
    495