Changing CardView shadow color

2019-03-25 04:13发布

问题:

This question was asked on SO many times, but still I didn't find a good solution for this problem.

Why do I need this to do? Well because project me and my team develops has iOS style.

What did I try?

  1. 9.pathch shadow generator but 9.pathes are essentially pngs and it gives me no flexibility and if I'll use this approach I should edit margins everywhere.
  2. Carbon library it supports custom shadows and they get drawn outside of view borders, but there is issue regarding rounded rectangles, when library doesn't draw shadow for rounded corners.
  3. using old CardView implementation and overriding its shadow color, but it gets drawn inside of card bounds, so it isn't option.

So is there a way to change shadow color of CardView with minimum edits of all layout files and with drawing shadow outside of the view like original CardView does?

回答1:

Consider this thread at twitter, where Nick Butcher talks about how to implement this:

See outlineAmbientShadowColor, outlineSpotShadowColor, spotShadowAlpha and ambientShadowAlpha attributes for details. Unfortunately, that's possible from API 28 onwards.

For lower APIs Nick has shared a gist. Here's the result:

Running on API 21

This technique isn't directly connected to CardView, it can be applied to any View.



回答2:

Well I think of an easy solution without using a Java or Some Libraries. You should make a Drawable shape and put it in the drawable folder and then adjust the gradient to be like a shadow.

For example, in my solution I have added two colors:

<color name="yellow_middle">#ffee58</color>
<color name="yellow_end">#7ae7de83</color>

Then I made a file and put it in drawable folder drawable\card_view_shape.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
  <size
        android:width="10dp"
        android:height="10dp" />
  <corners android:radius="6dp" />
  <stroke
        android:width="2dp"
        android:color="@color/yellow_end" />
  <gradient
       android:angle="-90"
       android:centerColor="@color/yellow_middle"
       android:endColor="@color/yellow_end"
       android:startColor="#fff" />
</shape>

Then from there you need to wrap a your view(that would have been inside CardView) in a container like LinearLayout then apply as the background to the container that you want to be seen like a cardview. To solve it well add some padding (Thats your shadow) to the Container itself. For instance check mine:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.xenolion.ritetrends.MainActivity">

    <LinearLayout
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center"
        android:background="@drawable/card_view_shape"
        android:orientation="vertical"
        android:paddingBottom="10dp"
        android:paddingLeft="3dp"
        android:paddingRight="3dp"
        android:paddingTop="3dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#fff"
            android:gravity="center"
            android:text="I love StackOverflow"
            android:textColor="#000"
            android:textSize="18sp" />

    </LinearLayout>


</FrameLayout>

Then the results looks like this:

Adjusting the bottom padding it will look like this:

COMMENT
Since I am not of an artist but if you play with it you may make the whole thing look exactly like CardView check some hints:

  • Putting multiple gradients in the shape
  • Adjust the end colors of gradients to appear more greyish
  • The end colours must also be a little transparent
  • Adjust your View's padding to appear like a shadow and coloured but greyish
  • The main View's background also matters to bring the reality From there redesign the shape to look even more realistic like a CardView.


回答3:

You Can Implement this without having a cardview, and can also have all the properties of cardview

You have to Do:

  1. Copy the two classes

  2. Wrap your required view with the Custom View as in the example, you don't have to do much changes in your layout or anywhere else!

The below class will create a custom view, this will be wrapping your layout/View to be displayed in cardview with custom shadow color

Create a class:

import android.content.Context;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.widget.LinearLayout;

import com.qzion.nfscrew.R;


public class RoundLinerLayoutNormal extends LinearLayout {
    public RoundLinerLayoutNormal(Context context) {
        super(context);
        initBackground();
    }

    public RoundLinerLayoutNormal(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        initBackground();
    }

    public RoundLinerLayoutNormal(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initBackground();
    }

    private void initBackground() {
        setBackground(ViewUtils.generateBackgroundWithShadow(this,R.color.white,
                R.dimen.radius_corner,R.color.colorPrimaryDark,R.dimen.elevation, Gravity.BOTTOM));
    }
}

Also create the class for the Shadow Settings, ViewUtils.java

import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.RoundRectShape;
import android.support.annotation.ColorRes;
import android.support.annotation.DimenRes;
import android.support.v4.content.ContextCompat;
import android.view.Gravity;
import android.view.View;

import static android.support.v4.view.ViewCompat.LAYER_TYPE_SOFTWARE;


public class ViewUtils {

    public static Drawable generateBackgroundWithShadow(View view, @ColorRes int backgroundColor,
                                                        @DimenRes int cornerRadius,
                                                        @ColorRes int shadowColor,
                                                        @DimenRes int elevation,
                                                        int shadowGravity) {
        float cornerRadiusValue = view.getContext().getResources().getDimension(cornerRadius);
        int elevationValue = (int) view.getContext().getResources().getDimension(elevation);
        int shadowColorValue = ContextCompat.getColor(view.getContext(),shadowColor);
        int backgroundColorValue = ContextCompat.getColor(view.getContext(),backgroundColor);

        float[] outerRadius = {cornerRadiusValue, cornerRadiusValue, cornerRadiusValue,
                cornerRadiusValue, cornerRadiusValue, cornerRadiusValue, cornerRadiusValue,
                cornerRadiusValue};

        Paint backgroundPaint = new Paint();
        backgroundPaint.setStyle(Paint.Style.FILL);
        backgroundPaint.setShadowLayer(cornerRadiusValue, 0, 0, 0);

        Rect shapeDrawablePadding = new Rect();
        shapeDrawablePadding.left = elevationValue;
        shapeDrawablePadding.right = elevationValue;

        int DY;
        switch (shadowGravity) {
            case Gravity.CENTER:
                shapeDrawablePadding.top = elevationValue;
                shapeDrawablePadding.bottom = elevationValue;
                DY = 0;
                break;
            case Gravity.TOP:
                shapeDrawablePadding.top = elevationValue*2;
                shapeDrawablePadding.bottom = elevationValue;
                DY = -1*elevationValue/3;
                break;
            default:
            case Gravity.BOTTOM:
                shapeDrawablePadding.top = elevationValue;
                shapeDrawablePadding.bottom = elevationValue*2;
                DY = elevationValue/3;
                break;
        }

        ShapeDrawable shapeDrawable = new ShapeDrawable();
        shapeDrawable.setPadding(shapeDrawablePadding);

        shapeDrawable.getPaint().setColor(backgroundColorValue);
        shapeDrawable.getPaint().setShadowLayer(cornerRadiusValue/3, 0, DY, shadowColorValue);

        view.setLayerType(LAYER_TYPE_SOFTWARE, shapeDrawable.getPaint());

        shapeDrawable.setShape(new RoundRectShape(outerRadius, null, null));

        LayerDrawable drawable = new LayerDrawable(new Drawable[]{shapeDrawable});
        drawable.setLayerInset(0, elevationValue, elevationValue*2, elevationValue, elevationValue*2);

        return drawable;

    }
}

and finally your XML, where you have the views required to have shadow.

<com.qzion.nfscrew.utils.RoundLinerLayoutNormal
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="10dp">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="This view will have shadow"/>

            </com.qzion.nfscrew.utils.RoundLinerLayoutNormal>