1
+ package org.zhiwei.compose.screen.graphics
2
+
3
+ import androidx.compose.foundation.Canvas
4
+ import androidx.compose.foundation.background
5
+ import androidx.compose.foundation.layout.Column
6
+ import androidx.compose.foundation.layout.fillMaxSize
7
+ import androidx.compose.foundation.layout.fillMaxWidth
8
+ import androidx.compose.foundation.layout.height
9
+ import androidx.compose.foundation.layout.padding
10
+ import androidx.compose.foundation.rememberScrollState
11
+ import androidx.compose.foundation.verticalScroll
12
+ import androidx.compose.material3.Slider
13
+ import androidx.compose.material3.Text
14
+ import androidx.compose.runtime.Composable
15
+ import androidx.compose.runtime.getValue
16
+ import androidx.compose.runtime.mutableFloatStateOf
17
+ import androidx.compose.runtime.mutableStateOf
18
+ import androidx.compose.runtime.remember
19
+ import androidx.compose.runtime.setValue
20
+ import androidx.compose.ui.Modifier
21
+ import androidx.compose.ui.draw.clipToBounds
22
+ import androidx.compose.ui.draw.shadow
23
+ import androidx.compose.ui.graphics.Color
24
+ import androidx.compose.ui.graphics.Path
25
+ import androidx.compose.ui.graphics.PathEffect
26
+ import androidx.compose.ui.graphics.PathOperation
27
+ import androidx.compose.ui.graphics.drawscope.Stroke
28
+ import androidx.compose.ui.tooling.preview.Preview
29
+ import androidx.compose.ui.unit.dp
30
+ import org.zhiwei.compose.ui.widget.Title_Desc_Text
31
+ import org.zhiwei.compose.ui.widget.Title_Sub_Text
32
+ import org.zhiwei.compose.ui.widget.Title_Text
33
+ import kotlin.math.roundToInt
34
+
35
+ @Composable
36
+ internal fun CanvasPathOperations_Screen (modifier : Modifier = Modifier ) {
37
+ Column (
38
+ modifier
39
+ .fillMaxWidth()
40
+ .verticalScroll(rememberScrollState())
41
+ ) {
42
+ Title_Text (title = " Clip Path/Rect" )
43
+ Title_Sub_Text (title = " 示例演示path的操作符,DrawScope的clipPath/clipRect使用不同的交互模式而形成不同的UI显示效果" )
44
+ Title_Desc_Text (desc = " path.op Stroke" )
45
+ PathOpStroke ()
46
+ }
47
+
48
+ }
49
+
50
+ @Composable
51
+ private fun PathOpStroke () {
52
+
53
+ var sides1 by remember { mutableFloatStateOf(5f ) }
54
+ var radius1 by remember { mutableFloatStateOf(300f ) }
55
+
56
+ var sides2 by remember { mutableFloatStateOf(7f ) }
57
+ var radius2 by remember { mutableFloatStateOf(300f ) }
58
+
59
+ var operation by remember { mutableStateOf(PathOperation .Difference ) }
60
+
61
+ val newPath = remember { Path () }
62
+
63
+ Canvas (modifier = canvasModifier) {
64
+ val canvasWidth = size.width
65
+ val canvasHeight = size.height
66
+
67
+ val cx1 = canvasWidth / 3
68
+ val cx2 = canvasWidth * 2 / 3
69
+ val cy = canvasHeight / 2
70
+
71
+
72
+ val path1 = createPolygonPath(cx1, cy, sides1.roundToInt(), radius1)
73
+ val path2 = createPolygonPath(cx2, cy, sides2.roundToInt(), radius2)
74
+
75
+ drawPath(
76
+ color = Color .Red ,
77
+ path = path1,
78
+ style = Stroke (
79
+ width = 2 .dp.toPx(),
80
+ pathEffect = PathEffect .dashPathEffect(floatArrayOf(10f , 10f ))
81
+ )
82
+ )
83
+
84
+ drawPath(
85
+ color = Color .Blue ,
86
+ path = path2,
87
+ style = Stroke (
88
+ width = 2 .dp.toPx(),
89
+ pathEffect = PathEffect .dashPathEffect(floatArrayOf(10f , 10f ))
90
+ )
91
+ )
92
+
93
+ // We apply operation to path1 and path2 and setting this new path to our newPath
94
+ /*
95
+ Set this path to the result of applying the Op to the two specified paths.
96
+ The resulting path will be constructed from non-overlapping contours.
97
+ The curve order is reduced where possible so that cubics may be turned into quadratics,
98
+ and quadratics maybe turned into lines.
99
+ */
100
+ newPath.op(path1, path2, operation = operation)
101
+
102
+ drawPath(
103
+ color = Color .Green ,
104
+ path = newPath,
105
+ style = Stroke (
106
+ width = 4 .dp.toPx(),
107
+ )
108
+ )
109
+ }
110
+
111
+ Column (modifier = Modifier .padding(horizontal = 20 .dp, vertical = 4 .dp)) {
112
+
113
+ ExposedSelectionMenu (title = " Path Operation" ,
114
+ index = when (operation) {
115
+ PathOperation .Difference -> 0
116
+ PathOperation .Intersect -> 1
117
+ PathOperation .Union -> 2
118
+ PathOperation .Xor -> 3
119
+ else -> 4
120
+ },
121
+ options = listOf (" Difference" , " Intersect" , " Union" , " Xor" , " ReverseDifference" ),
122
+ onSelected = {
123
+ operation = when (it) {
124
+ 0 -> PathOperation .Difference
125
+ 1 -> PathOperation .Intersect
126
+ 2 -> PathOperation .Union
127
+ 3 -> PathOperation .Xor
128
+ else -> PathOperation .ReverseDifference
129
+ }
130
+ }
131
+ )
132
+
133
+ Text (text = " Sides left: ${sides1.roundToInt()} " )
134
+ Slider (
135
+ value = sides1,
136
+ onValueChange = { sides1 = it },
137
+ valueRange = 3f .. 12f ,
138
+ steps = 10
139
+ )
140
+ Text (text = " radius left: ${radius1.roundToInt()} " )
141
+ Slider (
142
+ value = radius1,
143
+ onValueChange = { radius1 = it },
144
+ valueRange = 100f .. 500f
145
+ )
146
+
147
+ Text (text = " Sides right: ${sides2.roundToInt()} " )
148
+ Slider (
149
+ value = sides2,
150
+ onValueChange = { sides2 = it },
151
+ valueRange = 3f .. 12f ,
152
+ steps = 10
153
+ )
154
+ Text (text = " radius right: ${radius2.roundToInt()} " )
155
+ Slider (
156
+ value = radius2,
157
+ onValueChange = { radius2 = it },
158
+ valueRange = 100f .. 500f
159
+ )
160
+ }
161
+ }
162
+
163
+
164
+ private val canvasModifier = Modifier
165
+ .padding(8 .dp)
166
+ .shadow(1 .dp)
167
+ .background(Color .White )
168
+ .fillMaxSize()
169
+ .clipToBounds()
170
+ .height(300 .dp)
171
+
172
+ @Preview
173
+ @Composable
174
+ private fun PreviewCanvasPathOps () {
175
+ CanvasPathOperations_Screen ()
176
+ }
0 commit comments