Skip to content

Commit 3b797e9

Browse files
author
iOrchid
committed
pathEffect
1 parent d1dc865 commit 3b797e9

File tree

2 files changed

+347
-0
lines changed

2 files changed

+347
-0
lines changed

compose/src/main/java/org/zhiwei/compose/model/Screen.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import org.zhiwei.compose.screen.gesture.TapDragGestures_Screen
2222
import org.zhiwei.compose.screen.gesture.TouchImage_Screen
2323
import org.zhiwei.compose.screen.gesture.TransformGestures_Screen
2424
import org.zhiwei.compose.screen.graphics.CanvasBasic_Screen
25+
import org.zhiwei.compose.screen.graphics.CanvasPathEffect_Screen
2526
import org.zhiwei.compose.screen.graphics.CanvasPathOperations_Screen
2627
import org.zhiwei.compose.screen.graphics.CanvasPath_Screen
2728
import org.zhiwei.compose.screen.layout_state.ConstraintLayout_Screen
@@ -209,6 +210,10 @@ internal object GraphicsScreenUIs {
209210
"CanvasPathOps",
210211
"canvas绘制path,不同的图形使用交互方式不同,表现层叠交集效果。"
211212
) { CanvasPathOperations_Screen(modifier) },
213+
CourseItemModel(
214+
"CanvasPathEffect",
215+
"canvas绘制path的时候,可以设置不同的pathEffect效果。"
216+
) { CanvasPathEffect_Screen(modifier) },
212217
)
213218
}
214219

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
package org.zhiwei.compose.screen.graphics
2+
3+
import androidx.compose.animation.core.LinearEasing
4+
import androidx.compose.animation.core.RepeatMode
5+
import androidx.compose.animation.core.animateFloat
6+
import androidx.compose.animation.core.infiniteRepeatable
7+
import androidx.compose.animation.core.rememberInfiniteTransition
8+
import androidx.compose.animation.core.tween
9+
import androidx.compose.foundation.Canvas
10+
import androidx.compose.foundation.background
11+
import androidx.compose.foundation.layout.Column
12+
import androidx.compose.foundation.layout.fillMaxSize
13+
import androidx.compose.foundation.layout.height
14+
import androidx.compose.foundation.layout.padding
15+
import androidx.compose.foundation.rememberScrollState
16+
import androidx.compose.foundation.verticalScroll
17+
import androidx.compose.material3.Slider
18+
import androidx.compose.material3.Text
19+
import androidx.compose.runtime.Composable
20+
import androidx.compose.runtime.getValue
21+
import androidx.compose.runtime.mutableFloatStateOf
22+
import androidx.compose.runtime.mutableStateOf
23+
import androidx.compose.runtime.remember
24+
import androidx.compose.runtime.setValue
25+
import androidx.compose.ui.Modifier
26+
import androidx.compose.ui.draw.shadow
27+
import androidx.compose.ui.geometry.Offset
28+
import androidx.compose.ui.geometry.Size
29+
import androidx.compose.ui.graphics.Color
30+
import androidx.compose.ui.graphics.Path
31+
import androidx.compose.ui.graphics.PathEffect
32+
import androidx.compose.ui.graphics.StampedPathEffectStyle
33+
import androidx.compose.ui.graphics.drawscope.Stroke
34+
import androidx.compose.ui.text.font.FontWeight
35+
import androidx.compose.ui.tooling.preview.Preview
36+
import androidx.compose.ui.unit.dp
37+
import androidx.compose.ui.unit.sp
38+
import kotlin.math.roundToInt
39+
40+
@Composable
41+
internal fun CanvasPathEffect_Screen(modifier: Modifier = Modifier) {
42+
Column(
43+
modifier
44+
.fillMaxSize()
45+
.verticalScroll(rememberScrollState())
46+
) {
47+
Text(
48+
"dashedPathEffect",
49+
fontWeight = FontWeight.Bold,
50+
fontSize = 20.sp,
51+
modifier = Modifier.padding(8.dp)
52+
)
53+
DashedEffectExample()
54+
DashPathEffectAnimatedExample()
55+
56+
Text(
57+
"cornerPathEffect",
58+
fontWeight = FontWeight.Bold,
59+
fontSize = 20.sp,
60+
modifier = Modifier.padding(8.dp)
61+
)
62+
CornerPathEffectExample()
63+
64+
Text(
65+
"chainPathEffect",
66+
fontWeight = FontWeight.Bold,
67+
fontSize = 20.sp,
68+
modifier = Modifier.padding(8.dp)
69+
)
70+
ChainPathEffectExample()
71+
Text(
72+
"stompedPathEffect",
73+
fontWeight = FontWeight.Bold,
74+
fontSize = 20.sp,
75+
modifier = Modifier.padding(8.dp)
76+
)
77+
StompedPathEffectExample()
78+
}
79+
}
80+
81+
82+
@Composable
83+
private fun DashedEffectExample() {
84+
85+
var onInterval by remember { mutableFloatStateOf(20f) }
86+
var offInterval by remember { mutableFloatStateOf(20f) }
87+
var phase by remember { mutableFloatStateOf(10f) }
88+
89+
val pathEffect = PathEffect.dashPathEffect(
90+
intervals = floatArrayOf(onInterval, offInterval),
91+
phase = phase
92+
)
93+
94+
DrawPathEffect(pathEffect = pathEffect)
95+
96+
Text(text = "onInterval ${onInterval.roundToInt()}")
97+
Slider(
98+
value = onInterval,
99+
onValueChange = { onInterval = it },
100+
valueRange = 0f..100f,
101+
)
102+
103+
104+
Text(text = "offInterval ${offInterval.roundToInt()}")
105+
Slider(
106+
value = offInterval,
107+
onValueChange = { offInterval = it },
108+
valueRange = 0f..100f,
109+
)
110+
111+
Text(text = "phase ${phase.roundToInt()}")
112+
Slider(
113+
value = phase,
114+
onValueChange = { phase = it },
115+
valueRange = 0f..100f,
116+
)
117+
}
118+
119+
@Composable
120+
private fun DashPathEffectAnimatedExample() {
121+
122+
val transition = rememberInfiniteTransition(label = "path effect")
123+
124+
val phase by transition.animateFloat(
125+
initialValue = 0f,
126+
targetValue = 40f,
127+
animationSpec = infiniteRepeatable(
128+
animation = tween(
129+
durationMillis = 500,
130+
easing = LinearEasing
131+
),
132+
repeatMode = RepeatMode.Restart
133+
), label = "path effect"
134+
)
135+
136+
val pathEffect = PathEffect.dashPathEffect(
137+
intervals = floatArrayOf(20f, 20f),
138+
phase = phase
139+
)
140+
141+
DrawPathEffect(pathEffect = pathEffect)
142+
}
143+
144+
@Composable
145+
private fun CornerPathEffectExample() {
146+
147+
var cornerRadius by remember { mutableFloatStateOf(20f) }
148+
149+
val pathEffect = PathEffect.cornerPathEffect(cornerRadius)
150+
DrawRect(pathEffect)
151+
152+
Text(text = "cornerRadius ${cornerRadius.roundToInt()}")
153+
Slider(
154+
value = cornerRadius,
155+
onValueChange = { cornerRadius = it },
156+
valueRange = 0f..100f,
157+
)
158+
}
159+
160+
@Composable
161+
private fun ChainPathEffectExample() {
162+
163+
var onInterval1 by remember { mutableFloatStateOf(20f) }
164+
var offInterval1 by remember { mutableFloatStateOf(20f) }
165+
var phase1 by remember { mutableFloatStateOf(10f) }
166+
167+
var cornerRadius by remember { mutableFloatStateOf(20f) }
168+
169+
val pathEffect1 = PathEffect.dashPathEffect(
170+
intervals = floatArrayOf(onInterval1, offInterval1),
171+
phase = phase1
172+
)
173+
174+
val pathEffect2 = PathEffect.cornerPathEffect(cornerRadius)
175+
val pathEffect = PathEffect.chainPathEffect(outer = pathEffect1, inner = pathEffect2)
176+
177+
DrawRect(pathEffect)
178+
179+
Text(text = "onInterval1 ${onInterval1.roundToInt()}")
180+
Slider(
181+
value = onInterval1,
182+
onValueChange = { onInterval1 = it },
183+
valueRange = 0f..100f,
184+
)
185+
186+
187+
Text(text = "offInterval1 ${offInterval1.roundToInt()}")
188+
Slider(
189+
value = offInterval1,
190+
onValueChange = { offInterval1 = it },
191+
valueRange = 0f..100f,
192+
)
193+
194+
Text(text = "phase1 ${phase1.roundToInt()}")
195+
Slider(
196+
value = phase1,
197+
onValueChange = { phase1 = it },
198+
valueRange = 0f..100f,
199+
)
200+
201+
Text(text = "cornerRadius ${cornerRadius.roundToInt()}")
202+
Slider(
203+
value = cornerRadius,
204+
onValueChange = { cornerRadius = it },
205+
valueRange = 0f..100f,
206+
)
207+
}
208+
209+
@Composable
210+
private fun StompedPathEffectExample() {
211+
212+
var stompedPathEffectStyle by remember {
213+
mutableStateOf(StampedPathEffectStyle.Translate)
214+
}
215+
216+
var advance by remember { mutableFloatStateOf(20f) }
217+
var phase by remember { mutableFloatStateOf(20f) }
218+
219+
val path = remember {
220+
Path().apply {
221+
moveTo(10f, 0f)
222+
lineTo(20f, 10f)
223+
lineTo(10f, 20f)
224+
lineTo(0f, 10f)
225+
}
226+
}
227+
228+
val pathEffect = PathEffect.stampedPathEffect(
229+
shape = path,
230+
advance = advance,
231+
phase = phase,
232+
style = stompedPathEffectStyle
233+
)
234+
235+
DrawPathEffect(pathEffect = pathEffect)
236+
237+
Text(text = "advance ${advance.roundToInt()}")
238+
Slider(
239+
value = advance,
240+
onValueChange = { advance = it },
241+
valueRange = 0f..100f,
242+
)
243+
244+
245+
Text(text = "phase ${phase.roundToInt()}")
246+
Slider(
247+
value = phase,
248+
onValueChange = { phase = it },
249+
valueRange = 0f..100f,
250+
)
251+
252+
ExposedSelectionMenu(title = "StompedEffect Style",
253+
index = when (stompedPathEffectStyle) {
254+
StampedPathEffectStyle.Translate -> 0
255+
StampedPathEffectStyle.Rotate -> 1
256+
else -> 2
257+
},
258+
options = listOf("Translate", "Rotate", "Morph"),
259+
onSelected = {
260+
println("STOKE CAP $it")
261+
stompedPathEffectStyle = when (it) {
262+
0 -> StampedPathEffectStyle.Translate
263+
1 -> StampedPathEffectStyle.Rotate
264+
else -> StampedPathEffectStyle.Morph
265+
}
266+
}
267+
)
268+
269+
}
270+
271+
272+
@Composable
273+
private fun DrawRect(pathEffect: PathEffect) {
274+
Canvas(modifier = canvasModifier) {
275+
val horizontalCenter = size.width / 2
276+
val verticalCenter = size.height / 2
277+
val radius = size.height / 3
278+
drawRect(
279+
Color.Black,
280+
topLeft = Offset(horizontalCenter - radius, verticalCenter - radius),
281+
size = Size(radius * 2, radius * 2),
282+
style = Stroke(
283+
width = 2.dp.toPx(),
284+
pathEffect = pathEffect
285+
286+
)
287+
)
288+
}
289+
}
290+
291+
@Composable
292+
private fun DrawPathEffect(pathEffect: PathEffect) {
293+
Canvas(modifier = canvasModifier) {
294+
295+
val canvasWidth = size.width
296+
val canvasHeight = size.height
297+
298+
val radius = (canvasHeight / 4).coerceAtMost(canvasWidth / 6)
299+
val space = (canvasWidth - 4 * radius) / 3
300+
301+
drawRect(
302+
topLeft = Offset(space, (canvasHeight - 2 * radius) / 2),
303+
size = Size(radius * 2, radius * 2),
304+
color = Color.Black,
305+
style = Stroke(
306+
width = 2.dp.toPx(),
307+
pathEffect = pathEffect
308+
309+
)
310+
)
311+
312+
drawCircle(
313+
Color.Black,
314+
center = Offset(space * 2 + radius * 3, canvasHeight / 2),
315+
radius = radius,
316+
style = Stroke(width = 2.dp.toPx(), pathEffect = pathEffect)
317+
)
318+
319+
drawLine(
320+
color = Color.Black,
321+
start = Offset(50f, canvasHeight - 50f),
322+
end = Offset(canvasWidth - 50f, canvasHeight - 50f),
323+
strokeWidth = 2.dp.toPx(),
324+
pathEffect = pathEffect
325+
)
326+
327+
}
328+
}
329+
330+
private val canvasModifier = Modifier
331+
.padding(8.dp)
332+
.shadow(1.dp)
333+
.background(Color.White)
334+
.fillMaxSize()
335+
.height(200.dp)
336+
337+
338+
@Preview
339+
@Composable
340+
private fun PreviewPathEffect() {
341+
CanvasPathEffect_Screen()
342+
}

0 commit comments

Comments
 (0)