How to create slashed custom view with MaterialShapeDrawable

image source

The MaterialShapeDrawable was introduced in Material Design 2.0.
For building MaterialShapeDrawable we are going to need ShapeAppearanceModel. According to documentation, the ShapeAppearanceModel models the edges and corners of a shape, which are used by MaterialShapeDrawable to generate and render the shape for a view’s background. Here is MaterialShapeDrawable with rounded corners example:

val shapeAppearanceModel = ShapeAppearanceModel().toBuilder().setAllCornerSizes(12F) 
val materialShapeDrawable = MaterialShapeDrawable(shapeAppearanceModel).apply {
setTint(Color.YELLOW)
paintStyle = Paint.Style.FILL
}
view.backgoround = materialShapeDrawable

After the introduction to MaterialShapeDrawable let’s create the slashed custom view:

First we need to create custom EdgeTreatment for the splash Edge:

class SlashEdgeTreatment(@Dimension val leanSize: Float) : EdgeTreatment() {

override fun getEdgePath(length: Float, center: Float, interpolation: Float, shapePath: ShapePath) {
shapePath.lineTo(length, leanSize)
}
}

enum class Edge {
LEFT,
RIGHT
}

This is the Extention function for cutting the left or right side of the view with given leanSize:

fun View.setSlashEdgeBackground(
color: Int,
leanSize: Float,
cornerSize: Float,
edge: Edge
) {
val shapeAppearanceModelBuilder = ShapeAppearanceModel().toBuilder()

val shapeAppearanceModel = when (edge) {
Edge.LEFT -> shapeAppearanceModelBuilder
.setRightEdge(SlashEdgeTreatment(leanSize))
.setBottomLeftCornerSize(cornerSize)
.setTopLeftCornerSize(cornerSize)
Edge.RIGHT -> shapeAppearanceModelBuilder
.setLeftEdge(SlashEdgeTreatment(leanSize))
.setBottomRightCornerSize(cornerSize)
.setTopRightCornerSize(cornerSize)
}.build()

background = MaterialShapeDrawable(shapeAppearanceModel).apply {
setTint(color)
paintStyle = Paint.Style.FILL
}
}

As one could guess, we need two custom views(SlashEdgeTextView) with a cut from the left or right side and combined together in another view(SlashedView) to get the desired result.

attrs.xml for the SlashEdgeTextView:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="SlashTextView">
<attr name="splashSide">
<enum name="left" value="0" />
<enum name="right" value="1" />
</attr>
<attr name="fillColor" format="color" />
<attr name="leanSize" format="dimension" />
</declare-styleable>
</resources>

SlashEdgeTextView:

class SlashEdgeTextView @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attributeSet, defStyleAttr) {

init {
val cornerRadius = resources.getDimension(R.dimen.line_badge_view_corner_radius)
context.theme.obtainStyledAttributes(
attributeSet,
R.styleable.SlashTextView,
defStyleAttr, 0
).apply {
try {
val leanSize = getDimension(R.styleable.SlashTextView_leanSize, 0F)
val color = getColor(R.styleable.SlashTextView_fillColor, Color.WHITE)
val edge = when (getInt(R.styleable.SlashTextView_splashSide, 0)) {
0 -> Edge.LEFT
else
-> Edge.RIGHT
}
setSlashEdgeBackground(color, leanSize, cornerRadius, edge)
} finally {
recycle()
}
}
}
}

Finally lets combine the two SlashEdgeTextView into custom SlashView

slash_layout.xml :

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="@dimen/splash_view_height"
android:background="@color/transparent"
android:translationX="@dimen/splash_view_translation_x"
app:layout_constraintEnd_toEndOf="@+id/title_right">

<com.breadcrumb.transportleg.SlashEdgeTextView
android:id="@+id/title_left"
style="?attr/button_white"
android:layout_width="0dp"
android:layout_height="match_parent"
android:maxLines="1"
android:gravity="center"
android:minWidth="@dimen/splash_leg_view_child_min_width"
android:paddingStart="@dimen/margin_small"
android:paddingEnd="@dimen/margin_small"
android:translationX="@dimen/splash_leg_view_left_side_translation_x"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:leanSize="@dimen/splash_lean_size"
app:splashSide="left"
tools:fillColor="@color/green"
tools:text="U3" />

<com.breadcrumb.transportleg.SlashEdgeTextView
android:id="@+id/title_right"
style="?attr/button_white"
android:layout_width="0dp"
android:gravity="center"
android:layout_height="match_parent"
android:maxLines="1"
android:minWidth="@dimen/splash_min_width"
android:paddingStart="@dimen/splash_margin_small"
android:paddingEnd="@dimen/margin_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/title_left"
app:layout_constraintTop_toTopOf="parent"
app:leanSize="@dimen/splash_lean_size"
app:splashSide="right"
tools:fillColor="@color/color_blue"
tools:text="U7" />
</androidx.constraintlayout.widget.ConstraintLayout>

As you may have noticed there is a negative android:translationX set, with the negative translationX we control the gap size between SlashEdgeTextView which depends on slash edge lean size.

The final SlashView:

class SlashTransportationLegView @JvmOverloads constructor(
context: Context,
attributeSet: AttributeSet? = null,
defStyleAttr: Int = 0

) : FrameLayout(context, attributeSet, defStyleAttr) {
init {
LayoutInflater.from(context).inflate(
R.layout.slash_transportation_leg,
this,
true
)
}

fun setData(
titleLeft: String, @ColorInt colorTitleLeft: Int,
titleRight: String, @ColorInt colorTitleRight: Int
) {
title_left.apply {
text = titleLeft
setBackgroundColor(colorTitleLeft)
}
title_right.apply {
text = titleRight
setBackgroundColor(colorTitleRight)
}
}

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store