Изменение цвета тени CardView

Этот вопрос задавали ВО много раз, но все же я не нашел хорошего решения этой проблемы.

Зачем мне это нужно? Хорошо, потому что проект меня и моей команды разрабатывает, имеет стиль iOS.

Что я пробовал?

  1. 9.pathch shadow generator, но 9.pathes - это, по существу, png, и это не дает мне никакой гибкости, и если я буду использовать этот подход, я должен редактировать поля везде.
  2. Углеродная библиотека поддерживает пользовательские тени, и их рисуют вне границ вида, но есть проблема с закругленными прямоугольниками, когда библиотека не рисует тени для закругленных углов.
  3. используя старую реализацию CardView и переориентируя свой теневой цвет, но она нарисована внутри границ карты, поэтому это не вариант.

Итак, есть ли способ изменить теневой цвет CardView с минимальными изменениями всех файлов макета и с теневой тенью вне представления, как это делает оригинальный CardView?

Ответ 1

Рассмотрите эту ветку в твиттере, где Ник Батчер рассказывает о том, как реализовать эту функцию:

enter image description here

Подробности смотрите в атрибутах outlineAmbientShadowColor, outlineSpotShadowColor, spotShadowAlpha и ambientShadowAlpha. К сожалению, это возможно начиная с API 28 и далее.

Для более низких API Ник поделился суть. Вот результат:

Running on API 21

Этот метод не связан напрямую с CardView, его можно применить к любому View.

Ответ 2

Вы можете реализовать это без наличия карты, а также иметь все свойства карты

Ты должен сделать:

  1. Скопируйте два класса

  2. Оберните требуемое представление с помощью пользовательского представления, как в примере, вам не нужно вносить изменения в свой макет или где-либо еще!

В нижеприведенном классе создается пользовательский вид, который будет обертывать ваш макет/вид, который будет отображаться в кард-визите, с пользовательским цветом тени

Создайте класс:

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));
    }
}

Также создайте класс для настроек тени, 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;

    }
}

и, наконец, ваш XML, где у вас есть представления, требующие наличия тени.

<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>

Ответ 3

Я думаю о простом решении без использования Java или некоторых библиотек. Вы должны сделать Drawable shape и поместить его в папку с возможностью drawable а затем настроить градиент как тень.

Например, в моем решении я добавил два цвета:

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

Затем я сделал файл и поместил его в папку с возможностью 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>

Затем оттуда вам нужно обернуть свой вид (который был бы внутри CardView) в контейнере, LinearLayout как LinearLayout затем применить в качестве фона к контейнеру, который вы хотите видеть как вид карты. Чтобы решить эту проблему, добавьте немного заполнения (Thats your shadow) самому контейнеру. Например, проверьте мой:

<?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>

Затем результаты выглядят так:
Testing Results

Регулировка нижнего отступов будет выглядеть так:

Testing results

КОММЕНТАРИЙ
Поскольку я не художник, но если вы играете с ним, вы можете заставить все это выглядеть так же, как CardView проверить некоторые подсказки:

  • Помещение нескольких градиентов в форму
  • Отрегулируйте конечные цвета градиентов, чтобы они выглядели более сероватыми
  • Конечные цвета также должны быть немного прозрачными
  • Отрегулируйте видимость вида, чтобы выглядеть как тень и цветной, но сероватый
  • Основной фон "Вид" также имеет значение для приведения реальности. Оттуда редизайн формы выглядит еще более реалистично, как CardView.