1 /* 2 * Copyright (C) 2015 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.support.design.internal; 18 19 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.graphics.Canvas; 24 import android.graphics.Rect; 25 import android.graphics.drawable.Drawable; 26 import android.support.annotation.NonNull; 27 import android.support.annotation.RequiresApi; 28 import android.support.annotation.RestrictTo; 29 import android.support.design.R; 30 import android.support.v7.widget.LinearLayoutCompat; 31 import android.util.AttributeSet; 32 import android.view.Gravity; 33 34 /** 35 * @hide 36 */ 37 @RestrictTo(LIBRARY_GROUP) 38 public class ForegroundLinearLayout extends LinearLayoutCompat { 39 40 private Drawable mForeground; 41 42 private final Rect mSelfBounds = new Rect(); 43 44 private final Rect mOverlayBounds = new Rect(); 45 46 private int mForegroundGravity = Gravity.FILL; 47 48 protected boolean mForegroundInPadding = true; 49 50 boolean mForegroundBoundsChanged = false; 51 ForegroundLinearLayout(Context context)52 public ForegroundLinearLayout(Context context) { 53 this(context, null); 54 } 55 ForegroundLinearLayout(Context context, AttributeSet attrs)56 public ForegroundLinearLayout(Context context, AttributeSet attrs) { 57 this(context, attrs, 0); 58 } 59 ForegroundLinearLayout(Context context, AttributeSet attrs, int defStyle)60 public ForegroundLinearLayout(Context context, AttributeSet attrs, int defStyle) { 61 super(context, attrs, defStyle); 62 63 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundLinearLayout, 64 defStyle, 0); 65 66 mForegroundGravity = a.getInt( 67 R.styleable.ForegroundLinearLayout_android_foregroundGravity, mForegroundGravity); 68 69 final Drawable d = a.getDrawable(R.styleable.ForegroundLinearLayout_android_foreground); 70 if (d != null) { 71 setForeground(d); 72 } 73 74 mForegroundInPadding = a.getBoolean( 75 R.styleable.ForegroundLinearLayout_foregroundInsidePadding, true); 76 77 a.recycle(); 78 } 79 80 /** 81 * Describes how the foreground is positioned. 82 * 83 * @return foreground gravity. 84 * @see #setForegroundGravity(int) 85 */ 86 @Override getForegroundGravity()87 public int getForegroundGravity() { 88 return mForegroundGravity; 89 } 90 91 /** 92 * Describes how the foreground is positioned. Defaults to START and TOP. 93 * 94 * @param foregroundGravity See {@link android.view.Gravity} 95 * @see #getForegroundGravity() 96 */ 97 @Override setForegroundGravity(int foregroundGravity)98 public void setForegroundGravity(int foregroundGravity) { 99 if (mForegroundGravity != foregroundGravity) { 100 if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 101 foregroundGravity |= Gravity.START; 102 } 103 104 if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 105 foregroundGravity |= Gravity.TOP; 106 } 107 108 mForegroundGravity = foregroundGravity; 109 110 if (mForegroundGravity == Gravity.FILL && mForeground != null) { 111 Rect padding = new Rect(); 112 mForeground.getPadding(padding); 113 } 114 115 requestLayout(); 116 } 117 } 118 119 @Override verifyDrawable(Drawable who)120 protected boolean verifyDrawable(Drawable who) { 121 return super.verifyDrawable(who) || (who == mForeground); 122 } 123 124 @RequiresApi(11) 125 @Override jumpDrawablesToCurrentState()126 public void jumpDrawablesToCurrentState() { 127 super.jumpDrawablesToCurrentState(); 128 if (mForeground != null) { 129 mForeground.jumpToCurrentState(); 130 } 131 } 132 133 @Override drawableStateChanged()134 protected void drawableStateChanged() { 135 super.drawableStateChanged(); 136 if (mForeground != null && mForeground.isStateful()) { 137 mForeground.setState(getDrawableState()); 138 } 139 } 140 141 /** 142 * Supply a Drawable that is to be rendered on top of all of the child 143 * views in the frame layout. Any padding in the Drawable will be taken 144 * into account by ensuring that the children are inset to be placed 145 * inside of the padding area. 146 * 147 * @param drawable The Drawable to be drawn on top of the children. 148 */ 149 @Override setForeground(Drawable drawable)150 public void setForeground(Drawable drawable) { 151 if (mForeground != drawable) { 152 if (mForeground != null) { 153 mForeground.setCallback(null); 154 unscheduleDrawable(mForeground); 155 } 156 157 mForeground = drawable; 158 159 if (drawable != null) { 160 setWillNotDraw(false); 161 drawable.setCallback(this); 162 if (drawable.isStateful()) { 163 drawable.setState(getDrawableState()); 164 } 165 if (mForegroundGravity == Gravity.FILL) { 166 Rect padding = new Rect(); 167 drawable.getPadding(padding); 168 } 169 } else { 170 setWillNotDraw(true); 171 } 172 requestLayout(); 173 invalidate(); 174 } 175 } 176 177 /** 178 * Returns the drawable used as the foreground of this FrameLayout. The 179 * foreground drawable, if non-null, is always drawn on top of the children. 180 * 181 * @return A Drawable or null if no foreground was set. 182 */ 183 @Override getForeground()184 public Drawable getForeground() { 185 return mForeground; 186 } 187 188 @Override onLayout(boolean changed, int left, int top, int right, int bottom)189 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 190 super.onLayout(changed, left, top, right, bottom); 191 mForegroundBoundsChanged |= changed; 192 } 193 194 @Override onSizeChanged(int w, int h, int oldw, int oldh)195 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 196 super.onSizeChanged(w, h, oldw, oldh); 197 mForegroundBoundsChanged = true; 198 } 199 200 @Override draw(@onNull Canvas canvas)201 public void draw(@NonNull Canvas canvas) { 202 super.draw(canvas); 203 204 if (mForeground != null) { 205 final Drawable foreground = mForeground; 206 207 if (mForegroundBoundsChanged) { 208 mForegroundBoundsChanged = false; 209 final Rect selfBounds = mSelfBounds; 210 final Rect overlayBounds = mOverlayBounds; 211 212 final int w = getRight() - getLeft(); 213 final int h = getBottom() - getTop(); 214 215 if (mForegroundInPadding) { 216 selfBounds.set(0, 0, w, h); 217 } else { 218 selfBounds.set(getPaddingLeft(), getPaddingTop(), 219 w - getPaddingRight(), h - getPaddingBottom()); 220 } 221 222 Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), 223 foreground.getIntrinsicHeight(), selfBounds, overlayBounds); 224 foreground.setBounds(overlayBounds); 225 } 226 227 foreground.draw(canvas); 228 } 229 } 230 231 @RequiresApi(21) 232 @Override drawableHotspotChanged(float x, float y)233 public void drawableHotspotChanged(float x, float y) { 234 super.drawableHotspotChanged(x, y); 235 if (mForeground != null) { 236 mForeground.setHotspot(x, y); 237 } 238 } 239 240 } 241