Skip to content

Commit fc27f1c

Browse files
add cutout shape sample
1 parent 00fbb03 commit fc27f1c

File tree

1 file changed

+343
-0
lines changed

1 file changed

+343
-0
lines changed
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
package com.smarttoolfactory.tutorial1_1basics.chapter6_graphics
2+
3+
import androidx.compose.foundation.background
4+
import androidx.compose.foundation.border
5+
import androidx.compose.foundation.layout.*
6+
import androidx.compose.foundation.shape.CircleShape
7+
import androidx.compose.foundation.shape.GenericShape
8+
import androidx.compose.material.*
9+
import androidx.compose.material.icons.Icons
10+
import androidx.compose.material.icons.filled.Close
11+
import androidx.compose.runtime.Composable
12+
import androidx.compose.runtime.remember
13+
import androidx.compose.ui.Alignment
14+
import androidx.compose.ui.Modifier
15+
import androidx.compose.ui.draw.drawBehind
16+
import androidx.compose.ui.geometry.Offset
17+
import androidx.compose.ui.geometry.Rect
18+
import androidx.compose.ui.geometry.Size
19+
import androidx.compose.ui.graphics.Color
20+
import androidx.compose.ui.graphics.Path
21+
import androidx.compose.ui.platform.LocalDensity
22+
import androidx.compose.ui.text.font.FontWeight
23+
import androidx.compose.ui.unit.Dp
24+
import androidx.compose.ui.unit.LayoutDirection
25+
import androidx.compose.ui.unit.dp
26+
import androidx.compose.ui.unit.sp
27+
import com.smarttoolfactory.tutorial1_1basics.ui.backgroundColor
28+
import com.smarttoolfactory.tutorial1_1basics.ui.components.StyleableTutorialText
29+
30+
31+
@Composable
32+
fun Tutorial6_8Screen1() {
33+
TutorialContent()
34+
}
35+
36+
@Composable
37+
private fun TutorialContent() {
38+
39+
Column(
40+
modifier = Modifier.fillMaxSize().background(backgroundColor)
41+
) {
42+
StyleableTutorialText(
43+
text = "Use **Path.cubicTo** to create a cutout with Bezier curves and " +
44+
"**Path.arcTo** to round corners to draw cutout shape",
45+
bullets = false
46+
)
47+
CustomArcShapeSample()
48+
}
49+
}
50+
51+
@Composable
52+
private fun CustomArcShapeSample() {
53+
Column(
54+
modifier = Modifier
55+
.fillMaxSize()
56+
) {
57+
58+
val content = @Composable {
59+
Column(
60+
modifier = Modifier
61+
.fillMaxSize(),
62+
horizontalAlignment = Alignment.CenterHorizontally
63+
) {
64+
65+
Spacer(modifier = Modifier.height(20.dp))
66+
67+
Text(
68+
"Payment Failed",
69+
color = MaterialTheme.colors.error,
70+
fontWeight = FontWeight.Bold,
71+
fontSize = 18.sp,
72+
)
73+
Spacer(modifier = Modifier.height(10.dp))
74+
Text("Sorry !", fontSize = 26.sp, fontWeight = FontWeight.Bold)
75+
Spacer(modifier = Modifier.height(14.dp))
76+
Text("Your transfer to bank failed", color = Color.Gray)
77+
}
78+
}
79+
80+
val content2 = @Composable {
81+
Column(
82+
modifier = Modifier
83+
.fillMaxSize()
84+
.border(1.dp, Color.Green),
85+
horizontalAlignment = Alignment.CenterHorizontally
86+
) {
87+
88+
Spacer(modifier = Modifier.height(20.dp))
89+
90+
Text(
91+
"Payment Failed",
92+
color = MaterialTheme.colors.error,
93+
fontWeight = FontWeight.Bold,
94+
fontSize = 18.sp,
95+
)
96+
Spacer(modifier = Modifier.height(10.dp))
97+
Text("Sorry !", fontSize = 26.sp, fontWeight = FontWeight.Bold)
98+
Spacer(modifier = Modifier.height(14.dp))
99+
Text("Your transfer to bank failed", color = Color.Gray)
100+
}
101+
}
102+
103+
CustomArcShape(
104+
modifier = Modifier
105+
.padding(10.dp)
106+
.fillMaxWidth()
107+
.height(250.dp)
108+
) {
109+
content()
110+
}
111+
112+
Spacer(modifier = Modifier.height(40.dp))
113+
114+
CustomArcShape(
115+
modifier = Modifier
116+
.padding(10.dp)
117+
.fillMaxWidth()
118+
.height(250.dp)
119+
) {
120+
content2()
121+
}
122+
}
123+
}
124+
125+
@Composable
126+
private fun CustomArcShape(
127+
modifier: Modifier,
128+
elevation: Dp = 4.dp,
129+
color: Color = MaterialTheme.colors.surface,
130+
contentColor: Color = contentColorFor(color),
131+
content: @Composable () -> Unit
132+
) {
133+
134+
Column {
135+
val diameter = 60.dp
136+
val radiusDp = diameter / 2
137+
138+
val cornerRadiusDp = 10.dp
139+
140+
val density = LocalDensity.current
141+
val cutoutRadius = density.run { radiusDp.toPx() }
142+
val cornerRadius = density.run { cornerRadiusDp.toPx() }
143+
144+
val shape = remember {
145+
GenericShape { size: Size, layoutDirection: LayoutDirection ->
146+
this.roundedRectanglePath(
147+
size = size,
148+
cornerRadius = cornerRadius,
149+
fabRadius = cutoutRadius * 2
150+
)
151+
}
152+
}
153+
154+
Spacer(modifier = Modifier.height(diameter / 2))
155+
156+
Box(contentAlignment = Alignment.TopCenter) {
157+
158+
Icon(
159+
modifier = Modifier
160+
.offset(y = -diameter / 5)
161+
.background(Color(0xffD32F2F), CircleShape)
162+
.size(diameter)
163+
.drawBehind {
164+
drawCircle(
165+
Color.Red.copy(.5f),
166+
radius = 1.3f * size.width / 2
167+
)
168+
169+
drawCircle(
170+
Color.Red.copy(.3f),
171+
radius = 1.5f * size.width / 2
172+
)
173+
}
174+
.align(Alignment.TopCenter)
175+
.padding(8.dp),
176+
tint = Color.White,
177+
imageVector = Icons.Filled.Close,
178+
contentDescription = "Close"
179+
)
180+
181+
Surface(
182+
modifier = modifier,
183+
shape = shape,
184+
elevation = elevation,
185+
color = color,
186+
contentColor = contentColor
187+
) {
188+
Column {
189+
Spacer(modifier = Modifier.height(diameter))
190+
content()
191+
}
192+
}
193+
}
194+
}
195+
}
196+
197+
fun Path.roundedRectanglePath(
198+
size: Size,
199+
cornerRadius: Float,
200+
fabRadius: Float,
201+
) {
202+
203+
val centerX = size.width / 2
204+
val x0 = centerX - fabRadius * 1.15f
205+
val y0 = 0f
206+
207+
// offset of the first control point (top part)
208+
val topControlX = x0 + fabRadius * .5f
209+
val topControlY = y0
210+
211+
// offset of the second control point (bottom part)
212+
val bottomControlX = x0
213+
val bottomControlY = y0 + fabRadius
214+
215+
// first curve
216+
// set the starting point of the curve (P2)
217+
val firstCurveStart = Offset(x0, y0)
218+
219+
// set the end point for the first curve (P3)
220+
val firstCurveEnd = Offset(centerX, fabRadius * 1f)
221+
222+
// set the first control point (C1)
223+
val firstCurveControlPoint1 = Offset(
224+
x = topControlX,
225+
y = topControlY
226+
)
227+
228+
// set the second control point (C2)
229+
val firstCurveControlPoint2 = Offset(
230+
x = bottomControlX,
231+
y = bottomControlY
232+
)
233+
234+
// second curve
235+
// end of first curve and start of second curve is the same (P3)
236+
val secondCurveStart = Offset(
237+
x = firstCurveEnd.x,
238+
y = firstCurveEnd.y
239+
)
240+
241+
// end of the second curve (P4)
242+
val secondCurveEnd = Offset(
243+
x = centerX + fabRadius * 1.15f,
244+
y = 0f
245+
)
246+
247+
// set the first control point of second curve (C4)
248+
val secondCurveControlPoint1 = Offset(
249+
x = secondCurveStart.x + fabRadius,
250+
y = bottomControlY
251+
)
252+
253+
// set the second control point (C3)
254+
val secondCurveControlPoint2 = Offset(
255+
x = secondCurveEnd.x - fabRadius / 2,
256+
y = topControlY
257+
)
258+
259+
// Top left arc
260+
val radius = cornerRadius * 2
261+
262+
arcTo(
263+
rect = Rect(
264+
left = 0f,
265+
top = 0f,
266+
right = radius,
267+
bottom = radius
268+
),
269+
startAngleDegrees = 180.0f,
270+
sweepAngleDegrees = 90.0f,
271+
forceMoveTo = false
272+
)
273+
274+
lineTo(x = firstCurveStart.x, y = firstCurveStart.y)
275+
276+
// bezier curve with (P2, C1, C2, P3)
277+
cubicTo(
278+
x1 = firstCurveControlPoint1.x,
279+
y1 = firstCurveControlPoint1.y,
280+
x2 = firstCurveControlPoint2.x,
281+
y2 = firstCurveControlPoint2.y,
282+
x3 = firstCurveEnd.x,
283+
y3 = firstCurveEnd.y
284+
)
285+
286+
// bezier curve with (P3, C4, C3, P4)
287+
cubicTo(
288+
x1 = secondCurveControlPoint1.x,
289+
y1 = secondCurveControlPoint1.y,
290+
x2 = secondCurveControlPoint2.x,
291+
y2 = secondCurveControlPoint2.y,
292+
x3 = secondCurveEnd.x,
293+
y3 = secondCurveEnd.y
294+
)
295+
296+
lineTo(x = size.width - cornerRadius, y = 0f)
297+
298+
// Top right arc
299+
arcTo(
300+
rect = Rect(
301+
left = size.width - radius,
302+
top = 0f,
303+
right = size.width,
304+
bottom = radius
305+
),
306+
startAngleDegrees = -90.0f,
307+
sweepAngleDegrees = 90.0f,
308+
forceMoveTo = false
309+
)
310+
311+
lineTo(x = 0f + size.width, y = size.height - cornerRadius)
312+
313+
// Bottom right arc
314+
arcTo(
315+
rect = Rect(
316+
left = size.width - radius,
317+
top = size.height - radius,
318+
right = size.width,
319+
bottom = size.height
320+
),
321+
startAngleDegrees = 0f,
322+
sweepAngleDegrees = 90.0f,
323+
forceMoveTo = false
324+
)
325+
326+
lineTo(x = cornerRadius, y = size.height)
327+
328+
// Bottom left arc
329+
arcTo(
330+
rect = Rect(
331+
left = 0f,
332+
top = size.height - radius,
333+
right = radius,
334+
bottom = size.height
335+
),
336+
startAngleDegrees = 90.0f,
337+
sweepAngleDegrees = 90.0f,
338+
forceMoveTo = false
339+
)
340+
341+
lineTo(x = 0f, y = cornerRadius)
342+
close()
343+
}

0 commit comments

Comments
 (0)