Тень произвольной формы - PullRequest
0 голосов
/ 29 мая 2020

Мне нужно реализовать баннер в соответствии со следующими дизайнами:

image

Сложность здесь в тени круглого ло go, тенью ло go круг является продолжением тени прямоугольника angular карты знамени. Граница тени обведена на следующем изображении:

shadow

Конечно, тень не должна отбрасываться на поверхность ниже верхней стороны карта. Также центр lo go имеет некоторое смещение от границы карты.

Как я могу добиться этого эффекта? Стандартные формы android не позволяют сформировать такой контур. Рисовать все вручную кажется слишком сложным решением проблемы. Имеем minSdkVersion 21.

Ответы [ 2 ]

1 голос
/ 29 мая 2020

Вы можете добиться этого с помощью этого трюка

<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

Основная идея - создать два cardview с изображениями, одно под основной карточкой, а другое - в карточном виде, и использование полей делает изображение похожим на один круг.

0 голосов
/ 04 июня 2020

Я остановился на классе 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

...