Shadow of a custom shape

佐手、 提交于 2020-06-29 04:15:23

问题


I have to implement a banner according to the following designs:

The complexity here is in the shadow of the round logo, the shadow of the logo circle is the continuation of the shadow of the rectangular card of the banner. The border of the shadow is outlined in the following image:

Of course the shadow shouldn't be casted on the surface below the top side of the card. Also the logo center has some offset from the border of the card.

How can I achieve this effect? Standard android shapes doesn't allow to form such a contour. Drawing everything manually seems to be too complex decision for the problem. We have minSdkVersion 21.


回答1:


You can achieve it using this trick

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

The result will be:

The main idea create two cardview with images, one under the main card and another one in the cardview and using margins make the look like one circle.




回答2:


I've ended up on using the MaterialShapeDrawable class. It allows to customize drawable edges by manually defining how the edge should de drawn. This is achieved by deriving EdgeTreatment class (similar is possible for corners with CornerTreatment).

As a result the background of the banner looks like this:

Here is the banner top edge treatment class:

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

The method to construct a background drawable for banner:

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
}

Then this drawable is used as a background of the banner view:

banner.background = createVerticalBannerBackground(...)

And also it is necessary to set clipChildren attribute of the parent view of the banner to false:

android:clipChildren="false"

The final result:



来源:https://stackoverflow.com/questions/62090962/shadow-of-a-custom-shape

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!