1/* 2 * Copyright (C) 2014 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 17package android.support.v7.widget; 18 19import android.content.Context; 20import android.content.res.TypedArray; 21import android.graphics.Rect; 22import android.os.Build; 23import android.support.v7.cardview.R; 24import android.util.AttributeSet; 25import android.widget.FrameLayout; 26 27/** 28 * A FrameLayout with a rounded corner background and shadow. 29 * <p> 30 * CardView uses <code>elevation</code> property on L for shadows and falls back to a custom shadow 31 * implementation on older platforms. 32 * <p> 33 * Due to expensive nature of rounded corner clipping, on platforms before L, CardView does not 34 * clip its children that intersect with rounded corners. Instead, it adds padding to avoid such 35 * intersection (See {@link #setPreventCornerOverlap(boolean)} to change this behavior). 36 * <p> 37 * Before L, CardView adds padding to its content and draws shadows to that area. This padding 38 * amount is equal to <code>maxCardElevation + (1 - cos45) * cornerRadius</code> on the sides and 39 * <code>maxCardElevation * 1.5 + (1 - cos45) * cornerRadius</code> on top and bottom. 40 * <p> 41 * Since padding is used to offset content for shadows, you cannot set padding on CardView. 42 * Instead, 43 * you can use content padding attributes in XML or {@link #setContentPadding(int, int, int, int)} 44 * in code to set the padding between the edges of the Card and children of CardView. 45 * <p> 46 * Note that, if you specify exact dimensions for the CardView, because of the shadows, its content 47 * area will be different between platforms before L and after L. By using api version specific 48 * resource values, you can avoid these changes. Alternatively, If you want CardView to add inner 49 * padding on platforms L and after as well, you can set {@link #setUseCompatPadding(boolean)} to 50 * <code>true</code>. 51 * <p> 52 * To change CardView's elevation in a backward compatible way, use 53 * {@link #setCardElevation(float)}. CardView will use elevation API on L and before L, it will 54 * change the shadow size. To avoid moving the View while shadow size is changing, shadow size is 55 * clamped by {@link #getMaxCardElevation()}. If you want to change elevation dynamically, you 56 * should call {@link #setMaxCardElevation(float)} when CardView is initialized. 57 * 58 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor 59 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardCornerRadius 60 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation 61 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardMaxElevation 62 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardUseCompatPadding 63 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardPreventCornerOverlap 64 * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPadding 65 * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingLeft 66 * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingTop 67 * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingRight 68 * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingBottom 69 */ 70public class CardView extends FrameLayout implements CardViewDelegate { 71 72 private static final CardViewImpl IMPL; 73 74 static { 75 if (Build.VERSION.SDK_INT >= 21) { 76 IMPL = new CardViewApi21(); 77 } else if (Build.VERSION.SDK_INT >= 17) { 78 IMPL = new CardViewJellybeanMr1(); 79 } else { 80 IMPL = new CardViewEclairMr1(); 81 } 82 IMPL.initStatic(); 83 } 84 85 private boolean mCompatPadding; 86 87 private boolean mPreventCornerOverlap; 88 89 private final Rect mContentPadding = new Rect(); 90 91 private final Rect mShadowBounds = new Rect(); 92 93 94 public CardView(Context context) { 95 super(context); 96 initialize(context, null, 0); 97 } 98 99 public CardView(Context context, AttributeSet attrs) { 100 super(context, attrs); 101 initialize(context, attrs, 0); 102 } 103 104 public CardView(Context context, AttributeSet attrs, int defStyleAttr) { 105 super(context, attrs, defStyleAttr); 106 initialize(context, attrs, defStyleAttr); 107 } 108 109 @Override 110 public void setPadding(int left, int top, int right, int bottom) { 111 // NO OP 112 } 113 114 public void setPaddingRelative(int start, int top, int end, int bottom) { 115 // NO OP 116 } 117 118 /** 119 * Returns whether CardView will add inner padding on platforms L and after. 120 * 121 * @return True CardView adds inner padding on platforms L and after to have same dimensions 122 * with platforms before L. 123 */ 124 @Override 125 public boolean getUseCompatPadding() { 126 return mCompatPadding; 127 } 128 129 /** 130 * CardView adds additional padding to draw shadows on platforms before L. 131 * <p> 132 * This may cause Cards to have different sizes between L and before L. If you need to align 133 * CardView with other Views, you may need api version specific dimension resources to account 134 * for the changes. 135 * As an alternative, you can set this flag to <code>true</code> and CardView will add the same 136 * padding values on platforms L and after. 137 * <p> 138 * Since setting this flag to true adds unnecessary gaps in the UI, default value is 139 * <code>false</code>. 140 * 141 * @param useCompatPadding True if CardView should add padding for the shadows on platforms L 142 * and above. 143 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardUseCompatPadding 144 */ 145 public void setUseCompatPadding(boolean useCompatPadding) { 146 if (mCompatPadding == useCompatPadding) { 147 return; 148 } 149 mCompatPadding = useCompatPadding; 150 IMPL.onCompatPaddingChanged(this); 151 } 152 153 /** 154 * Sets the padding between the Card's edges and the children of CardView. 155 * <p> 156 * Depending on platform version or {@link #getUseCompatPadding()} settings, CardView may 157 * update these values before calling {@link android.view.View#setPadding(int, int, int, int)}. 158 * 159 * @param left The left padding in pixels 160 * @param top The top padding in pixels 161 * @param right The right padding in pixels 162 * @param bottom The bottom padding in pixels 163 * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPadding 164 * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingLeft 165 * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingTop 166 * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingRight 167 * @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingBottom 168 */ 169 public void setContentPadding(int left, int top, int right, int bottom) { 170 mContentPadding.set(left, top, right, bottom); 171 IMPL.updatePadding(this); 172 } 173 174 @Override 175 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 176 if (IMPL instanceof CardViewApi21 == false) { 177 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 178 switch (widthMode) { 179 case MeasureSpec.EXACTLY: 180 case MeasureSpec.AT_MOST: 181 final int minWidth = (int) Math.ceil(IMPL.getMinWidth(this)); 182 widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minWidth, 183 MeasureSpec.getSize(widthMeasureSpec)), widthMode); 184 break; 185 } 186 187 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 188 switch (heightMode) { 189 case MeasureSpec.EXACTLY: 190 case MeasureSpec.AT_MOST: 191 final int minHeight = (int) Math.ceil(IMPL.getMinHeight(this)); 192 heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minHeight, 193 MeasureSpec.getSize(heightMeasureSpec)), heightMode); 194 break; 195 } 196 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 197 } else { 198 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 199 } 200 } 201 202 private void initialize(Context context, AttributeSet attrs, int defStyleAttr) { 203 TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr, 204 R.style.CardView_Light); 205 int backgroundColor = a.getColor(R.styleable.CardView_cardBackgroundColor, 0); 206 float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0); 207 float elevation = a.getDimension(R.styleable.CardView_cardElevation, 0); 208 float maxElevation = a.getDimension(R.styleable.CardView_cardMaxElevation, 0); 209 mCompatPadding = a.getBoolean(R.styleable.CardView_cardUseCompatPadding, false); 210 mPreventCornerOverlap = a.getBoolean(R.styleable.CardView_cardPreventCornerOverlap, true); 211 int defaultPadding = a.getDimensionPixelSize(R.styleable.CardView_contentPadding, 0); 212 mContentPadding.left = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingLeft, 213 defaultPadding); 214 mContentPadding.top = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingTop, 215 defaultPadding); 216 mContentPadding.right = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingRight, 217 defaultPadding); 218 mContentPadding.bottom = a.getDimensionPixelSize(R.styleable.CardView_contentPaddingBottom, 219 defaultPadding); 220 if (elevation > maxElevation) { 221 maxElevation = elevation; 222 } 223 a.recycle(); 224 IMPL.initialize(this, context, backgroundColor, radius, elevation, maxElevation); 225 } 226 227 /** 228 * Updates the background color of the CardView 229 * 230 * @param color The new color to set for the card background 231 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor 232 */ 233 public void setCardBackgroundColor(int color) { 234 IMPL.setBackgroundColor(this, color); 235 } 236 237 /** 238 * Returns the inner padding after the Card's left edge 239 * 240 * @return the inner padding after the Card's left edge 241 */ 242 public int getContentPaddingLeft() { 243 return mContentPadding.left; 244 } 245 246 /** 247 * Returns the inner padding before the Card's right edge 248 * 249 * @return the inner padding before the Card's right edge 250 */ 251 public int getContentPaddingRight() { 252 return mContentPadding.right; 253 } 254 255 /** 256 * Returns the inner padding after the Card's top edge 257 * 258 * @return the inner padding after the Card's top edge 259 */ 260 public int getContentPaddingTop() { 261 return mContentPadding.top; 262 } 263 264 /** 265 * Returns the inner padding before the Card's bottom edge 266 * 267 * @return the inner padding before the Card's bottom edge 268 */ 269 public int getContentPaddingBottom() { 270 return mContentPadding.bottom; 271 } 272 273 /** 274 * Updates the corner radius of the CardView. 275 * 276 * @param radius The radius in pixels of the corners of the rectangle shape 277 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardCornerRadius 278 * @see #setRadius(float) 279 */ 280 public void setRadius(float radius) { 281 IMPL.setRadius(this, radius); 282 } 283 284 /** 285 * Returns the corner radius of the CardView. 286 * 287 * @return Corner radius of the CardView 288 * @see #getRadius() 289 */ 290 public float getRadius() { 291 return IMPL.getRadius(this); 292 } 293 294 /** 295 * Internal method used by CardView implementations to update the padding. 296 * 297 * @hide 298 */ 299 @Override 300 public void setShadowPadding(int left, int top, int right, int bottom) { 301 mShadowBounds.set(left, top, right, bottom); 302 super.setPadding(left + mContentPadding.left, top + mContentPadding.top, 303 right + mContentPadding.right, bottom + mContentPadding.bottom); 304 } 305 306 /** 307 * Updates the backward compatible elevation of the CardView. 308 * 309 * @param radius The backward compatible elevation in pixels. 310 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation 311 * @see #getCardElevation() 312 * @see #setMaxCardElevation(float) 313 */ 314 public void setCardElevation(float radius) { 315 IMPL.setElevation(this, radius); 316 } 317 318 /** 319 * Returns the backward compatible elevation of the CardView. 320 * 321 * @return Elevation of the CardView 322 * @see #setCardElevation(float) 323 * @see #getMaxCardElevation() 324 */ 325 public float getCardElevation() { 326 return IMPL.getElevation(this); 327 } 328 329 /** 330 * Updates the backward compatible elevation of the CardView. 331 * <p> 332 * Calling this method has no effect if device OS version is L or newer and 333 * {@link #getUseCompatPadding()} is <code>false</code>. 334 * 335 * @param radius The backward compatible elevation in pixels. 336 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardElevation 337 * @see #setCardElevation(float) 338 * @see #getMaxCardElevation() 339 */ 340 public void setMaxCardElevation(float radius) { 341 IMPL.setMaxElevation(this, radius); 342 } 343 344 /** 345 * Returns the backward compatible elevation of the CardView. 346 * 347 * @return Elevation of the CardView 348 * @see #setMaxCardElevation(float) 349 * @see #getCardElevation() 350 */ 351 public float getMaxCardElevation() { 352 return IMPL.getMaxElevation(this); 353 } 354 355 /** 356 * Returns whether CardView should add extra padding to content to avoid overlaps with rounded 357 * corners on API versions 20 and below. 358 * 359 * @return True if CardView prevents overlaps with rounded corners on platforms before L. 360 * Default value is <code>true</code>. 361 */ 362 @Override 363 public boolean getPreventCornerOverlap() { 364 return mPreventCornerOverlap; 365 } 366 367 /** 368 * On API 20 and before, CardView does not clip the bounds of the Card for the rounded corners. 369 * Instead, it adds padding to content so that it won't overlap with the rounded corners. 370 * You can disable this behavior by setting this field to <code>false</code>. 371 * <p> 372 * Setting this value on API 21 and above does not have any effect unless you have enabled 373 * compatibility padding. 374 * 375 * @param preventCornerOverlap Whether CardView should add extra padding to content to avoid 376 * overlaps with the CardView corners. 377 * @attr ref android.support.v7.cardview.R.styleable#CardView_cardPreventCornerOverlap 378 * @see #setUseCompatPadding(boolean) 379 */ 380 public void setPreventCornerOverlap(boolean preventCornerOverlap) { 381 if (preventCornerOverlap == mPreventCornerOverlap) { 382 return; 383 } 384 mPreventCornerOverlap = preventCornerOverlap; 385 IMPL.onPreventCornerOverlapChanged(this); 386 } 387} 388