Anmol
Anmol

Reputation: 8670

Why setting shapeAppearanceModel in MaterialCardView stops childclipping?

My usecase is to edit shapeAppearanceModel for com.google.android.material.card.MaterialCardView

card.shapeAppearanceModel = card.shapeAppearanceModel
            .toBuilder()
            .setTopEdge(TriangleEdgeTreatment(triangleSize))
            .build()

Above code is working as expected But due to above programatically setup of shapeAppearanceModel

xml

 <com.google.android.material.card.MaterialCardView
        android:id="@+id/card"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:cardBackgroundColor="?myCustomColor"
        app:cardCornerRadius="8dp"
        app:cardElevation=4dp"
        app:cardPreventCornerOverlap="true"
        app:cardUseCompatPadding="false">

    <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="fitXY"
            android:src="@drawable/myImageDrawable"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
</com.google.android.material.card.MaterialCardView>

In Android studio as the TopEdge is define programatically Editor is not replicating the bug but in Emulator/Real device the child is not getting clipped and The Rounded Corners are getting overlapped by ImageView.

And when i remove the shapeAppearanceModel setup the clipping work's as expected.

ScreenShot

Bug

Upvotes: 5

Views: 1646

Answers (1)

Davin Reinaldo Gozali
Davin Reinaldo Gozali

Reputation: 301

The reason is Because MaterialCardView locked clipToOutline behaviour only whe the Shape is Marked as Rounded and unfortunately, that implementation also locked by the component it self by default :

here if you open MaterialCardView.java, you can find this code:

@Override
  public void setShapeAppearanceModel(@NonNull ShapeAppearanceModel shapeAppearanceModel) {
    if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
      setClipToOutline(shapeAppearanceModel.isRoundRect(getBoundsAsRectF()));
    }
    cardViewHelper.setShapeAppearanceModel(shapeAppearanceModel);
  }

and if you go deeper, the implementation of isRoundRect, would be like this :

@RestrictTo(LIBRARY_GROUP)
  public boolean isRoundRect(@NonNull RectF bounds) {
    boolean hasDefaultEdges =
        leftEdge.getClass().equals(EdgeTreatment.class)
            && rightEdge.getClass().equals(EdgeTreatment.class)
            && topEdge.getClass().equals(EdgeTreatment.class)
            && bottomEdge.getClass().equals(EdgeTreatment.class);

    float cornerSize = topLeftCornerSize.getCornerSize(bounds);

    boolean cornersHaveSameSize =
        topRightCornerSize.getCornerSize(bounds) == cornerSize
            && bottomLeftCornerSize.getCornerSize(bounds) == cornerSize
            && bottomRightCornerSize.getCornerSize(bounds) == cornerSize;

    boolean hasRoundedCorners =
        topRightCorner instanceof RoundedCornerTreatment
            && topLeftCorner instanceof RoundedCornerTreatment
            && bottomRightCorner instanceof RoundedCornerTreatment
            && bottomLeftCorner instanceof RoundedCornerTreatment;

    return hasDefaultEdges && cornersHaveSameSize && hasRoundedCorners;
  }

there's a condition to make it return true will be if the shape has the same or all corner rounded.

You can't trick that by programmatically set card.setClipToOutline(true) because it happended after the render proses.

The reason they make it that way because of some limitation by design :

(further reading : https://github.com/material-components/material-components-android/issues/1950)

The Good News you can solve that by creating your own MaterialCardView and override some implementation like this :

package com.example.myapp;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;

import com.google.android.material.card.MaterialCardView;
import com.google.android.material.shape.ShapeAppearancePathProvider;

public class MaskedCardView extends MaterialCardView {
    private ShapeAppearancePathProvider pathProvider = new ShapeAppearancePathProvider();
    private Path path = new Path();
    private RectF rectF = new RectF(0f, 0f, 0f, 0f);

    public MaskedCardView(Context context) {
        super(context);
    }

    public MaskedCardView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MaskedCardView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        canvas.clipPath(path);
        super.onDraw(canvas);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        rectF.right = (float) w;
        rectF.bottom = (float) h;
        pathProvider.calculatePath(getShapeAppearanceModel(), 1f, rectF, path);
        super.onSizeChanged(w, h, oldw, oldh);
    }
}

I hope it can answer your question.

Upvotes: 3

Related Questions