CardView Source Parsing-View Shadow

Keywords: Android xml Attribute nexus

CardView extends the FrameLayout class and allows you to display information within the card that is presented consistently across the platform.The CardView widget can have shadows and rounded corners.

If you want to create a card using shadows, use the card_view:cardElevation property.CardView uses true height and dynamic shadows in Android 5.0 (API level 21) and later versions, while earlier versions of Android returned programmatic shadows

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">
            <ImageView
                android:layout_width="match_parent"
                android:layout_height="160dp"
                android:scaleType="centerCrop"
                android:src="@drawable/balon" />
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical"
                android:padding="16dp">
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginBottom="8dp"
                    android:text="@string/card_title"
                    android:textColor="#000"
                    android:textSize="18sp" />
                <TextView
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:text="@string/card_content"
                    android:textColor="#555" />
            </LinearLayout>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">
                <Button
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/action_share"
                    android:theme="@style/PrimaryFlatButton" />
                <Button
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/action_explore"
                    android:theme="@style/PrimaryFlatButton" />
            </LinearLayout>
        </LinearLayout>
    </android.support.v7.widget.CardView>
</RelativeLayout>
Common properties of ## CardView: |Properties|Effects| | — | — | |card_view:cardElevation |Shadow Size |card_view:cardMaxElevation |Maximum Shadow Height |card_view:cardBackgroundColor |Background color of the card |card_view:cardCornerRadius |Card rounded size |card_view:contentPadding | Margin interval between card contents |card_view:contentPaddingBottom |Margin between card content and bottom |card_view:contentPaddingTop |Margin between card content and top |card_view:contentPaddingLeft |Margin between card content and left |card_view:contentPaddingRight |Margin between card content and right |card_view:contentPaddingStart |Start at margin interval for card content |card_view:contentPaddingEnd |Interval between card contents and margins |card_view:cardUseCompatPadding | Set the margin, Android 5.0 (code: Lollipop, API level 21) and above still have the same calculation as previous versions |card_view:cardPreventConrerOverlap | Add internal margins to versions prior to Android 5.0, this property to prevent overlap of content and corners The use of CardView is relatively simple, and there are quite a number of articles on the Internet to refer to. This article does not elaborate too much here. CardView compatibility considerations differ across different versions of the system, so let's look at a few small Demo s first. #### cardPreventConrerOverlap property: Lollipop version below, cardPreventConrerOverlap = false, does not set contentPadding, as shown in the figure, where the content and rounded corners overlap.

Lollipop version below, cardPreventConrerOverlap = true, does not set contentPadding, as shown in the figure, adding extra padding to prevent overlapping content and rounded corners.


#### cardUseCompatPadding property: To show the effect, I set up the elevation larger, test device nexus 4 768x1280. The following figure shows the following Lollipop version on the left, Lollipop version on the right, and cardUseCompatPadding = false.


The following figure shows the Lollipop version on the left, Lollipop version on the right, and cardUseCompatPadding = true.

You can see that if you want the internal margins of the Lollipop version and above to be the same as those below the Lollipop version, you need to set this property to true..

But the effect of this property is not as simple as you might think.
Let's add another control to the layout, cardUseCompatPadding= false.



The gap is obvious enough!So how can we ensure the same display for each version?Set cardUseCompatPadding=true.


ok, in fact, the root cause of this problem is that the shading effect of view differs in different versions of implementation, as will be discussed in the source analysis below.

There is another way to solve this problem.Do not set the cardUseCompatPadding property to true, fill in the margin = 0dp of cardview in the corresponding dimens.xml below the Lollipop version, and set the required value in dimens.xml under the Lollipop version (that is, the values-21 folder).

If you want to give CardView a clear width and height?

What content areas are different in size?This kind of problem to be tested Mei eyebrow discovery is not too disgraceful, how to solve it?Set the cardUseCompatPadding property to true.

Similarly, this issue can be adapted to different system versions of dimens.xml.

###To summarize - CardView sets the shadow of the view through the elevation property, but previous versions of Lollipop were simulated implementations, that is, they were implemented differently. - Because clipping is performance intensive, previous versions of Lollipop did not clip internal views, avoiding overlapping internal views and rounded corners by adding padding.This behavior can be changed using the setPreventCornerOverlap method or the corresponding xml card_view:cardPreventConrerOverlap property, which defaults to true. - Previous to Lollipop, a margin was added between CardView and content, and shadows were drawn in the area, with maxCardElevation + (1 - cos45) * cornerRadius spacing between the sides and maxCardElevation * 1.5 + (1 - cos45) * cornerRadius spacing between the top and bottom. - Because the padding property is used to offset and draw shadows, the padding property of CardView cannot be used. If you want to set the margin between CardView and its children, you can use the setContentPadding (int, int, int) method or the corresponding xml property. - If you set a specific size for CardView and its content area is different between the Lollipop version and previous versions due to shadows, you can avoid this problem by using different resource values for different system versions or setting the useCompatPadding property to true. - CardView elevation is set in a compatible way by setCardElevation(float), which uses the elevation API under Lollipop or earlier, thereby changing the size of the shadow.To prevent the view from moving when changing the size of the shadow, the shadow size will not exceed MaxCardElevation. If you want to change the shadow size dynamically after CardView initialization, you should use the setMaxCardElevation(float) method. Anatomical Source. The structure diagram is as follows:

Within CardView, the corresponding CardViewImpl object is instantiated according to different versions of the system, and the CardViewImpl object interacts with CardView through the CardViewDelegate object.

Instantiate the corresponding implementation based on the system version in the static code block of CardView.

 static {
        if (Build.VERSION.SDK_INT >= 21) {
            IMPL = new CardViewApi21();
        } else if (Build.VERSION.SDK_INT >= 17) {
            IMPL = new CardViewJellybeanMr1();
        } else {
            IMPL = new CardViewGingerbread();
        }
        IMPL.initStatic();
    }
initStatic() is called here, empty implementation in API21, that is, CardViewApi21 class, and the following in API17 (CardViewJellybeanMr1):
public void initStatic() {
        RoundRectDrawableWithShadow.sRoundRectHelper
                = new RoundRectDrawableWithShadow.RoundRectHelper() {
            @Override
            public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
                    Paint paint) {
                canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
            }
        };
    }
Pre-API17 versions were implemented as follows (CardViewGingerbread):
public void initStatic() {
        //Draw rounded rectangles using a seven-step drawing operation, faster than canvas.drawRoundRect in versions prior to API 17.
        //Because API 11-16 uses alpha mask texture to draw
        RoundRectDrawableWithShadow.sRoundRectHelper =
                new RoundRectDrawableWithShadow.RoundRectHelper() {
            @Override
            public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
                    Paint paint) {
                final float twoRadius = cornerRadius * 2;
                final float innerWidth = bounds.width() - twoRadius - 1;
                final float innerHeight = bounds.height() - twoRadius - 1;
                if (cornerRadius >= 1f) {
                    // increment corner radius to account for half pixels.
                    float roundedCornerRadius = cornerRadius + .5f;
                    sCornerRect.set(-roundedCornerRadius, -roundedCornerRadius, roundedCornerRadius,
                            roundedCornerRadius);
                    int saved = canvas.save();
                    canvas.translate(bounds.left + roundedCornerRadius,
                            bounds.top + roundedCornerRadius);
                    canvas.drawArc(sCornerRect, 180, 90, true, paint);
                    canvas.translate(innerWidth, 0);
                    canvas.rotate(90);
                    canvas.drawArc(sCornerRect, 180, 90, true, paint);
                    canvas.translate(innerHeight, 0);
                    canvas.rotate(90);
                    canvas.drawArc(sCornerRect, 180, 90, true, paint);
                    canvas.translate(innerWidth, 0);
                    canvas.rotate(90);
                    canvas.drawArc(sCornerRect, 180, 90, true, paint);
                    canvas.restoreToCount(saved);
                    //Draw the top and bottom parts
                    canvas.drawRect(bounds.left + roundedCornerRadius - 1f, bounds.top,
                            bounds.right - roundedCornerRadius + 1f,
                            bounds.top + roundedCornerRadius, paint);

                    canvas.drawRect(bounds.left + roundedCornerRadius - 1f,
                            bounds.bottom - roundedCornerRadius,
                            bounds.right - roundedCornerRadius + 1f, bounds.bottom, paint);
                }
                // Draw the middle part
                canvas.drawRect(bounds.left, bounds.top + cornerRadius,
                        bounds.right, bounds.bottom - cornerRadius , paint);
            }
        };
    }
This is the main difference in shadow implementation between API17 and previous versions, because of efficiency issues, previous versions of API17 used step-by-step drawing to draw rounded rectangles. initialize(...), this method mainly gets the properties, and then calls the specific implementation of the initialization method.
private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr,
                R.style.CardView);
        ColorStateList backgroundColor;
        if (a.hasValue(R.styleable.CardView_cardBackgroundColor)) {
            backgroundColor = a.getColorStateList(R.styleable.CardView_cardBackgroundColor);
        } else {
            // Extract from current theme without setting background
            final TypedArray aa = getContext().obtainStyledAttributes(COLOR_BACKGROUND_ATTR);
            final int themeColorBackground = aa.getColor(0, 0);
            aa.recycle();

            //Use cardview_light_background if the colorBackground s in the theme are light, otherwise use cardview_dark_background
            final float[] hsv = new float[3];
            Color.colorToHSV(themeColorBackground, hsv);
            backgroundColor = ColorStateList.valueOf(hsv[2] > 0.5f
                    ? getResources().getColor(R.color.cardview_light_background)
                    : getResources().getColor(R.color.cardview_dark_background));
        }
        float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0);
        ...
        mUserSetMinHeight = a.getDimensionPixelSize(R.styleable.CardView_android_minHeight, 0);
        a.recycle();

        IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
                elevation, maxElevation);
    }
Next let's look at the source code with a question. 1. How did API versions prior to 19 shade? 2. How do API19 and above implement View cropping? 3. How do API19 and later versions shade? 4. Impact of the cardPreventConrerOverlap property prior to API19? 5. API19 and above are affected by the cardUseCompatPadding attribute? 6. Why do shadows shift in the x- and y-axes instead of being uniformly distributed around the view? Question 1: ## How did previous versions of API19 shade? Next, let's look at how shadows were achieved before API19 (CardViewGingerbread class).
 @Override
    public void initialize(CardViewDelegate cardView, Context context,
            ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
        RoundRectDrawableWithShadow background = createBackground(context, backgroundColor, radius,
                elevation, maxElevation);
        background.setAddPaddingForCorners(cardView.getPreventCornerOverlap());
        cardView.setCardBackground(background);
        updatePadding(cardView);
    }
It appears that its shadow is achieved by the RoundRectDrawableWithShadow class. Let's see that its shadow is onDraw implemented by the RoundRectDrawableWithShadow class, which calls the buildComponents method for the first time.
public void draw(Canvas canvas) {
        if (mDirty) {
            buildComponents(getBounds());
            mDirty = false;
        }
        canvas.translate(0, mRawShadowSize / 2);
        drawShadow(canvas);
        canvas.translate(0, -mRawShadowSize / 2);
        sRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint);
    }
In the buildComponents method, mRawMaxShadowSize is actually maxElevation, where the boundary of the cardview is determined, offset from top to bottom, and the empty area is used to draw shadows.
  private void buildComponents(Rect bounds) {
        // Card is offset SHADOW_MULTIPLIER * maxShadowSize to account for the shadow shift.
        // We could have different top-bottom offsets to avoid extra gap above but in that case
        // center aligning Views inside the CardView would be problematic.
        final float verticalOffset = mRawMaxShadowSize * SHADOW_MULTIPLIER;
        mCardBounds.set(bounds.left + mRawMaxShadowSize, bounds.top+verticalOffset,
                bounds.right - mRawMaxShadowSize, bounds.bottom - verticalOffset);

        buildShadowCorners();
    }
The buildShadowCorners method initializes the path for drawing edge and corner shadows.
private void buildShadowCorners() {
        RectF innerBounds = new RectF(-mCornerRadius, -mCornerRadius, mCornerRadius, mCornerRadius);
        RectF outerBounds = new RectF(innerBounds);
        outerBounds.inset(-mShadowSize, -mShadowSize);

        if (mCornerShadowPath == null) {
            mCornerShadowPath = new Path();
        } else {
            mCornerShadowPath.reset();
        }
        mCornerShadowPath.setFillType(Path.FillType.EVEN_ODD);
        mCornerShadowPath.moveTo(-mCornerRadius, 0);
        mCornerShadowPath.rLineTo(-mShadowSize, 0);
        // outer arc
        mCornerShadowPath.arcTo(outerBounds, 180f, 90f, false);
        // inner arc
        mCornerShadowPath.arcTo(innerBounds, 270f, -90f, false);
        mCornerShadowPath.close();
        float startRatio = mCornerRadius / (mCornerRadius + mShadowSize);
        mCornerShadowPaint.setShader(new RadialGradient(0, 0, mCornerRadius + mShadowSize,
                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
                new float[]{0f, startRatio, 1f}
                , Shader.TileMode.CLAMP));

        // we offset the content shadowSize/2 pixels up to make it more realistic.
        // this is why edge shadow shader has some extra space
        // When drawing bottom edge shadow, we use that extra space.
        mEdgeShadowPaint.setShader(new LinearGradient(0, -mCornerRadius + mShadowSize, 0,
                -mCornerRadius - mShadowSize,
                new int[]{mShadowStartColor, mShadowStartColor, mShadowEndColor},
                new float[]{0f, .5f, 1f}, Shader.TileMode.CLAMP));
        mEdgeShadowPaint.setAntiAlias(false);
    }
Next, let's look at the drawShadow method, the basic canvas operation here.
private void drawShadow(Canvas canvas) {
        final float edgeShadowTop = -mCornerRadius - mShadowSize;
        final float inset = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
        final boolean drawHorizontalEdges = mCardBounds.width() - 2 * inset > 0;
        final boolean drawVerticalEdges = mCardBounds.height() - 2 * inset > 0;
        // LT
        int saved = canvas.save();
        canvas.translate(mCardBounds.left + inset, mCardBounds.top + inset);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        if (drawHorizontalEdges) {
            canvas.drawRect(0, edgeShadowTop,
                    mCardBounds.width() - 2 * inset, -mCornerRadius,
                    mEdgeShadowPaint);
        }
        canvas.restoreToCount(saved);
        // RB
        saved = canvas.save();
        canvas.translate(mCardBounds.right - inset, mCardBounds.bottom - inset);
        canvas.rotate(180f);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        if (drawHorizontalEdges) {
            canvas.drawRect(0, edgeShadowTop,
                    mCardBounds.width() - 2 * inset, -mCornerRadius + mShadowSize,
                    mEdgeShadowPaint);
        }
        canvas.restoreToCount(saved);
        // LB
        saved = canvas.save();
        canvas.translate(mCardBounds.left + inset, mCardBounds.bottom - inset);
        canvas.rotate(270f);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        if (drawVerticalEdges) {
            canvas.drawRect(0, edgeShadowTop,
                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
        }
        canvas.restoreToCount(saved);
        // RT
        saved = canvas.save();
        canvas.translate(mCardBounds.right - inset, mCardBounds.top + inset);
        canvas.rotate(90f);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        if (drawVerticalEdges) {
            canvas.drawRect(0, edgeShadowTop,
                    mCardBounds.height() - 2 * inset, -mCornerRadius, mEdgeShadowPaint);
        }
        canvas.restoreToCount(saved);
    }
Visible API19 shadows are achieved by canvas+path+RadialGradient drawing corner shadows and canvas+path+LinearGradient drawing edge shadows. Question 2: How do API19 and above implement View cropping? Enter CardViewApi21 class,
@Override
    public void initialize(CardViewDelegate cardView, Context context,
                           ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
        final RoundRectDrawable background = new RoundRectDrawable(backgroundColor, radius);
        cardView.setCardBackground(background);

        View view = cardView.getCardView();
        view.setClipToOutline(true);
        view.setElevation(elevation);
        setMaxElevation(cardView, maxElevation);
    }
RoundRectDrawable is instantiated here as the background for cardview.RoundRectDrawable is used to draw a rounded rectangle of the background. cardView.getCardView() gets the CardView object. We said earlier that CardView inherits from FrameLayout, so CardView is both ViewGroup and View. What does view.setClipToOutline (true) mean? **Allow custom view shadows and contours after Android 5.0** The background of the view draws the boundary of the object, which determines the default shape of its shadow.The outline represents the shape of the graphic object and defines the ripple area of the touch feedback. The following is an example of a view defined by a drawing object in its background:
<TextView
    android:id="@+id/myview"
    ...
    android:elevation="2dp"
    android:background="@drawable/myrect" />
Background drawable objects are defined as a rectangle with rounded corners:
<!-- res/drawable/myrect.xml -->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
       android:shape="rectangle">
    <solid android:color="#42000000" />
    <corners android:radius="5dp" />
</shape>
The view will project a shadow with rounded corners, as the background drawable object will define the outline of the view.If a custom outline is provided, it will replace the default shape of the view shadow. If you want to define a custom outline for a view in code: Expand the ViewOutlineProvider category. Replace the getOutline() method. Use the View.setOutlineProvider() method to specify a new outline provider for your view. You can use the methods in the Outline category to create ellipses and rectangular outlines with rounded corners.The default outline provider for the view will take the outline from the view background.If you want to prevent views from casting shadows, set their outline provider to null. **Clipping View** Clipping the view allows you to easily change the shape of the view.You can tailor the view to match other design elements or change the shape of the view based on user input.You can clip a view to its outline area using the View.setClipToOutline() method or the android:clipToOutline property.As determined by the Outline.canClip() method, only rectangular, circular, and rounded rectangular Outlines support clipping. If you want to clip the view to the shape of a drawing object, set the drawing object to the view background (as shown above) and call the View.setClipToOutline() method. Question 3: How do API19 and later versions shade? The initialize method of CardViewApi21 calls view.setElevation(elevation),
public void setElevation(float elevation) {
        if (elevation != getElevation()) {
            invalidateViewProperty(true, false);
            mRenderNode.setElevation(elevation);
            invalidateViewProperty(false, true);

            invalidateParentIfNeededAndWasQuickRejected();
        }
    }
Look at mRenderNode.setElevation again;
     public boolean setElevation(float lift) {
        return nSetElevation(mNativeRenderNode, lift);
    }
nSetElevation(mNativeRenderNode, lift) is a native method so that all view s starting with Android 5.0 can display shadows and are implemented directly by a native method based on the elevation property. Android 5.0 started with the addition of Material Design, which introduced height for UI elements and Z attribute for View. The view height represented by the Z attribute determines the visual appearance of its shadows: views with a higher Z value project larger and softer shadows.Views with higher Z values will block views with lower Z values; however, the view's Z value does not affect the size of the view. The view's Z-value contains two components: Height: Static component. Conversion: A dynamic component for animation. Z = elevation + translationZ So there are two factors that affect View shadows, elevation and translationZ. In Material Design Guidelines, it is suggested that elements such as cards and buttons should have a floating effect when touched, that is, to increase the Z-axis displacement. How can we achieve this effect?

Simply create a TranslationZ transformation animation with the help of a new attribute of Lollipop, android:stateListAnimator, and place it in/res/anim under your own name (such as touch_raise.xml), adding the following:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_enabled="true" android:state_pressed="true">
        <objectAnimator
        android:duration="@android:integer/config_shortAnimTime"
        android:propertyName="translationZ"
        android:valueTo="@dimen/touch_raise"
        android:valueType="floatType" />
    </item>
    <item>
        <objectAnimator
        android:duration="@android:integer/config_shortAnimTime"
        android:propertyName="translationZ"
        android:valueTo="0dp"
        android:valueType="floatType" />
    </item>
</selector>

Then copy one more copy of the Layout XML where you want the CardView (the same as the other Views) to add effects to / res/layout-v21, and add the attribute android:stateListAnimator="@anim/touch_raise" to the new CardView of the XML.This way, your card will float (darken shadows) when you hold it down.
For the ripple effect, simply add the android:foreground="? attr/selectableItemBackground" attribute to CardView.

Question 4: The impact of the cardPreventConrerOverlap property prior to API19?

CardView's setPreventCornerOverlap method.

  public void setPreventCornerOverlap(boolean preventCornerOverlap) {
        if (preventCornerOverlap != mPreventCornerOverlap) {
            mPreventCornerOverlap = preventCornerOverlap;
            IMPL.onPreventCornerOverlapChanged(mCardViewDelegate);
        }
    }

Then look at the onPreventCorner OverlapChanged method of CardViewGingerbread.

 @Override
    public void onPreventCornerOverlapChanged(CardViewDelegate cardView) {
        getShadowBackground(cardView).setAddPaddingForCorners(cardView.getPreventCornerOverlap());
        updatePadding(cardView);
    }

As you follow along, you find that the key point is in RoundRectDrawableWithShadow, where addPaddingForCorners is the passed preventCornerOverlap, and when preventCornerOverlap is true, the inner margin increases (1 - COS_45) * cornerRadius so that the child Views of CardView do not overlap the rounded corners.

static float calculateVerticalPadding(float maxShadowSize, float cornerRadius,
                                          boolean addPaddingForCorners) {
        if (addPaddingForCorners) {
            return (float) (maxShadowSize * SHADOW_MULTIPLIER + (1 - COS_45) * cornerRadius);
        } else {
            return maxShadowSize * SHADOW_MULTIPLIER;
        }
    }

Question 5 API19 and above is affected by the cardUseCompatPadding attribute?

CardView's setUseCompatPadding method.

  public void setUseCompatPadding(boolean useCompatPadding) {
        if (mCompatPadding != useCompatPadding) {
            mCompatPadding = useCompatPadding;
            IMPL.onCompatPaddingChanged(mCardViewDelegate);
        }
    }

Enter CardViewApi21.

 @Override
    public void onCompatPaddingChanged(CardViewDelegate cardView) {
        setMaxElevation(cardView, getMaxElevation(cardView));
    }

The final trace is RoundRectDrawable, where mInsetForPadding is the value of the cardUseCompatPadding property. When the cardUseCompatPadding property is true, the internal margin is set. The calculateVerticalPadding and calculateHorizontalPadding methods are static methods of WithRoundRectDrableShadow, so that versions 5.0 and earlier have the same internal margin calculation.

private void updateBounds(Rect bounds) {
        if (bounds == null) {
            bounds = getBounds();
        }
        mBoundsF.set(bounds.left, bounds.top, bounds.right, bounds.bottom);
        mBoundsI.set(bounds);
        if (mInsetForPadding) {
            float vInset = calculateVerticalPadding(mPadding, mRadius, mInsetForRadius);
            float hInset = calculateHorizontalPadding(mPadding, mRadius, mInsetForRadius);
            mBoundsI.inset((int) Math.ceil(hInset), (int) Math.ceil(vInset));
            // to make sure they have same bounds.
            mBoundsF.set(mBoundsI);
        }
    }

As you can see from the updatePadding method of CardViewApi21, if cardUseCompatPadding is not set, the inner margin of the shadow is 0, which explains the previous phenomenon.

@Override
    public void updatePadding(CardViewDelegate cardView) {
        if (!cardView.getUseCompatPadding()) {
            cardView.setShadowPadding(0, 0, 0, 0);
            return;
        }
        float elevation = getMaxElevation(cardView);
        final float radius = getRadius(cardView);
        int hPadding = (int) Math.ceil(RoundRectDrawableWithShadow
                .calculateHorizontalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
        int vPadding = (int) Math.ceil(RoundRectDrawableWithShadow
                .calculateVerticalPadding(elevation, radius, cardView.getPreventCornerOverlap()));
        cardView.setShadowPadding(hPadding, vPadding, hPadding, vPadding);
    }

Question 6 Why do shadows shift in the x- and y-axes instead of being uniformly distributed around the view?

In RoundRectDrawableWithShadow's draw method, we see that before drawing a shadow, the canvas shifts in the positive y-axis direction, which changes the direction of the shadow.

 @Override
    public void draw(Canvas canvas) {
        if (mDirty) {
            buildComponents(getBounds());
            mDirty = false;
        }
        canvas.translate(0, mRawShadowSize / 2);
        drawShadow(canvas);
        canvas.translate(0, -mRawShadowSize / 2);
        sRoundRectHelper.drawRoundRect(canvas, mCardBounds, mCornerRadius, mPaint);
    }

If your project is designed to match Material Design, is it best if your design will one day allow you to achieve the same size shadows around you?We also know how to do it!
Here I put the implementation in Github Yes, welcome to your attention.

Reference material:
https://developer.android.com/training/material/lists-cards.html#Dependencies
http://www.jianshu.com/p/33b1d21d6ba6
https://developer.android.com/training/material/shadows-clipping.html#Shadows
https://android.jlelse.eu/android-card-view-edb905e67cd6
http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/1025/3621.html

Posted by goobers on Tue, 04 Jun 2019 09:49:48 -0700