As a programmer, it’s fun to take a look at the source code when you don’t have time.

So before we get into the source code, let’s look at this CardView versus the MaterialCardView.

<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".main.CardTestActivity">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:layout_margin="12dp"
        app:cardBackgroundColor="@android:color/holo_green_light"
        app:cardCornerRadius="10dp"/>

    <com.google.android.material.card.MaterialCardView
        android:layout_width="match_parent"
        android:layout_height="160dp"
        android:layout_margin="12dp"
        app:cardBackgroundColor="@android:color/holo_orange_light"
        app:cardCornerRadius="10dp"
        app:strokeColor="@android:color/holo_blue_light"
        app:strokeWidth="5dp"/>

</LinearLayout>
Copy the code

The MaterialCardView is actually an extension of CardView, it inherits from CardView, so it has everything that CardView has, but it also has the ability to draw borders on top of CardView. The MaterialCardView can draw borders), so it has two more strokeColor and strokeWidth properties than the CardView, and without those two properties, you can completely use it as a CardView, But I don’t recommend it because it’s not necessary.

The source code of the MaterialCardView is actually quite simple (if you want to see the full source code, please go to the back). The main thing to pay attention to is the construction method with three parameters:

public MaterialCardView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray attributes = ThemeEnforcement.obtainStyledAttributes(context, attrs, styleable.MaterialCardView, defStyleAttr, style.Widget_MaterialComponents_CardView, new int[0]); this.cardViewHelper = new MaterialCardViewHelper(this); / / read strokeColor and strokeWidth attribute's value, and draw frame, adjust ContentPadding enclosing cardViewHelper. LoadFromAttributes (attributes); attributes.recycle(); }Copy the code

Here we instantiate a MaterialCardViewHelper object, The loadFromAttributes(TypedArray Attributes) method is then called from this object to read the strokeColor and strokeWidth attributes, draw the border, and adjust the ContentPadding.

/ * * * From MaterialCardViewHelper. Class * read strokeColor and strokeWidth attribute's value, ContentPadding * @param Attributes */ public void loadFromAttributes(TypedArray Attributes) {// Read strokeColor StrokeWidth and the value of the attribute this. StrokeColor = attributes. The getColor (styleable MaterialCardView_strokeColor, - 1); this.strokeWidth = attributes.getDimensionPixelSize(styleable.MaterialCardView_strokeWidth, 0); // Draw the border this.updateforeGround (); / / adjust ContentPadding enclosing adjustContentPadding (); }Copy the code

Draw the border using the updateForeground() method, which sets the foreground image using the setForeground(Drawable foreground) method of the View, The border image is created with the createForegroundDrawable() method.

/ * * * From MaterialCardViewHelper. Class * draw frame (the border is painted in the foreground image) * / voidupdateForeground() { this.materialCardView.setForeground(this.createForegroundDrawable()); } / * * * From MaterialCardViewHelper. Class * create a specified Angle radius, the width and color border Drawable * @return
     */
    private Drawable createForegroundDrawable() {
        GradientDrawable fgDrawable = new GradientDrawable();
        fgDrawable.setCornerRadius(this.materialCardView.getRadius());
        if (this.strokeColor != -1) {
            fgDrawable.setStroke(this.strokeWidth, this.strokeColor);
        }
        return fgDrawable;
    }
Copy the code

AdjustContentPadding () is used to adjust the space inside the MaterialCardView. It’s just a new ContentPadding with the width of the border around it.

/ * * * From MaterialCardViewHelper. Class * adjusted ContentPadding around (on the basis of ContentPadding plus border width) * / private voidadjustContentPadding() {
        int contentPaddingLeft = this.materialCardView.getContentPaddingLeft() + this.strokeWidth;
        int contentPaddingTop = this.materialCardView.getContentPaddingTop() + this.strokeWidth;
        int contentPaddingRight = this.materialCardView.getContentPaddingRight() + this.strokeWidth;
        int contentPaddingBottom = this.materialCardView.getContentPaddingBottom() + this.strokeWidth;
        this.materialCardView.setContentPadding(contentPaddingLeft, contentPaddingTop, contentPaddingRight, contentPaddingBottom);
    }
Copy the code

Special reminder:

  • For the MaterialCardView, if you use the app:cardUseCompatPadding=”true”, and you make the border smaller, you won’t see the border (it doesn’t exist, it just doesn’t exist, you can increase the border width, But it doesn’t feel as good as it should, so it’s best to remove the cardUseCompatPadding. .
  • Because the border of the MaterialCardView is drawn on the foreground image, the Android :foreground is useless, of course, The setable foreground of the MaterialCardView can still be used in this code, but it will cover the border.

MaterialCardView source code:

Public class MaterialCardView extends CardView {** ** MaterialCardViewHelper is a helper class of MaterialCardView. */ Private Final MaterialCardViewHelper cardViewHelper; public MaterialCardView(Context context) { this(context, (AttributeSet)null); } public MaterialCardView(Context context, AttributeSet attrs) { this(context, attrs, attr.materialCardViewStyle); } public MaterialCardView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray attributes = ThemeEnforcement.obtainStyledAttributes(context, attrs, styleable.MaterialCardView, defStyleAttr, style.Widget_MaterialComponents_CardView, new int[0]); this.cardViewHelper = new MaterialCardViewHelper(this); / / read strokeColor and strokeWidth attribute's value, and draw frame, adjust ContentPadding enclosing cardViewHelper. LoadFromAttributes (attributes); attributes.recycle(); } /** * set the border color * @param strokeColor */ public voidsetStrokeColor(@ColorInt int strokeColor) { this.cardViewHelper.setStrokeColor(strokeColor); } /** * gets the border color * @return
     */
    @ColorInt
    public int getStrokeColor() {
        returnthis.cardViewHelper.getStrokeColor(); } @param strokeWidth @param strokeWidth public voidsetStrokeWidth(@Dimension int strokeWidth) { this.cardViewHelper.setStrokeWidth(strokeWidth); } /** * gets the border width * @return
     */
    @Dimension
    public int getStrokeWidth() {
        returnthis.cardViewHelper.getStrokeWidth(); } /** * set radius @param radius */ public voidsetRadius(floatradius) { super.setRadius(radius); / / set the corner radius, update frame enclosing cardViewHelper. UpdateForeground (); }}Copy the code

MaterialCardViewHelper source code:

@RestrictTo({Scope.LIBRARY_GROUP}) class MaterialCardViewHelper { private static final int DEFAULT_STROKE_VALUE = -1; //MaterialCardView object, which is passed to the private final MaterialCardView MaterialCardView by its constructor when instantiating the MaterialCardViewHelper; private int strokeColor; private int strokeWidth; public MaterialCardViewHelper(MaterialCardView card) { this.materialCardView = card; } /** * read the strokeColor and strokeWidth attributes, ContentPadding * @param Attributes */ public void loadFromAttributes(TypedArray Attributes) {// Read strokeColor StrokeWidth and the value of the attribute this. StrokeColor = attributes. The getColor (styleable MaterialCardView_strokeColor, - 1); this.strokeWidth = attributes.getDimensionPixelSize(styleable.MaterialCardView_strokeWidth, 0); // Draw the border this.updateforeGround (); / / adjust ContentPadding enclosing adjustContentPadding (); } /** * set the border color and update the border * @param strokeColor */ voidsetStrokeColor(@ColorInt int strokeColor) { this.strokeColor = strokeColor; this.updateForeground(); } /** * gets the border color * @return
     */
    @ColorInt
    int getStrokeColor() {
        returnthis.strokeColor; } /** * set the border width, update the border, adjust ContentPadding * @param strokeWidth */ voidsetStrokeWidth(@Dimension int strokeWidth) { this.strokeWidth = strokeWidth; this.updateForeground(); this.adjustContentPadding(); } /** * gets the border width * @return
     */
    @Dimension
    int getStrokeWidth() {
        returnthis.strokeWidth; } /** * draws the border that is drawn on the foreground image */ voidupdateForeground() { this.materialCardView.setForeground(this.createForegroundDrawable()); } /** * create a border Drawable * @ that specifies the Angle radius, width, and colorreturn
     */
    private Drawable createForegroundDrawable() {
        GradientDrawable fgDrawable = new GradientDrawable();
        fgDrawable.setCornerRadius(this.materialCardView.getRadius());
        if (this.strokeColor != -1) {
            fgDrawable.setStroke(this.strokeWidth, this.strokeColor);
        }
        return fgDrawable; } /** * Adjust the ContentPadding (around the ContentPadding with the border width) */ private voidadjustContentPadding() { int contentPaddingLeft = this.materialCardView.getContentPaddingLeft() + this.strokeWidth; int contentPaddingTop = this.materialCardView.getContentPaddingTop() + this.strokeWidth; int contentPaddingRight = this.materialCardView.getContentPaddingRight() + this.strokeWidth; int contentPaddingBottom = this.materialCardView.getContentPaddingBottom() + this.strokeWidth; this.materialCardView.setContentPadding(contentPaddingLeft, contentPaddingTop, contentPaddingRight, contentPaddingBottom); }}Copy the code