自定义形状的阴影

时间:2020-05-29 16:54:14

标签: android view shadow shapes

我必须根据以下设计实现横幅:

这里的复杂性在于圆形徽标的阴影,徽标圆圈的阴影是横幅矩形卡片的阴影的延续。下图概述了阴影的边框:

shadow

当然,不应将阴影投射在卡片顶部下方的表面上。徽标中心也与卡的边框有一些偏移。

如何实现此效果?标准的android形状不允许形成这样的轮廓。手动绘制所有内容似乎对于这个问题来说太复杂了。我们有minSdkVersion 21。

2 个答案:

答案 0 :(得分:1)

您可以使用此技巧来实现

<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto">



    <androidx.cardview.widget.CardView
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="20dp"
        app:cardCornerRadius="56dp"
        app:cardElevation="16dp"
        android:layout_width="56dp"
        android:layout_height="56dp">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <de.hdodenhof.circleimageview.CircleImageView
                android:layout_width="match_parent"
                android:src="@color/colorPrimary"
                android:layout_height="match_parent"/>
        </LinearLayout>
    </androidx.cardview.widget.CardView>


    <androidx.cardview.widget.CardView
        app:cardElevation="16dp"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginTop="48dp"
        android:layout_width="350dp"
        android:layout_height="500dp">

        <LinearLayout
            android:layout_marginTop="-28dp"
            android:layout_gravity="center_horizontal"
            android:layout_width="56dp"
            android:layout_height="56dp">
            <de.hdodenhof.circleimageview.CircleImageView
                android:layout_width="match_parent"
                android:src="@color/colorPrimary"
                android:layout_height="match_parent"/>
        </LinearLayout>

    </androidx.cardview.widget.CardView>

</androidx.constraintlayout.widget.ConstraintLayout> 

结果将是:

enter image description here

主要思想是创建两个带有图像的卡片视图,一个在主卡片下,另一个在卡片视图中,并使用边距使外观看起来像一个圆圈。

答案 1 :(得分:0)

我最终使用了MaterialShapeDrawable类。它允许通过手动定义应如何绘制边缘来自定义可绘制边缘。这可以通过派生EdgeTreatment类来实现(对于CornerTreatment的角点也可能是相似的)。

结果,横幅的背景如下:

banner background shape

这是横幅顶部边缘处理类:

private class BannerTopEdgeTreatment(private val circleRadius: Int,
                                     private val circleCenterOffset: Int) : EdgeTreatment(), Cloneable {

    init {
        // do not allow to offset circle center up
        if (circleCenterOffset < 0)
            throw IllegalArgumentException()
    }

    override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) {

        // use interpolated radius
        val radius = circleRadius * interpolation

        // if circle lays entirely inside the rectangle then just draw a line
        val circleTop = circleCenterOffset - radius
        if (circleTop >= 0) {
            shapePath.lineTo(length, 0f)
            return
        }

        // calc the distance from the center of the edge to the point where arc begins
        // ignore the case when the radius is so big that the circle fully covers the edge
        // just draw a line for now, but maybe it can be replaced by drawing the arc
        val c = sqrt(radius.pow(2) - circleCenterOffset.toDouble().pow(2))
        if (c > center) {
            shapePath.lineTo(length, 0f)
            return
        }

        // draw a line from the left corner to the start of the arc
        val arcStart = center - c
        shapePath.lineTo(arcStart.toFloat(), 0f)

        // calc the start angle and the sweep angle of the arc and draw the arc
        // angles are measured clockwise with 0 degrees at 3 o'clock
        val alpha = Math.toDegrees(asin(circleCenterOffset / radius).toDouble())
        val startAngle = 180 + alpha
        val sweepAngle = 180 - 2 * alpha
        shapePath.addArc(
                center - radius,
                circleCenterOffset - radius,
                center + radius,
                circleCenterOffset + radius,
                startAngle.toFloat(),
                sweepAngle.toFloat())

        // draw the line from the end of the arc to the right corner
        shapePath.lineTo(length, 0f)
    }
}

构造横幅可绘制背景的方法:

fun createBannerBackgroundDrawable(backgroundColor: ColorStateList,
                                   @Px circleRadius: Int,
                                   @Px circleCenterOffset: Int,
                                   @Px cornersRadius: Int,
                                   @Px elevation: Int): Drawable {
    val appearanceModel = ShapeAppearanceModel.builder()
            .setTopEdge(BannerTopEdgeTreatment(circleRadius, circleCenterOffset))
            .setAllCorners(CornerFamily.ROUNDED, cornersRadius.toFloat())
            .build()
    val drawable = MaterialShapeDrawable(appearanceModel)
    drawable.fillColor = backgroundColor
    drawable.elevation = elevation.toFloat()
    return drawable
}

然后将此可绘制对象用作横幅视图的背景:

banner.background = createVerticalBannerBackground(...)

而且还必须将横幅广告的父视图的clipChildren属性设置为false

android:clipChildren="false"

最终结果:

banner with shadow