首页 > 解决方案 > 动态内容的自定义背景形状 - 适当缩放

问题描述

我有一个关于在an​​droid中使用自定义背景(形状)的正确方法的问题。我有以下屏幕(用 adobe xd 制作):

网格布局的自定义背景

透明的白色形状应用作放置网格布局的滚动视图的背景。现在我希望在填充网格布局时背景能够正确缩放,这意味着只有高度应该改变。这是填充网格布局后所需的外观:

在此处输入图像描述

首先,我通过简单地将图像设置为网格布局的背景来尝试它,这是输出:

在此处输入图像描述

问题很明显,我不希望圆改变它的半径。然后我想出了分割背景的想法,我把背景的上半部分剪下来,放到一个imageview中,和gridlayout一起放在一个相对的布局里面。这似乎可行,至少在我测试过的那些设备上是这样。但我不认为这是要走的路,因为我必须为每个可能的屏幕尺寸提供图像(以及旋转怎么样 - 是的,更多自定义图像......)

有没有一种正确且更动态的方式来做这件事?我也发现了这个:Android custom vector scaling,但似乎存在与我完全相同的问题 - 当屏幕尺寸发生变化时缩放背景

标签: androidkotlinview

解决方案


您应该能够使用MaterialShapeDrawable.

首先将材料组件依赖项添加到您的 gradleimplementation 'com.google.android.material:material:1.1.0'

然后,您可以开始对组件的材质版本应用边角处理,例如CardViewTextView

下面的代码将内置应用TriangleEdgeTreatmentMaterialTextView. 请参阅下图了解这在设备上的外观(不完美,只是可以实现的示例)

//In OnCreateView
val text = view.findViewById<MaterialTextView>(R.id.text)
val topEdge = TriangleEdgeTreatment(resources.getDimension(R.dimen.triangle_depth), true)
val shapeAppearance = ShapeAppearanceModel.builder()
shapeAppearance.setTopEdge(topEdge)

card.background = MaterialShapeDrawable(shape.build()).apply {
    setTint(ContextCompat.getColor(this@MyFragment.context!!, R.color.colorPrimary))
    paintStyle = Paint.Style.FILL
}

在此处输入图像描述

您可以覆盖EdgeTreatment该类以创建自己的边缘实现。

class CurvedEdgeTreatment() : EdgeTreatment() {
    override fun getEdgePath(
        length: Float,
        center: Float,
        interpolation: Float,
        shapePath: ShapePath
    ) {
    //Your implementation
    }
}

这就是我希望您解决问题的方式。有关更多信息MaterialShapeDrawable以及可以实现的目标,您可以查看此链接,它包含更多关于拐角和边缘处理的示例,并且可能对您的特定场景有所帮助。

这是一个实际的实现,可以满足您的要求:

class CurvedEdgeTreatment(
    private val diameter: Float,
    private val roundedCornerRadius: Float = 0f,
    private val horizontalOffset: Float = 0f,
    private val verticalOffset: Float = 0f
) : EdgeTreatment() {
    private val arcQuarter = 90f
    private val arcHalf = 180f
    private val angleUp = 270f
    private val angleLeft = 180f

    override fun getEdgePath(
        length: Float,
        center: Float,
        interpolation: Float,
        shapePath: ShapePath
    ) {
        val radius = diameter / 2f
        val roundedCornerOffset: Float = interpolation * roundedCornerRadius
        val middle: Float = center + horizontalOffset

        // The center offset of the cutout tweens between the vertical offset when attached, and the
        // cradleRadius as it becomes detached.
        val verticalOffset: Float =
            interpolation * verticalOffset + (1 - interpolation) * radius
        val verticalOffsetRatio = verticalOffset / radius
        if (verticalOffsetRatio >= 1.0f) {
            // Vertical offset is so high that there's no curve to draw in the edge, i.e., the fab is
            // actually above the edge so just draw a straight line.
            shapePath.lineTo(length, 0f)
            return  // Early exit.
        }

        // Calculate the path of the cutout by calculating the location of two adjacent circles. One
        // circle is for the rounded corner. If the rounded corner circle radius is 0 the corner will
        // not be rounded. The other circle is the cutout.
        // Calculate the X distance between the center of the two adjacent circles using pythagorean
        // theorem.
        val distanceBetweenCenters = radius + roundedCornerOffset
        val distanceBetweenCentersSquared =
            distanceBetweenCenters * distanceBetweenCenters
        val distanceY = verticalOffset + roundedCornerOffset
        val distanceX =
            sqrt(distanceBetweenCentersSquared - (distanceY * distanceY).toDouble()).toFloat()

        // Calculate the x position of the rounded corner circles.
        val leftRoundedCornerCircleX = middle - distanceX
        val rightRoundedCornerCircleX = middle + distanceX

        // Calculate the arc between the center of the two circles.
        val cornerRadiusArcLength =
            Math.toDegrees(atan(distanceX / distanceY.toDouble())).toFloat()
        val cutoutArcOffset: Float = arcQuarter - cornerRadiusArcLength

        // Draw the starting line up to the left rounded corner.
        shapePath.lineTo( /* x= */leftRoundedCornerCircleX, 0f)

        // Draw the arc for the left rounded corner circle. The bounding box is the area around the
        // circle's center which is at `(leftRoundedCornerCircleX, roundedCornerOffset)`.
        shapePath.addArc( /* left= */
            leftRoundedCornerCircleX - roundedCornerOffset, 0f,  /* right= */
            leftRoundedCornerCircleX + roundedCornerOffset,  /* bottom= */
            roundedCornerOffset * 2,  /* startAngle= */
            angleUp,  /* sweepAngle= */
            cornerRadiusArcLength
        )

        // Draw the cutout circle.
        shapePath.addArc( /* left= */
            middle - radius,  /* top= */
            -radius - verticalOffset,  /* right= */
            middle + radius,  /* bottom= */
            radius - verticalOffset,  /* startAngle= */
            angleLeft - cutoutArcOffset,  /* sweepAngle= */
            cutoutArcOffset * 2 - arcHalf
        )

        // Draw an arc for the right rounded corner circle. The bounding box is the area around the
        // circle's center which is at `(rightRoundedCornerCircleX, roundedCornerOffset)`.
        shapePath.addArc( /* left= */
            rightRoundedCornerCircleX - roundedCornerOffset, 0f,  /* right= */
            rightRoundedCornerCircleX + roundedCornerOffset,  /* bottom= */
            roundedCornerOffset * 2,  /* startAngle= */
            angleUp - cornerRadiusArcLength,  /* sweepAngle= */
            cornerRadiusArcLength
        )

        // Draw the ending line after the right rounded corner.
        shapePath.lineTo( /* x= */length, 0f)
    }
}

BottomAppBarEdgeTreatment这个实现主要是从可以在这里找到的代码中复制的,进行了一些小的更改以使其对您的场景更有用。


推荐阅读