如何在Android中显示LinearLayout周围的阴影?

79

我该如何为我的线性布局添加阴影?我想要带有阴影的白色圆角背景。到目前为止,我已经做了以下工作:

<LinearLayout
 android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@xml/rounded_rect_shape"
android:orientation="vertical"
android:padding="10dp">
<-- My buttons, textviews, Imageviews go here -->
</LinearLayout>

还有在xml目录下的rounded_rect_shape.xml文件

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

   <solid android:color="#ffffff" />

   <corners
      android:bottomLeftRadius="3dp"
      android:bottomRightRadius="3dp"
      android:topLeftRadius="3dp"
      android:topRightRadius="3dp" />
</shape>

13个回答

147

通过实现一个图层列表作为LinearLayout的背景,也有另一种解决问题的方法。

将background_with_shadow.xml文件添加到res/drawable中,其中包含:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item >
        <shape 
            android:shape="rectangle">
        <solid android:color="@android:color/darker_gray" />
        <corners android:radius="5dp"/>
        </shape>
    </item>
    <item android:right="1dp" android:left="1dp" android:bottom="2dp">
        <shape 
            android:shape="rectangle">
        <solid android:color="@android:color/white"/>
        <corners android:radius="5dp"/>
        </shape>
    </item>
</layer-list>

然后将layer-list作为LinearLayout的背景添加进去。

<LinearLayout
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:background="@drawable/background_with_shadow"/>

很棒的效果,谢谢!但是我有一个小问题 -几乎看不出来-:阴影并不真正靠近白色布局,而是共享中心空间。如果我的意思不够清楚,请尝试使用100dp代替1dp的相同代码。 我知道,在大多数情况下这是看不见的,但如果有人能给我提供一个解决方案,那就太完美了! - Poutrathor
1
@Poutrathor,很有趣,你注意到了那个问题。不幸的是,我目前没有其他解决方案可以提供。 你想要实现什么?也许有人可以帮助你进一步解决问题。 - muthee
你想要实现什么目标呢?当然是完美!开个玩笑,我已经注意到了,所以我报告了它。你的解决方案非常适合时尚的非常短的阴影,我现在很高兴地使用它。我希望我能给你更多的赞 :) - Poutrathor
能否获取一张屏幕截图以便查看它应该是什么样子的?我认为我做错了,因为它看起来有问题。 - user3453281
3
另外,上面的形状是阴影(!),而下面的形状是主要的较大部分。 - sandalone
显示剩余5条评论

30

这很容易实现。

只需构建一个从黑色到透明颜色的 GradientDrawable,然后使用父关系将您的形状放置在要具有阴影的视图附近,然后只需为高度或宽度赋值即可。

以下是一个示例,此文件必须在 res/drawable 中创建,我将其命名为 shadow.xml

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

    <gradient
        android:startColor="#9444"
        android:endColor="#0000"
        android:type="linear"
        android:angle="90"> <!-- Change this value to have the correct shadow angle, must be multiple from 45 -->
    </gradient>

</shape>

例如,在一个LinearLayout之前放入以下代码,将android:layout_widthandroid:layout_height设置为fill_parent2.3dp,就可以在您的LinearLayout上产生漂亮的阴影效果。

<View
    android:id="@+id/shadow"
    android:layout_width="fill_parent"
    android:layout_height="2.3dp"  
    android:layout_above="@+id/id_from_your_LinearLayout" 
    android:background="@drawable/shadow">
</View>

注意1:如果您增加android:layout_height属性的值,将会显示更多阴影。

注意2:如果您将此代码放置在RelativeLayout中,请使用android:layout_above="@+id/id_from_your_LinearLayout"属性,否则请忽略它。

希望对某些人有所帮助。


你好...我知道这是老问题了...但是你的意思是说,唯一实现矩形视图结构周围阴影效果的方法是创建另一个矩形结构,然后将它们对齐吗?谢谢...我是Android新手... - dsdsdsdsd
你好 @dsdsdsdsds,我不确定这是获取阴影效果的唯一方法,因为材料设计引入了许多设计资源,我回家后会看一下,如果我找到了什么,我会在这里评论。 - Murillo Ferreira

28

在Android中,没有显示阴影的属性。但是有一些可能的方法:

  1. 添加一个纯色的LinearLayout,覆盖在实际布局之上,在底部和右侧添加与1或2dp相等的间距。

  2. 使用带阴影的9-patch图像,并将其设置为您的LinearLayout的背景。


9-patch是实现这一点的最佳方式! - Andranik
有没有办法在自定义多边形中使用9 patch?我尝试了这个工具http://inloop.github.io/shadow4android/,但它只能为椭圆形/矩形形状创建阴影。 - rimes

18

对于棒棒糖及以上版本,您可以使用高度(elevation)

对于旧版:

这里有一个来自http://odedhb.blogspot.com/2013/05/android-layout-shadow-without-9-patch.html的懒人技巧。

(toast_frame在KitKat上不起作用,toast中的阴影已被移除)

只需使用:

android:background="@android:drawable/toast_frame"
或:
android:background="@android:drawable/dialog_frame"
作为背景。
例子:
<TextView
        android:layout_width="fill_parent"
        android:text="I am a simple textview with a shadow"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:padding="16dp"
        android:textColor="#fff"
        android:background="@android:drawable/toast_frame"
        />

并且使用不同的背景颜色:

<LinearLayout
        android:layout_height="64dp"
        android:layout_width="fill_parent"
        android:gravity="center"
        android:background="@android:drawable/toast_frame"
        android:padding="4dp"
        >
    <Button
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:text="Button shadow"
            android:background="#33b5e5"
            android:textSize="24sp"
            android:textStyle="bold"
            android:textColor="#fff"
            android:layout_gravity="center|bottom"
            />

</LinearLayout>

同时为白色设置 android:background="@android:drawable/alert_light_frame" - abbasalim

13

试试这个... layout_shadow.xml

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item>
        <shape android:shape="rectangle">
            <solid android:color="#CABBBBBB"/>
            <corners android:radius="2dp" />
        </shape>
    </item>

    <item
        android:left="0dp"
        android:right="0dp"
        android:top="0dp"
        android:bottom="2dp">
        <shape android:shape="rectangle">
            <solid android:color="@android:color/white"/>
            <corners android:radius="2dp" />
        </shape>
    </item>
</layer-list>

按照以下方式应用于您的布局

 android:background="@drawable/layout_shadow"

7
我知道这篇文章已经有点老了,但是大部分的回答都需要添加很多额外的代码。
如果你的背景颜色较浅,你可以简单地使用以下代码:
android:elevation="25dp"

重点不在于代码行数的多少,而在于你的代码应该在所有设备上都能正常运行。你提出的这个高程仅适用于API 21及以上的版本。 - musooff

6

实际上,我同意 @odedbreiner 的看法,但是我将对话框框架放在第一层中,并隐藏白色层下的黑色背景。

    <?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:drawable="@android:drawable/dialog_frame"
        android:right="2dp" android:left="2dp" android:bottom="2dp" android:top="5dp" >
        <shape android:shape="rectangle">
            <corners android:radius="5dp"/>
        </shape>
    </item>
    <item>
        <shape
            android:shape="rectangle">
            <solid android:color="@android:color/white"/>
            <corners android:radius="5dp"/>
        </shape>
    </item>
</layer-list>

2
  1. 保存这张9.png图片。(将其更名为9.png

enter image description here

2.将它保存到你的drawable文件夹下。

3.将它设置到你的布局中。

4.设置内边距。

例如:

<LinearLayout  
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:background="@drawable/shadow"
  android:paddingBottom="6dp"
  android:paddingLeft="5dp"
  android:paddingRight="5dp"
  android:paddingTop="6dp"
>

.
.
.
</LinearLayout>

2
在DRAWABLE中创建一个名为“shadow.xml”的XML示例,使用以下代码(您可以修改它或查找其他更好的代码):
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item>
        <shape android:shape="rectangle">
            <solid android:color="@color/middle_grey"/>
        </shape>
    </item>

    <item android:left="2dp"
          android:right="2dp"
          android:bottom="2dp">
        <shape android:shape="rectangle">
            <solid android:color="@color/white"/>
        </shape>
    </item>

</layer-list>

在LinearLayout或其他您想要创建阴影的Widget中创建XML后,您可以使用BACKGROUND属性来查看效果。它会像这样:
<LinearLayout
    android:orientation="horizontal"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:paddingRight="@dimen/margin_med"
    android:background="@drawable/shadow"
    android:minHeight="?attr/actionBarSize"
    android:gravity="center_vertical">

1
您可以使用以下类来处理XML标签:

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.os.Build;
import android.support.annotation.FloatRange;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;

import com.webappmate.weeassure.R;

/**
 * Created by GIGAMOLE on 13.04.2016.
 */
public class ShadowLayout extends FrameLayout {

    // Default shadow values
    private final static float DEFAULT_SHADOW_RADIUS = 30.0F;
    private final static float DEFAULT_SHADOW_DISTANCE = 15.0F;
    private final static float DEFAULT_SHADOW_ANGLE = 45.0F;
    private final static int DEFAULT_SHADOW_COLOR = Color.DKGRAY;

    // Shadow bounds values
    private final static int MAX_ALPHA = 255;
    private final static float MAX_ANGLE = 360.0F;
    private final static float MIN_RADIUS = 0.1F;
    private final static float MIN_ANGLE = 0.0F;
    // Shadow paint
    private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG) {
        {
            setDither(true);
            setFilterBitmap(true);
        }
    };
    // Shadow bitmap and canvas
    private Bitmap mBitmap;
    private final Canvas mCanvas = new Canvas();
    // View bounds
    private final Rect mBounds = new Rect();
    // Check whether need to redraw shadow
    private boolean mInvalidateShadow = true;

    // Detect if shadow is visible
    private boolean mIsShadowed;

    // Shadow variables
    private int mShadowColor;
    private int mShadowAlpha;
    private float mShadowRadius;
    private float mShadowDistance;
    private float mShadowAngle;
    private float mShadowDx;
    private float mShadowDy;

    public ShadowLayout(final Context context) {
        this(context, null);
    }

    public ShadowLayout(final Context context, final AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ShadowLayout(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        setWillNotDraw(false);
        setLayerType(LAYER_TYPE_HARDWARE, mPaint);

        // Retrieve attributes from xml
        final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ShadowLayout);

        try {
            setIsShadowed(typedArray.getBoolean(R.styleable.ShadowLayout_sl_shadowed, true));
            setShadowRadius(
                    typedArray.getDimension(
                            R.styleable.ShadowLayout_sl_shadow_radius, DEFAULT_SHADOW_RADIUS
                    )
            );
            setShadowDistance(
                    typedArray.getDimension(
                            R.styleable.ShadowLayout_sl_shadow_distance, DEFAULT_SHADOW_DISTANCE
                    )
            );
            setShadowAngle(
                    typedArray.getInteger(
                            R.styleable.ShadowLayout_sl_shadow_angle, (int) DEFAULT_SHADOW_ANGLE
                    )
            );
            setShadowColor(
                    typedArray.getColor(
                            R.styleable.ShadowLayout_sl_shadow_color, DEFAULT_SHADOW_COLOR
                    )
            );
        } finally {
            typedArray.recycle();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        // Clear shadow bitmap
        if (mBitmap != null) {
            mBitmap.recycle();
            mBitmap = null;
        }
    }

    public boolean isShadowed() {
        return mIsShadowed;
    }

    public void setIsShadowed(final boolean isShadowed) {
        mIsShadowed = isShadowed;
        postInvalidate();
    }

    public float getShadowDistance() {
        return mShadowDistance;
    }

    public void setShadowDistance(final float shadowDistance) {
        mShadowDistance = shadowDistance;
        resetShadow();
    }

    public float getShadowAngle() {
        return mShadowAngle;
    }

    @SuppressLint("SupportAnnotationUsage")
    @FloatRange
    public void setShadowAngle(@FloatRange(from = MIN_ANGLE, to = MAX_ANGLE) final float shadowAngle) {
        mShadowAngle = Math.max(MIN_ANGLE, Math.min(shadowAngle, MAX_ANGLE));
        resetShadow();
    }

    public float getShadowRadius() {
        return mShadowRadius;
    }

    public void setShadowRadius(final float shadowRadius) {
        mShadowRadius = Math.max(MIN_RADIUS, shadowRadius);

        if (isInEditMode()) return;
        // Set blur filter to paint
        mPaint.setMaskFilter(new BlurMaskFilter(mShadowRadius, BlurMaskFilter.Blur.NORMAL));
        resetShadow();
    }

    public int getShadowColor() {
        return mShadowColor;
    }

    public void setShadowColor(final int shadowColor) {
        mShadowColor = shadowColor;
        mShadowAlpha = Color.alpha(shadowColor);

        resetShadow();
    }

    public float getShadowDx() {
        return mShadowDx;
    }

    public float getShadowDy() {
        return mShadowDy;
    }

    // Reset shadow layer
    private void resetShadow() {
        // Detect shadow axis offset
        mShadowDx = (float) ((mShadowDistance) * Math.cos(mShadowAngle / 180.0F * Math.PI));
        mShadowDy = (float) ((mShadowDistance) * Math.sin(mShadowAngle / 180.0F * Math.PI));

        // Set padding for shadow bitmap
        final int padding = (int) (mShadowDistance + mShadowRadius);
        setPadding(padding, padding, padding, padding);
        requestLayout();
    }

    private int adjustShadowAlpha(final boolean adjust) {
        return Color.argb(
                adjust ? MAX_ALPHA : mShadowAlpha,
                Color.red(mShadowColor),
                Color.green(mShadowColor),
                Color.blue(mShadowColor)
        );
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        // Set ShadowLayout bounds
        mBounds.set(
                0, 0, MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.getSize(heightMeasureSpec)
        );
    }

    @Override
    public void requestLayout() {
        // Redraw shadow
        mInvalidateShadow = true;
        super.requestLayout();
    }

    @Override
    protected void dispatchDraw(final Canvas canvas) {
        // If is not shadowed, skip
        if (mIsShadowed) {
            // If need to redraw shadow
            if (mInvalidateShadow) {
                // If bounds is zero
                if (mBounds.width() != 0 && mBounds.height() != 0) {
                    // Reset bitmap to bounds
                    mBitmap = Bitmap.createBitmap(
                            mBounds.width(), mBounds.height(), Bitmap.Config.ARGB_8888
                    );
                    // Canvas reset
                    mCanvas.setBitmap(mBitmap);

                    // We just redraw
                    mInvalidateShadow = false;
                    // Main feature of this lib. We create the local copy of all content, so now
                    // we can draw bitmap as a bottom layer of natural canvas.
                    // We draw shadow like blur effect on bitmap, cause of setShadowLayer() method of
                    // paint does`t draw shadow, it draw another copy of bitmap
                    super.dispatchDraw(mCanvas);

                    // Get the alpha bounds of bitmap
                    final Bitmap extractedAlpha = mBitmap.extractAlpha();
                    // Clear past content content to draw shadow
                    mCanvas.drawColor(0, PorterDuff.Mode.CLEAR);

                    // Draw extracted alpha bounds of our local canvas
                    mPaint.setColor(adjustShadowAlpha(false));
                    mCanvas.drawBitmap(extractedAlpha, mShadowDx, mShadowDy, mPaint);

                    // Recycle and clear extracted alpha
                    extractedAlpha.recycle();
                } else {
                    // Create placeholder bitmap when size is zero and wait until new size coming up
                    mBitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
                }
            }

            // Reset alpha to draw child with full alpha
            mPaint.setColor(adjustShadowAlpha(true));
            // Draw shadow bitmap
            if (mCanvas != null && mBitmap != null && !mBitmap.isRecycled())
                canvas.drawBitmap(mBitmap, 0.0F, 0.0F, mPaint);
        }

        // Draw child`s
        super.dispatchDraw(canvas);
    }


}

使用XML中的标签,像这样:

在html中保留标签

<yourpackagename.ShadowLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:layout_gravity="center_horizontal"
        app:sl_shadow_color="#9e000000"
        app:sl_shadow_radius="4dp">
<child views>
</yourpackagename.ShadowLayout>

更新

将以下代码放入资源目录下的attrs.xml文件中

  <declare-styleable name="ShadowLayout">
    <attr name="sl_shadowed" format="boolean"/>
    <attr name="sl_shadow_distance" format="dimension"/>
    <attr name="sl_shadow_angle" format="integer"/>
    <attr name="sl_shadow_radius" format="dimension"/>
    <attr name="sl_shadow_color" format="color"/>
</declare-styleable>

它将为任何视图提供阴影,希望它能对您有所帮助。 - Shashwat Gupta
@CoolMind,我已经更新了R.styleable.ShadowLayout的代码。请查看我的更新答案。 - Shashwat Gupta
谢谢你的更新。我尝试在API 19模拟器上测试它,它可以将TextView居中,但没有添加阴影。 - CoolMind
它可以工作。我认为需要sl_shadowed="true",半径是填充即阴影的宽度,距离和角度使dx、dy偏移量。我认为应该添加类似于以下内容的东西 //设置阴影位图的填充 val padding = (shadowDistance + shadowRadius).toInt() setPadding( if(shadowLeft) padding else 0, if(shadowTop) padding else 0, if(shadowRight) padding else 0, if(shadowBottom) padding else 0) 然后您可以有条件地在首选布局边缘上打开阴影 - Michał Ziobro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接