我必须根据以下设计实现横幅:
这里的复杂性在于圆形徽标的阴影,徽标圆圈的阴影是横幅矩形卡片的阴影的延续。下图概述了阴影的边框:
当然,不应将阴影投射在卡片顶部下方的表面上。徽标中心也与卡的边框有一些偏移。
如何实现此效果?标准的android形状不允许形成这样的轮廓。手动绘制所有内容似乎对于这个问题来说太复杂了。我们有minSdkVersion
21。
答案 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>
结果将是:
主要思想是创建两个带有图像的卡片视图,一个在主卡片下,另一个在卡片视图中,并使用边距使外观看起来像一个圆圈。
答案 1 :(得分:0)
我最终使用了MaterialShapeDrawable
类。它允许通过手动定义应如何绘制边缘来自定义可绘制边缘。这可以通过派生EdgeTreatment
类来实现(对于CornerTreatment
的角点也可能是相似的)。
结果,横幅的背景如下:
这是横幅顶部边缘处理类:
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"
最终结果: