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 org.xmlpull.v1.XmlPullParser;
     22 import org.xmlpull.v1.XmlPullParserException;
     23 
     24 import android.annotation.NonNull;
     25 import android.content.res.Resources;
     26 import android.content.res.Resources.Theme;
     27 import android.content.res.TypedArray;
     28 import android.graphics.Insets;
     29 import android.graphics.Outline;
     30 import android.graphics.PixelFormat;
     31 import android.graphics.Rect;
     32 import android.util.AttributeSet;
     33 
     34 import java.io.IOException;
     35 
     36 /**
     37  * A Drawable that insets another Drawable by a specified distance.
     38  * This is used when a View needs a background that is smaller than
     39  * the View's actual bounds.
     40  *
     41  * <p>It can be defined in an XML file with the <code>&lt;inset></code> element. For more
     42  * information, see the guide to <a
     43  * href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.</p>
     44  *
     45  * @attr ref android.R.styleable#InsetDrawable_visible
     46  * @attr ref android.R.styleable#InsetDrawable_drawable
     47  * @attr ref android.R.styleable#InsetDrawable_insetLeft
     48  * @attr ref android.R.styleable#InsetDrawable_insetRight
     49  * @attr ref android.R.styleable#InsetDrawable_insetTop
     50  * @attr ref android.R.styleable#InsetDrawable_insetBottom
     51  */
     52 public class InsetDrawable extends DrawableWrapper {
     53     private final Rect mTmpRect = new Rect();
     54 
     55     private InsetState mState;
     56 
     57     /**
     58      * No-arg constructor used by drawable inflation.
     59      */
     60     InsetDrawable() {
     61         this(new InsetState(null), null);
     62     }
     63 
     64     /**
     65      * Creates a new inset drawable with the specified inset.
     66      *
     67      * @param drawable The drawable to inset.
     68      * @param inset Inset in pixels around the drawable.
     69      */
     70     public InsetDrawable(Drawable drawable, int inset) {
     71         this(drawable, inset, inset, inset, inset);
     72     }
     73 
     74     /**
     75      * Creates a new inset drawable with the specified insets.
     76      *
     77      * @param drawable The drawable to inset.
     78      * @param insetLeft Left inset in pixels.
     79      * @param insetTop Top inset in pixels.
     80      * @param insetRight Right inset in pixels.
     81      * @param insetBottom Bottom inset in pixels.
     82      */
     83     public InsetDrawable(Drawable drawable, int insetLeft, int insetTop,int insetRight,
     84             int insetBottom) {
     85         this(new InsetState(null), null);
     86 
     87         mState.mInsetLeft = insetLeft;
     88         mState.mInsetTop = insetTop;
     89         mState.mInsetRight = insetRight;
     90         mState.mInsetBottom = insetBottom;
     91 
     92         setDrawable(drawable);
     93     }
     94 
     95     @Override
     96     public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
     97             throws XmlPullParserException, IOException {
     98         super.inflate(r, parser, attrs, theme);
     99 
    100         final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.InsetDrawable);
    101         updateStateFromTypedArray(a);
    102         inflateChildDrawable(r, parser, attrs, theme);
    103         verifyRequiredAttributes(a);
    104         a.recycle();
    105     }
    106 
    107     private void verifyRequiredAttributes(TypedArray a) throws XmlPullParserException {
    108         // If we're not waiting on a theme, verify required attributes.
    109         if (getDrawable() == null && (mState.mThemeAttrs == null
    110                 || mState.mThemeAttrs[R.styleable.InsetDrawable_drawable] == 0)) {
    111             throw new XmlPullParserException(a.getPositionDescription()
    112                     + ": <inset> tag requires a 'drawable' attribute or "
    113                     + "child tag defining a drawable");
    114         }
    115     }
    116 
    117     @Override
    118     void updateStateFromTypedArray(TypedArray a) {
    119         super.updateStateFromTypedArray(a);
    120 
    121         final InsetState state = mState;
    122         final int N = a.getIndexCount();
    123         for (int i = 0; i < N; i++) {
    124             final int attr = a.getIndex(i);
    125             switch (attr) {
    126                 case R.styleable.InsetDrawable_drawable:
    127                     final Drawable dr = a.getDrawable(attr);
    128                     if (dr != null) {
    129                         setDrawable(dr);
    130                     }
    131                     break;
    132                 case R.styleable.InsetDrawable_inset:
    133                     final int inset = a.getDimensionPixelOffset(attr, Integer.MIN_VALUE);
    134                     if (inset != Integer.MIN_VALUE) {
    135                         state.mInsetLeft = inset;
    136                         state.mInsetTop = inset;
    137                         state.mInsetRight = inset;
    138                         state.mInsetBottom = inset;
    139                     }
    140                     break;
    141                 case R.styleable.InsetDrawable_insetLeft:
    142                     state.mInsetLeft = a.getDimensionPixelOffset(attr, state.mInsetLeft);
    143                     break;
    144                 case R.styleable.InsetDrawable_insetTop:
    145                     state.mInsetTop = a.getDimensionPixelOffset(attr, state.mInsetTop);
    146                     break;
    147                 case R.styleable.InsetDrawable_insetRight:
    148                     state.mInsetRight = a.getDimensionPixelOffset(attr, state.mInsetRight);
    149                     break;
    150                 case R.styleable.InsetDrawable_insetBottom:
    151                     state.mInsetBottom = a.getDimensionPixelOffset(attr, state.mInsetBottom);
    152                     break;
    153             }
    154         }
    155     }
    156 
    157     @Override
    158     public void applyTheme(Theme t) {
    159         final InsetState state = mState;
    160         if (state == null) {
    161             return;
    162         }
    163 
    164         if (state.mThemeAttrs != null) {
    165             final TypedArray a = t.resolveAttributes(state.mThemeAttrs, R.styleable.InsetDrawable);
    166             try {
    167                 updateStateFromTypedArray(a);
    168                 verifyRequiredAttributes(a);
    169             } catch (XmlPullParserException e) {
    170                 throw new RuntimeException(e);
    171             } finally {
    172                 a.recycle();
    173             }
    174         }
    175 
    176         // The drawable may have changed as a result of applying the theme, so
    177         // apply the theme to the wrapped drawable last.
    178         super.applyTheme(t);
    179     }
    180 
    181     @Override
    182     public boolean getPadding(Rect padding) {
    183         final boolean pad = super.getPadding(padding);
    184 
    185         padding.left += mState.mInsetLeft;
    186         padding.right += mState.mInsetRight;
    187         padding.top += mState.mInsetTop;
    188         padding.bottom += mState.mInsetBottom;
    189 
    190         return pad || (mState.mInsetLeft | mState.mInsetRight
    191                 | mState.mInsetTop | mState.mInsetBottom) != 0;
    192     }
    193 
    194     /** @hide */
    195     @Override
    196     public Insets getOpticalInsets() {
    197         final Insets contentInsets = super.getOpticalInsets();
    198         return Insets.of(contentInsets.left + mState.mInsetLeft,
    199                 contentInsets.top + mState.mInsetTop,
    200                 contentInsets.right + mState.mInsetRight,
    201                 contentInsets.bottom + mState.mInsetBottom);
    202     }
    203 
    204     @Override
    205     public int getOpacity() {
    206         final InsetState state = mState;
    207         final int opacity = getDrawable().getOpacity();
    208         if (opacity == PixelFormat.OPAQUE && (state.mInsetLeft > 0 || state.mInsetTop > 0
    209                 || state.mInsetRight > 0 || state.mInsetBottom > 0)) {
    210             return PixelFormat.TRANSLUCENT;
    211         }
    212         return opacity;
    213     }
    214 
    215     @Override
    216     protected void onBoundsChange(Rect bounds) {
    217         final Rect r = mTmpRect;
    218         r.set(bounds);
    219 
    220         r.left += mState.mInsetLeft;
    221         r.top += mState.mInsetTop;
    222         r.right -= mState.mInsetRight;
    223         r.bottom -= mState.mInsetBottom;
    224 
    225         // Apply inset bounds to the wrapped drawable.
    226         super.onBoundsChange(r);
    227     }
    228 
    229     @Override
    230     public int getIntrinsicWidth() {
    231         return getDrawable().getIntrinsicWidth() + mState.mInsetLeft + mState.mInsetRight;
    232     }
    233 
    234     @Override
    235     public int getIntrinsicHeight() {
    236         return getDrawable().getIntrinsicHeight() + mState.mInsetTop + mState.mInsetBottom;
    237     }
    238 
    239     @Override
    240     public void getOutline(@NonNull Outline outline) {
    241         getDrawable().getOutline(outline);
    242     }
    243 
    244     @Override
    245     DrawableWrapperState mutateConstantState() {
    246         mState = new InsetState(mState);
    247         return mState;
    248     }
    249 
    250     static final class InsetState extends DrawableWrapper.DrawableWrapperState {
    251         int mInsetLeft = 0;
    252         int mInsetTop = 0;
    253         int mInsetRight = 0;
    254         int mInsetBottom = 0;
    255 
    256         InsetState(InsetState orig) {
    257             super(orig);
    258 
    259             if (orig != null) {
    260                 mInsetLeft = orig.mInsetLeft;
    261                 mInsetTop = orig.mInsetTop;
    262                 mInsetRight = orig.mInsetRight;
    263                 mInsetBottom = orig.mInsetBottom;
    264             }
    265         }
    266 
    267         @Override
    268         public Drawable newDrawable(Resources res) {
    269             return new InsetDrawable(this, res);
    270         }
    271     }
    272 
    273     /**
    274      * The one constructor to rule them all. This is called by all public
    275      * constructors to set the state and initialize local properties.
    276      */
    277     private InsetDrawable(InsetState state, Resources res) {
    278         super(state, res);
    279 
    280         mState = state;
    281     }
    282 }
    283 
    284