Направление тени Android 5 Lollipop или смещение по оси y

Есть ли возможность изменить направление тени смещения Y?

Сейчас у меня следующий макет

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container"
    android:layout_width="match_parent" android:layout_height="match_parent"
    tools:context=".MainActivity" tools:ignore="MergeRootFrame" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginBottom="200dp"
        android:elevation="5dp"
        android:translationZ="5dp"
        android:background="@android:color/darker_gray"
        android:layout_gravity="bottom"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginBottom="100dp"
        android:elevation="4dp"
        android:translationZ="5dp"
        android:background="@android:color/holo_blue_dark"
        android:layout_gravity="bottom"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:elevation="3dp"
        android:translationZ="5dp"
        android:background="@android:color/holo_green_dark"
        android:layout_gravity="bottom"/>

</FrameLayout>

который воспроизводит результат слева, но мне нравится добиваться эффекта на правом изображении введите здесь описание изображения

Может ли кто-нибудь указать мне, как это сделать?


person djandreski    schedule 20.12.2014    source источник
comment
Нет, вы не можете изменить положение света. Он исправлен во всех приложениях, чтобы обеспечить визуально согласованную модель освещения. Думали ли вы о том, чтобы переупорядочить слои так, чтобы самый верхний слой имел наименьшую высоту, как на изображении справа?   -  person alanv    schedule 22.12.2014
comment
Да, но тогда под представлениями не отображается тень   -  person djandreski    schedule 23.12.2014


Ответы (3)


Добавлен метод setOutlineProvider для View. в API 21 может пригодиться. Из документации:

Задает ViewOutlineProvider представления, которое генерирует Outline, определяющий форму тени, которую он отбрасывает, и позволяет обрезать контур.

Это точно позволяет настраивать форму, но я не уверен на 100%, что направление тени можно изменить.

person stkent    schedule 20.12.2014

После некоторых исследований я обнаружил, что вы не можете установить смещение X и Y для теней, поэтому я придумал красивое решение, которое дает желаемый эффект, оно состоит из списка слоев, но первый слой будет градиентом, чтобы дать красивый выглядит эффект тени так:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

<item android:gravity="top" >

    <shape  android:shape="rectangle">

        <size
            android:height="15dp"/>
        <gradient
            android:angle="90"
            android:startColor="@android:color/black"/>

    </shape>



</item>
<item
    android:top="15dp">

    <shape  android:shape="rectangle">

        <solid android:color="@android:color/white" />

    </shape>



</item>
</layer-list>

верхнее значение второго слоя и высота формы первого слоя должны быть одинаковыми, но вы можете изменить их значения для достижения желаемой высоты, тогда все, что вам нужно сделать, это установить это как фон вида, который вы хотите применить возвышение. Может также использоваться в качестве альтернативы возвышения для устройств до леденцов.

Также не забудьте добавить top_padding, эквивалентный размеру высоты, указанному в xml, к представлению, к которому вы применяете это в качестве фона.

ПРИМЕЧАНИЕ. Это будет хорошо выглядеть, только если ваш вид занимает всю ширину экрана, поскольку тени не применяются к сторонам.

person Amro elaswar    schedule 06.12.2016

Единственный способ создать верхнюю тень, который я нашел, — это изменить часть исходного кода проекта совместимости с Android v7 CardView. Этот проект переносит класс CardView в более старые версии Android и, таким образом, также включает тень возвышения. Полученная тень очень близка к «реальной» тени возвышения.

Важным классом из проекта CardView является RoundRectDrawableWithShadow.

Я называю это так:

float density = getResources().getDisplayMetrics().density;
float elevation = 2;
xlayout.setBackgroundDrawable(new RoundRectDrawableWithShadow(
        getResources(), Color.BLACK, 0, 
        elevation*density, ((elevation +1 )*density) + 1
));

Вам понадобится следующее в res/values/values.xml

<color name="cardview_shadow_end_color">#03000000</color>
<color name="cardview_shadow_start_color">#47000000</color>
<dimen name="cardview_compat_inset_shadow">1dp</dimen>

Здесь модифицированный класс RoundRectDrawableWithShadow:

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package xxx;

import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
import xxx.R;

/**
 * A rounded rectangle drawable which also includes a shadow around.
 */
public class RoundRectDrawableWithShadow extends Drawable {
    // used to calculate content padding
    final static double COS_45 = Math.cos(Math.toRadians(45));

    final static float SHADOW_MULTIPLIER = 1.5f;

    final int mInsetShadow; // extra shadow to avoid gaps between card and shadow

    /*
    * This helper is set by CardView implementations.
    * <p>
    * Prior to API 17, canvas.drawRoundRect is expensive; which is why we need this interface
    * to draw efficient rounded rectangles before 17.
    * */
    static RoundRectHelper sRoundRectHelper;

    Paint mPaint;

    Paint mCornerShadowPaint;

    Paint mEdgeShadowPaint;

    final RectF mCardBounds;

    float mCornerRadius;

    Path mCornerShadowPath;

    // updated value with inset
    float mMaxShadowSize;

    // actual value set by developer
    float mRawMaxShadowSize;

    // multiplied value to account for shadow offset
    float mShadowSize;

    // actual value set by developer
    float mRawShadowSize;

    private boolean mDirty = true;

    private final int mShadowStartColor;

    private final int mShadowEndColor;

    private boolean mAddPaddingForCorners = true;

    /**
     * If shadow size is set to a value above max shadow, we print a warning
     */
    private boolean mPrintedShadowClipWarning = false;

    public RoundRectDrawableWithShadow(
            Resources resources, int backgroundColor, float radius,
            float shadowSize, float maxShadowSize
    ) {
        mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
        mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
        mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mPaint.setColor(backgroundColor);
        mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mCornerShadowPaint.setStyle(Paint.Style.FILL);
        mCornerRadius = (int) (radius + .5f);
        mCardBounds = new RectF();
        mEdgeShadowPaint = new Paint(mCornerShadowPaint);
        mEdgeShadowPaint.setAntiAlias(false);
        setShadowSize(shadowSize, maxShadowSize);

        RoundRectDrawableWithShadow.sRoundRectHelper
                = new RoundRectDrawableWithShadow.RoundRectHelper() {
            @Override
            public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
                    Paint paint) {
                canvas.drawRoundRect(bounds, cornerRadius, cornerRadius, paint);
            }
        };
    }

    /**
     * Casts the value to an even integer.
     */
    private int toEven(float value) {
        int i = (int) (value + .5f);
        if (i % 2 == 1) {
            return i - 1;
        }
        return i;
    }

    public void setAddPaddingForCorners(boolean addPaddingForCorners) {
        mAddPaddingForCorners = addPaddingForCorners;
        invalidateSelf();
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
        mCornerShadowPaint.setAlpha(alpha);
        mEdgeShadowPaint.setAlpha(alpha);
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        super.onBoundsChange(bounds);
        mDirty = true;
    }

    void setShadowSize(float shadowSize, float maxShadowSize) {
        if (shadowSize < 0 || maxShadowSize < 0) {
            throw new IllegalArgumentException("invalid shadow size");
        }
        shadowSize = toEven(shadowSize);
        maxShadowSize = toEven(maxShadowSize);
        if (shadowSize > maxShadowSize) {
            shadowSize = maxShadowSize;
            if (!mPrintedShadowClipWarning) {
                mPrintedShadowClipWarning = true;
            }
        }
        if (mRawShadowSize == shadowSize && mRawMaxShadowSize == maxShadowSize) {
            return;
        }
        mRawShadowSize = shadowSize;
        mRawMaxShadowSize = maxShadowSize;
        mShadowSize = (int)(shadowSize * SHADOW_MULTIPLIER + mInsetShadow + .5f);
        mMaxShadowSize = maxShadowSize + mInsetShadow;
        mDirty = true;
        invalidateSelf();
    }

    @Override
    public boolean getPadding(Rect padding) {
        int vOffset = (int) Math.ceil(calculateVerticalPadding(mRawMaxShadowSize, mCornerRadius,
                mAddPaddingForCorners));
//        int hOffset = (int) Math.ceil(calculateHorizontalPadding(mRawMaxShadowSize, mCornerRadius,
//                mAddPaddingForCorners));
//        padding.set(hOffset, vOffset, hOffset, vOffset);
        padding.set(0, vOffset, 0, 0);
        return true;
    }

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

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

    @Override
    public void setColorFilter(ColorFilter cf) {
        mPaint.setColorFilter(cf);
        mCornerShadowPaint.setColorFilter(cf);
        mEdgeShadowPaint.setColorFilter(cf);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT;
    }

    void setCornerRadius(float radius) {
        radius = (int) (radius + .5f);
        if (mCornerRadius == radius) {
            return;
        }
        mCornerRadius = radius;
        mDirty = true;
        invalidateSelf();
    }

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

    private void drawShadow(Canvas canvas) {
        final float edgeShadowTop = -mCornerRadius - mShadowSize;
        final float insetVertical = mCornerRadius + mInsetShadow + mRawShadowSize / 2;
        final float insetHorizontal = -mInsetShadow;
        // LT top
        int saved = canvas.save();
        canvas.translate(mCardBounds.left + insetHorizontal, mCardBounds.top + insetVertical);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        canvas.drawRect(0, edgeShadowTop,
                mCardBounds.width() - 2 * insetHorizontal, -mCornerRadius + mShadowSize,
                mEdgeShadowPaint);
        canvas.restoreToCount(saved);

        // RT right
        saved = canvas.save();
        canvas.translate(mCardBounds.right - insetHorizontal, mCardBounds.top + insetVertical);
        canvas.rotate(90f);
        canvas.drawPath(mCornerShadowPath, mCornerShadowPaint);
        canvas.restoreToCount(saved);
    }

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

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

    float getCornerRadius() {
        return mCornerRadius;
    }

    void getMaxShadowAndCornerPadding(Rect into) {
        getPadding(into);
    }

    void setShadowSize(float size) {
        setShadowSize(size, mRawMaxShadowSize);
    }

    void setMaxShadowSize(float size) {
        setShadowSize(mRawShadowSize, size);
    }

    float getShadowSize() {
        return mRawShadowSize;
    }

    float getMaxShadowSize() {
        return mRawMaxShadowSize;
    }

    float getMinWidth() {
        final float content = 2 *
                Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow + mRawMaxShadowSize / 2);
        return content + (mRawMaxShadowSize + mInsetShadow) * 2;
    }

    float getMinHeight() {
        final float content = 2 * Math.max(mRawMaxShadowSize, mCornerRadius + mInsetShadow
                        + mRawMaxShadowSize * SHADOW_MULTIPLIER / 2);
        return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
    }

    public void setColor(int color) {
        mPaint.setColor(color);
        invalidateSelf();
    }

    static interface RoundRectHelper {
        void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint);
    }
}

Работай как шарм для меня.

person mdiener    schedule 28.01.2015
comment
Тень отбрасывается вверх, но за ней есть белый фон, который должен быть прозрачным.... - person the_prole; 05.11.2015
comment
Можете ли вы опубликовать свой полный макет xml? - person mdiener; 05.11.2015
comment
Какое значение вы использовали для плотности? - person the_prole; 09.11.2015
comment
Что касается плотности, то она зависит от устройства: плотность с плавающей запятой = getResources().getDisplayMetrics().density; - person mdiener; 09.11.2015