├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_CN.md ├── example ├── .gitignore ├── assets │ ├── icon_light_selected.png │ └── icon_light_unselected.png ├── lib │ └── main.dart └── pubspec.yaml ├── lib └── fsuper.dart ├── publish.sh ├── pubspec.yaml └── test └── fsuper_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .idea/ 10 | 11 | .metadata 12 | 13 | fsuper.iml 14 | 15 | pubspec.lock 16 | 17 | 18 | android/ 19 | ios/ 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.1.0 2 | - Support null-safety 3 | 4 | ## 2.0.2 5 | 6 | - fix `maxWidth` not work 7 | 8 | ## 2.0.1 9 | 10 | - optimization 11 | 12 | ## 2.0.0 13 | 14 | - Remove the following parameters and use the `style` parameter instead: 15 | -`textColor` 16 | -`textSize` 17 | -`textStyle` 18 | -`textWeight` 19 | -`fontHeight` 20 | 21 | - Remove the following parameters and use the `redPointTextStyle` parameter instead: 22 | -`redPointTextColor` 23 | -`redPointTextSize` 24 | 25 | -`corner` parameter type changed from **Corner** to **FCorner** 26 | 27 | -`cornerStyle` parameter type changed from **CornerStyle** to **FCornerStyle** 28 | 29 | - Optimize the layout of Child 30 | 31 | - Add Neumorphism style support. 32 | - Neumorphism style support can be turned on/off through the `isSupportNeumorphism` parameter 33 | - The `highlightShadowColor` parameter can configure the bright shadow color after enabling the Neumorphism style 34 | - Through the `float` parameter, you can configure the Neumorphism effect to float / reduce the visual effect 35 | - The `lightOrientation` parameter can adjust the direction of the light source 36 | 37 | - Now, under certain scenarios, even child child can respond to events normally even if it exceeds the range of **FSuper** 38 | 39 | ## 0.1.5 40 | 41 | - Optimize fsuper.dart 42 | 43 | ## 0.1.4 44 | 45 | - Rich **corner** effect 46 | 47 | - Exquisite **border** decoration 48 | 49 | - Naturally supports wonderful **rich text** 50 | 51 | - **Gradient effect** 52 | 53 | - More sense of space **Shadow** 54 | 55 | - Not simple **Red Point** 56 | 57 | - Flexible and powerful **relative layout** 58 | 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache-2.0 LICENSE 2 | 3 | Copyright 2020-present Fliggy Android Team . 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at following link. 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

FSuper

8 | 9 | 10 |
11 | 12 |

FSuper can help developers build complex views quickly and comfortably.

13 | 14 |

It supports rich text, rounded corners, borders, pictures, small red dots, and set up to two sub-components at the same time, and control their relative positions, high-quality Neumorphism style.

15 | 16 |

Author:Newton(coorchice.cb@alibaba-inc.com)

17 |

18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |

47 |

48 | 49 | 50 | 51 |

52 | 53 | **English | [简体中文](https://github.com/Fliggy-Mobile/fsuper/blob/master/README_CN.md)** 54 | 55 | > Like it? Please cast your **Star** 🥰! 56 | 57 | # ✨ Features 58 | 59 | - Rich **corner** effect 60 | 61 | - Exquisite **border** decoration 62 | 63 | - Naturally supports wonderful **rich text** 64 | 65 | - **Gradient effect** 66 | 67 | - More sense of space **Shadow** 68 | 69 | - Not simple **Red Point** 70 | 71 | - Flexible and powerful **relative layout** 72 | 73 | - High-quality **Neumorphism** style 74 | 75 | # 🛠 Guide 76 | 77 | ## ⚙️ Parameters 78 | 79 | ### 🔩 Basic parameters 80 | 81 | |Param|Type|Necessary|Default|desc| 82 | |---|---|:---:|---|---| 83 | |width|double|false|null|width| 84 | |height|double|false|null|height. can not be double.infinity| 85 | |maxWidth|double|false|null|maxWidth. If width> maxWidth, width is overridden. If there is no width, the maximum expanded width is maxWidth| 86 | |maxHeight|double|false|null|maxHeight. If height> maxHeight, height is overridden. If there is no height, the maximum extension width is maxHeight| 87 | |backgroundColor|Color|false|null|background color| 88 | |backgroundImage|ImageProvider|false|null|Background illustration. Overrides backgroundColor and gradient| 89 | |gradient|Gradient|false|null|Gradient. Will be overwriting backgroundColor| 90 | |padding|EdgeInsetsGeometry|false|null|Text and margins on each side. This is very useful to reserve space for children in FSuper| 91 | |margin|EdgeInsets|false|null|FSuper margins in parent container| 92 | |corner|FCorner|false|null|Corner size| 93 | |cornerStyle|FCornerStyle|false|FCornerStyle.round|Corner style. Rounded by default, set FCornerStyle.bevel to bevel| 94 | |text|String|false|null|Text content| 95 | |style|TextStyle|false|null|text style| 96 | |textAlign|TextAlign|false|TextAlign.center|Text alignment| 97 | |spans|List|false|null|Rich text. After receiving text, the text configuration will be inherited by default. Can be set individually via TextStyle| 98 | |onClick|GestureTapCallback|false|null|Set FSuper click listener| 99 | 100 | 101 | ### 🧸 Child Widget Parameters 102 | 103 | |Param|Type|Necessary|Default|desc| 104 | |---|---|:---:|---|---| 105 | |child1|Widget|false|null|child widget 1| 106 | |child1Alignment|Alignment|false|null|Relative position of child widget 1 in FSuper| 107 | |child1Margin|EdgeInsets|false|null|child widget 1 relative position based offset| 108 | |onChild1Click|GestureTapCallback|false|null|Click listener of child widget 1| 109 | |child2|Widget|false|null|child widget 2| 110 | |child2Alignment|Alignment|false|null|Relative position of child widget 2 in FSuper| 111 | |child2Margin|EdgeInsets|false|null|child widget 2 relative position based offset| 112 | |onChi2d1Click|GestureTapCallback|false|null|Click listener of child widget 2| 113 | 114 | ### 🎈 Red Point Parameters 115 | 116 | |Param|Type|Necessary|Default|desc| 117 | |---|---|:---:|---|---| 118 | |redPoint|bool|false|false|Whether to show the Red Point| 119 | |redPointColor|Color|false|Colors.redAccent|The Red Point color| 120 | |redPointSize|double|false|20|The Red Point size| 121 | |redPointText|String|false|null|text on Red Point| 122 | |redPointTextStyle|TextStyle|false|null|red point text style| 123 | |redPointOffset|Offset|false|null|The Red Point shifts to the upper right. The Red Point of Offset (0,0) is in the upper right corner of FSuper. By default, the Red Point is shifted to the upper right by 1/4| 124 | 125 | ### 🖼 Stroke Parameters 126 | 127 | |Param|Type|Necessary|Default|desc| 128 | |---|---|:---:|---|---| 129 | |strokeWidth|double|false|null|Border width. > 0 border will be displayed| 130 | |strokeColor|Color|false|null|stroke color| 131 | 132 | 133 | ### 🔳 Shadow Parameters 134 | 135 | |Param|Type|Necessary|Default|desc| 136 | |---|---|:---:|---|---| 137 | |shadowColor|Color|false|null|Shadow color| 138 | |shadowOffset|Offset|false|null|Shadow offset| 139 | |shadowBlur|double|false|null|The larger the value, the larger the shadow| 140 | 141 | ### 🍭 Neumorphism Style 142 | 143 | |Param|Type|Necessary|Default|desc| 144 | |---|---|:---:|---|---| 145 | |isSupportNeumorphism|bool|false|false|Whether to support the Neumorphism style. Open this item [highlightColor] will be invalid| 146 | |lightOrientation|FLightOrientation|false|FLightOrientation.LeftTop|Valid when [isSupportNeumorphism] is true. The direction of the light source is divided into four directions: upper left, lower left, upper right, and lower right. Used to control the illumination direction of the light source, which will affect the highlight direction and shadow direction| 147 | |highlightShadowColor|Color|false|null|After the Neumorphism style is turned on, the bright shadow color| 148 | |float|bool|false|false|Whether the Neumorphism style is turned on, whether it is a floating effect, otherwise it is a concave effect, the default is true| 149 | 150 | ## 📺 Demo 151 | 152 | ### 🔩 Basic Demo 153 | 154 | ![](https://gw.alicdn.com/tfs/TB1GwWEtRv0gK0jSZKbXXbK2FXa-856-580.png) 155 | 156 | ``` 157 | FSuper( 158 | margin: EdgeInsets.fromLTRB(12, 0, 12, 0), 159 | width: double.infinity, 160 | text: "This is FSuper!", 161 | backgroundColor: Color(0xffffc900), 162 | ), 163 | 164 | 165 | FSuper( 166 | text: "En.. ", 167 | spans: [ 168 | TextSpan( 169 | text: "FWidget", 170 | style: TextStyle( 171 | color: Color(0xffffc900), 172 | backgroundColor: Colors.black38, 173 | fontSize: 20, 174 | )), 175 | ... 176 | ], 177 | ), 178 | 179 | ``` 180 | 181 | **FSuper** The first part of the text is set through the `text` property, and related text style properties can be set. If you want to achieve rich text effects, you can pass a **TextSpan** array via the spans property. 182 | 183 | Of course, by default, the properties you have not set for **TextSpan** will automatically inherit the text style configuration of **FSuper**. 184 | 185 | By default, **FSuper** is able to adapt the text content size. 186 | 187 | But you can still specify a specific size through the `width`,` height` properties. If you want the parent container to be full of components, you can set their value to `double.infinity`. 188 | 189 | > ⚠️ You should never set the size of **FSuper** to double.infinity in an infinite parent container, because it really does not know how big it should be! 190 | 191 | In addition, **FSuper** also provides `maxWidth` and` maxHeight` to assist in layout, which is useful in the case of an uncertain component size. Your components will never exceed their limits. 192 | 193 | ## 🖼 Corner & Stroke Demo 194 | 195 | ![](https://gw.alicdn.com/tfs/TB1PCWDtKL2gK0jSZPhXXahvXXa-854-860.png) 196 | 197 | 198 | ``` 199 | FSuper( 200 | width: 130, 201 | padding: EdgeInsets.only(top: 16, bottom: 16) 202 | text: 'Corner FSuper', 203 | backgroundColor: Color(0xffFF7043), 204 | corner: FCorner.all(12), 205 | cornerStyle: FCornerStyle.bevel, 206 | ), 207 | 208 | FSuper( 209 | text: '音乐类型:流行音乐', 210 | padding: EdgeInsets.all(2), 211 | corner: FCorner.all(3), 212 | strokeColor: Color(0xffc2bfc2), 213 | strokeWidth: 1, 214 | ), 215 | ``` 216 | 217 | Using **FSuper** to declare a corner and border component is very simple. 218 | 219 | Corners can be declared just by the `corner` property. A **Corner** object will describe the corners of the component. You can control each corner individually. 220 | 221 | **FSuper** supports two types of corners: 222 | 223 | - **FCornerStyle.round**:Rounded corners. This is what we need most of the time. 224 | 225 | - **FCornerStyle.bevel**:bevel corners. 226 | 227 | If you want a border, you only need to make `StrokeWidth> 0` of **FSuper**. In addition, the `strokeColor` property allows you to describe the color of the border. 228 | 229 | ## 🔳 Gradient & Shadow Demo 230 | 231 | ![](https://gw.alicdn.com/tfs/TB1kz9EtKT2gK0jSZFvXXXnFXXa-852-580.png) 232 | 233 | ``` 234 | FSuper( 235 | width: 280, 236 | height: 45, 237 | text: 'Search Flight', 238 | textAlignment: Alignment.center, 239 | corner: FCorner.all(23), 240 | gradient: LinearGradient(colors: [ 241 | Color(0xfffed83a), 242 | Color(0xfffcad2c), 243 | ]), 244 | ), 245 | ``` 246 | 247 | The `gradient` property allows you to declare a gradient background for **FSuper** using a gradient object. 248 | 249 | The gradient background will override the solid background color set by backgroundColor. 250 | 251 | > ⚠️ Background priority:backgroundImage > gradient > backgroundColor. 252 | 253 | ``` 254 | FSuper( 255 | text: 'Overview', 256 | backgroundColor: Colors.white, 257 | padding: EdgeInsets.fromLTRB(6.0 + 18.0 + 6.0, 9, 9, 9), 258 | corner: Corner(rightTopCorner: 20, rightBottomCorner: 20), 259 | child1: Icon( 260 | Icons.subject, 261 | size: 18, 262 | color: Color(0xffa6a4a7), 263 | ), 264 | child1Alignment: Alignment.centerLeft, 265 | child1Margin: EdgeInsets.only(left: 3), 266 | shadowColor: Colors.black38, 267 | shadowBlur: 10, 268 | onClick: () { 269 | _showDialog(context, "Disco"); 270 | }, 271 | ), 272 | ``` 273 | 274 | If you are considering adding shadow effects to your components, using **FSuper** is a great choice. 275 | 276 | ## 🎈 Red Point Demo 277 | 278 | ![](https://gw.alicdn.com/tfs/TB1TpN6tF67gK0jSZPfXXahhFXa-858-662.png) 279 | 280 | ``` 281 | FSuper( 282 | width: 60, 283 | height: 60, 284 | backgroundColor: Color(0xffeeeeee), 285 | corner: FCorner.all(6), 286 | redPoint: true, 287 | readPointTextStyle: TextStyle(fontSize: 20.0), 288 | redPointText: "红包", 289 | ), 290 | ``` 291 | Using **FSuper** can be very simple to achieve the common Red Point effect. Just configure `redPoint: true`. 292 | 293 | In addition, you can add arbitrary text content to the Red Point (it is really convenient) and set its position. 294 | 295 | > ⚠️ The (0,0) position of the Red Point is in the upper right corner of **FSuper**. 296 | 297 | One-stop service to meet all your needs. 298 | 299 | 300 | ## 🧸 Child Widget 301 | 302 | ![](https://gw.alicdn.com/tfs/TB10XKGtKH2gK0jSZFEXXcqMpXa-856-848.png) 303 | 304 | 305 | ``` 306 | FSuper( 307 | width: double.infinity, 308 | padding: EdgeInsets.fromLTRB( 309 | (16.0 + 25.0 + 12), 8, (0.0 + 8.0), 8), 310 | margin: EdgeInsets.fromLTRB(10, 10, 10, 0), 311 | corner: FCorner.all(6), 312 | backgroundColor: Color(0xfffff0e7), 313 | strokeColor: Color(0xfffee0cd), 314 | strokeWidth: 1, 315 | text: '警告提示的文案', 316 | textAlignment: Alignment.centerLeft, 317 | textAlign: TextAlign.left, 318 | spans: [ 319 | ... 320 | ], 321 | child1: Transform.rotate( 322 | angle: pi, 323 | child: Icon( 324 | Icons.info, 325 | size: 25, 326 | color: Color(0xfffd6721), 327 | ), 328 | ), 329 | child1Alignment: Alignment.centerLeft, 330 | child1Margin: EdgeInsets.fromLTRB(16, 0, 0, 0), 331 | child2: Icon( 332 | Icons.close, 333 | size: 15, 334 | color: Colors.black38, 335 | ), 336 | child2Alignment: Alignment.topRight, 337 | child2Margin: EdgeInsets.fromLTRB(0, 8, 12, 0), 338 | onChild2Click: () { 339 | _showDialog(context, "关闭警告⚠️"); 340 | }, 341 | ), 342 | ``` 343 | 344 | In **FSuper**, two child components can be declared by `child1, child2`. You can specify their location and declare a click event. 345 | 346 | This will greatly increase development speed in some common and complex layouts. Especially in the scenario where the size of one component is small and uncertain, and the other component determines the position based on its size, **FSuper** handles everything. 347 | 348 | The effects of these components in the picture only need one **FSuper** component to complete. 349 | 350 | 351 | ## 🍭 Neumorphism 风格 352 | 353 | ![](https://gw.alicdn.com/tfs/TB1F3evNpT7gK0jSZFpXXaTkpXa-720-1037.gif) 354 | 355 | ```dart 356 | FSuper( 357 | 358 | /// 开启 Neumorphism 支持 359 | /// 360 | /// Turn on Neumorphism support 361 | isSupportNeumorphism: true, 362 | 363 | /// 配置光源方向 364 | /// 365 | /// Configure light source direction 366 | lightOrientation: lightOrientation, 367 | 368 | /// 配置暗部阴影 369 | /// 370 | /// Configure dark shadows 371 | shadowColor: Colors.black87, 372 | 373 | /// 配置亮部阴影 374 | /// 375 | /// Configure highlight shadow 376 | highlightShadowColor: Colors.white24, 377 | 378 | /// 是否呈浮起视效 379 | /// 380 | /// Whether it is floating visual effect 381 | float: false, 382 | shadowOffset: Offset(0.0, 1.0), 383 | width: 50, 384 | height: 50, 385 | backgroundColor: Color(0xff28292f), 386 | corner: FCorner.all(40), 387 | child1: Icon( 388 | Icons.star, 389 | color: Color(0xfffff176), 390 | size: 23, 391 | ), 392 | ), 393 | ``` 394 | 395 | **FButton** brings an incredible, ultra-high texture **Neumorphism** style to developers. 396 | 397 | Developers only need to configure the `isSupportNeumorphism` parameter to enable and disable the **Neumorphism** style. 398 | 399 | If you want to adjust the style of **Neumorphism**, you can make subtle adjustments through several attributes related to Shadow, among which: 400 | 401 | - shadowColor: configure the shadow of the shadow 402 | 403 | - highlightShadowColor: configure highlight shadow 404 | 405 | **FButton** also provides `lightOrientation` parameters, and even allows developers to adjust the care angle, and has obtained different **Neumorphism** effects. 406 | 407 | Through the `float` parameter, developers can easily switch between **floating visual effect** and **recessed visual effect**. 408 | 409 | ## 🎞 More Demo 410 | 411 | ![](https://gw.alicdn.com/tfs/TB1__eItHj1gK0jSZFOXXc7GpXa-854-1542.png) 412 | 413 | Do not be surprised, the effects in the figure are all achieved with **FSuper**. 414 | 415 | The design of the sub-components makes **FSuper** a qualitative leap in flexibility, and most of the complex views are capable. 416 | 417 | For example, the chat bubble in the picture does not need to use the background picture, just use **FSuper** to achieve it. This makes such components extremely flexible and easy to modify. 418 | 419 | ``` 420 | FSuper( 421 | maxWidth: 220, 422 | textAlign: TextAlign.left, 423 | text: "I'm created by FSuper 😄", 424 | padding: EdgeInsets.only( 425 | left: 12, right: 12, top: 15, bottom: 15), 426 | backgroundColor: Color(0xffa5ed7e), 427 | corner: FCorner.all(6), 428 | child1: Transform.rotate( 429 | angle: pi / 4, 430 | child: FSuper( 431 | width: 10, 432 | height: 10, 433 | backgroundColor: Color(0xffa5ed7e), 434 | corner: FCorner.all(1.5), 435 | ), 436 | ), 437 | child1Alignment: Alignment.topRight, 438 | child1Margin: EdgeInsets.only(right: -4, top: 20), 439 | shadowColor: Color(0xffa5ed7e), 440 | shadowBlur: 5, 441 | ), 442 | ``` 443 | 444 | # 😃 How to use? 445 | 446 | Add dependency in project `pubspec.yaml` file: 447 | 448 | ## 🌐 pub dependency 449 | 450 | ``` 451 | dependencies: 452 | fsuper: ^ 453 | ``` 454 | 455 | > ⚠️ Attention, please go to [**pub**](https://pub.dev/packages/fsuper) to get the latest version number of **FSuper** 456 | 457 | ## 🖥 git dependencies 458 | 459 | ``` 460 | dependencies: 461 | fsuper: 462 | git: 463 | url: 'git@github.com:Fliggy-Mobile/fsuper.git' 464 | ref: '' 465 | ``` 466 | 467 | > ⚠️ Attention,Please refer to the official project of [**FSuper**](https://github.com/Fliggy-Mobile/fsuper) for the branch number or tag. 468 | 469 | 470 | # 💡 License 471 | 472 | ``` 473 | Copyright 2020-present Fliggy Android Team . 474 | 475 | Licensed under the Apache License, Version 2.0 (the "License"); 476 | you may not use this file except in compliance with the License. 477 | You may obtain a copy of the License at following link. 478 | 479 | http://www.apache.org/licenses/LICENSE-2.0 480 | 481 | Unless required by applicable law or agreed to in writing, software 482 | distributed under the License is distributed on an "AS IS" BASIS, 483 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 484 | See the License for the specific language governing permissions and 485 | limitations under the License. 486 | 487 | ``` 488 | 489 | ### Like it? Please cast your [**Star**](https://github.com/Fliggy-Mobile/fsuper) 🥰! 490 | 491 | 492 | --- 493 | 494 | # How to run Demo project? 495 | 496 | 1. **clone** project to local 497 | 498 | 2. Enter the project `example` directory and run the following command 499 | 500 | ``` 501 | flutter create . 502 | ``` 503 | 504 | 3. Run the demo in `example` 505 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

FSuper

8 | 9 | 10 |
11 | 12 |

FSuper 能够帮助开发者快速舒适的构建复杂视图。

13 | 14 |

支持富文本、圆角、边框、图片、小红点、以及同时设置多达两个子组件,且控制它们的相对位置,高质感的 Neumorphism 风格。

15 | 16 |

主理人:纽特(coorchice.cb@alibaba-inc.com)

17 | 18 |

19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 |

47 |

48 | 49 | 50 | 51 |

52 | 53 | 54 | **[English](https://github.com/Fliggy-Mobile/fsuper) | 简体中文** 55 | 56 | > 感觉还不错?请投出您的 **Star** 吧 🥰 ! 57 | 58 | # ✨ 特性 59 | 60 | - 丰富的 **边角** 效果 61 | 62 | - 精美的 **边框** 装饰 63 | 64 | - 天然支持精彩的 **富文本** 65 | 66 | - **渐变效果** 也不在话下 67 | 68 | - 更具空间感的 **阴影** 69 | 70 | - 不简单的 **小红点** 71 | 72 | - 灵活且强大的 **相对位置布局** 73 | 74 | - 高质感的 **Neumorphism** 风格 75 | 76 | # 🛠 使用指南 77 | 78 | ## ⚙️ 参数 79 | 80 | ### 🔩 基础参数 81 | 82 | |参数|类型|必要|默认值|说明| 83 | |---|---|:---:|---|---| 84 | |width|double|否|null|宽度| 85 | |height|double|否|null|高度。不能设置为 double.infinity| 86 | |maxWidth|double|否|null|最大宽度。如果 width > maxWidth,会覆盖 width。如果没有 width,最大扩展宽度为 maxWidth| 87 | |maxHeight|double|否|null|最大高度。如果 height > maxHeight,会覆盖 height。如果没有 height,最大扩展宽度为 maxHeight| 88 | |backgroundColor|Color|否|null|背景颜色| 89 | |backgroundImage|ImageProvider|否|null|背景图。会覆盖 backgroundColor 和 gradient| 90 | |gradient|Gradient|否|null|渐变色。会覆盖 backgroundColor| 91 | |padding|EdgeInsetsGeometry|否|null|文本和各边的边距。这十分有用,通过它来给 FSuper 中的 child 预留展示空间| 92 | |margin|EdgeInsets|否|null|FSuper 在父容器中的边距| 93 | |corner|FCorner|否|null|边角大小| 94 | |cornerStyle|FFCornerStyle|否|FFCornerStyle.round|边角样式。默认为圆角,设置 FCornerStyle.bevel 为斜角| 95 | |text|String|否|null|文本| 96 | |style|TextStyle|false|null|文本样式| 97 | |textAlign|TextAlign|否|TextAlign.center|文本的对齐方式| 98 | |spans|List|否|null|富文本。可以接到 text 文本之后,默认会继承 text 的配置。可以通过 TextStyle 单独设置| 99 | |onClick|GestureTapCallback|否|null|设置 FSuper 的点击事件| 100 | 101 | 102 | ### 🧸 子组件参数 103 | 104 | |参数|类型|必要|默认值|说明| 105 | |---|---|:---:|---|---| 106 | |child1|Widget|否|null|子组件1| 107 | |child1Alignment|Alignment|否|null|子组件1在 FSuper 中的相对位置| 108 | |child1Margin|EdgeInsets|否|null|子组件1基于相对位置的偏移| 109 | |onChild1Click|GestureTapCallback|否|null|子组件1的点击事件| 110 | |child2|Widget|否|null|子组件2| 111 | |child2Alignment|Alignment|否|null|子组件2在 FSuper 中的相对位置| 112 | |child2Margin|EdgeInsets|否|null|子组件2基于相对位置的偏移| 113 | |onChi2d1Click|GestureTapCallback|否|null|子组件2的点击事件| 114 | 115 | ### 🎈 小红点参数 116 | 117 | |参数|类型|必要|默认值|说明| 118 | |---|---|:---:|---|---| 119 | |redPoint|bool|否|false|是否展示小红点| 120 | |redPointColor|Color|否|Colors.redAccent|小红点颜色| 121 | |redPointSize|double|否|20|小红点大小| 122 | |redPointText|String|否|null|小红点上的文本| 123 | |redPointTextStyle|TextStyle|false|null|小红点文本样式| 124 | |redPointOffset|Offset|否|null|小红点向右上方的位置偏移量。Offset(0,0)小红点在 FSuper 的右上角。默认会向右上方偏移小红点size的1/4| 125 | 126 | ### 🖼 边框参数 127 | 128 | |参数|类型|必要|默认值|说明| 129 | |---|---|:---:|---|---| 130 | |strokeWidth|double|否|null|边框宽度。>0 时边框就会显示| 131 | |strokeColor|Color|否|null|边框颜色| 132 | 133 | 134 | ### 🔳 阴影参数 135 | 136 | |参数|类型|必要|默认值|说明| 137 | |---|---|:---:|---|---| 138 | |shadowColor|Color|否|null|阴影颜色| 139 | |shadowOffset|Offset|否|null|阴影偏移量| 140 | |shadowBlur|double|否|null|值越大,阴影越大| 141 | 142 | ### 🍭 Neumorphism 风格 143 | 144 | |参数|类型|必要|默认值|说明| 145 | |---|---|:---:|---|---| 146 | |isSupportNeumorphism|bool|false|false|是否支持 Neumorphism 风格。开启该项 [highlightColor] 将会失效| 147 | |lightOrientation|FLightOrientation|false|FLightOrientation.LeftTop|当 [isSupportNeumorphism] 为 true 时有效。光源方向,分为左上、左下、右上、右下四个方向。用来控制光源照射方向,会影响高亮方向和阴影方向| 148 | |highlightShadowColor|Color|false|null|开启 Neumorphism 风格后的,亮部阴影颜色| 149 | |float|bool|false|false|开启 Neumorphism 风格后,是否呈浮起效果,否则为凹陷效果,默认为 true| 150 | 151 | ## 📺 使用示例 152 | 153 | ### 🔩 基本使用 154 | 155 | ![](https://gw.alicdn.com/tfs/TB1GwWEtRv0gK0jSZKbXXbK2FXa-856-580.png) 156 | 157 | ``` 158 | FSuper( 159 | margin: EdgeInsets.fromLTRB(12, 0, 12, 0), 160 | width: double.infinity, 161 | text: "This is FSuper!", 162 | backgroundColor: Color(0xffffc900), 163 | ), 164 | 165 | 166 | FSuper( 167 | text: "En.. ", 168 | spans: [ 169 | TextSpan( 170 | text: "FWidget", 171 | style: TextStyle( 172 | color: Color(0xffffc900), 173 | backgroundColor: Colors.black38, 174 | fontSize: 20, 175 | )), 176 | ... 177 | ], 178 | ), 179 | 180 | ``` 181 | 182 | **FSuper** 通过 `text` 属性设置文字的第一部分,且有相关的文本样式属性可以设置。如果你希望实现富文本效果,可以通过 `spans` 属性传入一个 **TextSpan** 数组。 183 | 184 | 当然,默认情况下,你没有给 **TextSpan** 设置的属性,将会自动继承 **FSuper** 的文本样式配置。 185 | 186 | 默认情况下,**FSuper** 能够自适应文本内容大小。 187 | 188 | 但你仍然可以通过 `width`、`height` 属性来指定一个具体的大小。如果你想要让组件充满的父容器的,可以将它们的值设置为 `double.infinity`。 189 | 190 | > ⚠️ 你始终都不要在一个无限大的父容器中,给 **FSuper** 的尺寸值设置为 `double.infinity`,因为它真的不知道自己该有多大! 191 | 192 | 此外,**FSuper** 还提供了 `maxWidth` 和 `maxHeight` 来辅助布局,这在一个不确定组件大小的情况下十分有用。你的组件将始终不能超过它们的限制。 193 | 194 | ## 🖼 圆角和边框 195 | 196 | ![](https://gw.alicdn.com/tfs/TB1PCWDtKL2gK0jSZPhXXahvXXa-854-860.png) 197 | 198 | 199 | ``` 200 | FSuper( 201 | width: 130, 202 | padding: EdgeInsets.only(top: 16, bottom: 16) 203 | text: 'Corner FSuper', 204 | backgroundColor: Color(0xffFF7043), 205 | corner: FCorner.all(12), 206 | cornerStyle: FCornerStyle.bevel, 207 | ), 208 | 209 | FSuper( 210 | text: '音乐类型:流行音乐', 211 | padding: EdgeInsets.all(2), 212 | corner: FCorner.all(3), 213 | strokeColor: Color(0xffc2bfc2), 214 | strokeWidth: 1, 215 | ), 216 | ``` 217 | 218 | 使用 **FSuper** 声明一个边角、边框组件是十分简单的。 219 | 220 | 仅仅通过 `corner` 属性就能声明边角,一个 **Corner** 对象将会描述组件的边角情况,你可以单独控制每一个边角。 221 | 222 | **FSuper** 支持两种类型的边角: 223 | 224 | - **FCornerStyle.round**:圆角。这是大多数时候我们需要的。 225 | 226 | - **FCornerStyle.bevel**:斜角。 227 | 228 | 如果你想要边框,只需要让 **FSuper** 的 `strokeWidth > 0` 就可以了。此外,`strokeColor` 属性让你能够描述边框的颜色。 229 | 230 | ## 🔳 渐变和阴影 231 | 232 | ![](https://gw.alicdn.com/tfs/TB1kz9EtKT2gK0jSZFvXXXnFXXa-852-580.png) 233 | 234 | ``` 235 | FSuper( 236 | width: 280, 237 | height: 45, 238 | text: 'Search Flight', 239 | textAlignment: Alignment.center, 240 | corner: FCorner.all(23), 241 | gradient: LinearGradient(colors: [ 242 | Color(0xfffed83a), 243 | Color(0xfffcad2c), 244 | ]), 245 | ), 246 | ``` 247 | 248 | `gradient` 属性允许你使用一个渐变对象来为 **FSuper** 声明一个渐变色背景。 249 | 250 | 渐变色背景会覆盖 `backgroundColor` 设置的纯色背景色。 251 | 252 | > ⚠️ 背景优先级:backgroundImage > gradient > backgroundColor. 253 | 254 | ``` 255 | FSuper( 256 | text: 'Overview', 257 | backgroundColor: Colors.white, 258 | padding: EdgeInsets.fromLTRB(6.0 + 18.0 + 6.0, 9, 9, 9), 259 | corner: Corner(rightTopCorner: 20, rightBottomCorner: 20), 260 | child1: Icon( 261 | Icons.subject, 262 | size: 18, 263 | color: Color(0xffa6a4a7), 264 | ), 265 | child1Alignment: Alignment.centerLeft, 266 | child1Margin: EdgeInsets.only(left: 3), 267 | shadowColor: Colors.black38, 268 | shadowBlur: 10, 269 | onClick: () { 270 | _showDialog(context, "Disco"); 271 | }, 272 | ), 273 | ``` 274 | 275 | 如果你在考虑为组件添加阴影效果,那使用 **FSuper** 就是一个绝佳的选择。 276 | 277 | ## 🎈 小红点 278 | 279 | ![](https://gw.alicdn.com/tfs/TB1TpN6tF67gK0jSZPfXXahhFXa-858-662.png) 280 | 281 | ``` 282 | FSuper( 283 | width: 60, 284 | height: 60, 285 | backgroundColor: Color(0xffeeeeee), 286 | corner: FCorner.all(6), 287 | redPoint: true, 288 | readPointTextStyle: TextStyle(fontSize: 20.0), 289 | redPointText: "红包", 290 | ), 291 | ``` 292 | 293 | 使用 **FSuper** 能够十分简单的实现很常见的小红点效果。只需配置 `redPoint: true`。 294 | 295 | 此外,你可以为小红点添加任意的文本内容(真是方便),以及设置它的位置。 296 | 297 | > ⚠️ 小红点的 (0,0)位置,在 **FSuper** 的右上角。 298 | 299 | 一条龙服务,满足你的所有需求。 300 | 301 | 302 | ## 🧸 子组件 303 | 304 | ![](https://gw.alicdn.com/tfs/TB10XKGtKH2gK0jSZFEXXcqMpXa-856-848.png) 305 | 306 | 307 | ``` 308 | FSuper( 309 | width: double.infinity, 310 | padding: EdgeInsets.fromLTRB( 311 | (16.0 + 25.0 + 12), 8, (0.0 + 8.0), 8), 312 | margin: EdgeInsets.fromLTRB(10, 10, 10, 0), 313 | corner: FCorner.all(6), 314 | backgroundColor: Color(0xfffff0e7), 315 | strokeColor: Color(0xfffee0cd), 316 | strokeWidth: 1, 317 | text: '警告提示的文案', 318 | textAlignment: Alignment.centerLeft, 319 | textAlign: TextAlign.left, 320 | spans: [ 321 | ... 322 | ], 323 | child1: Transform.rotate( 324 | angle: pi, 325 | child: Icon( 326 | Icons.info, 327 | size: 25, 328 | color: Color(0xfffd6721), 329 | ), 330 | ), 331 | child1Alignment: Alignment.centerLeft, 332 | child1Margin: EdgeInsets.fromLTRB(16, 0, 0, 0), 333 | child2: Icon( 334 | Icons.close, 335 | size: 15, 336 | color: Colors.black38, 337 | ), 338 | child2Alignment: Alignment.topRight, 339 | child2Margin: EdgeInsets.fromLTRB(0, 8, 12, 0), 340 | onChild2Click: () { 341 | _showDialog(context, "关闭警告⚠️"); 342 | }, 343 | ), 344 | ``` 345 | 346 | 在 **FSuper** 中,可以通过 `child1、child2` 来声明两个子组件。你可以指定它们的位置和声明点击事件。 347 | 348 | 这在一些常见的复杂布局中,将会大大提升开发速度。尤其是在 **一个组件大小小不确定,而另一个组件要基于它的大小确定位置** 的场景中,**FSuper** 处理好了一切。 349 | 350 | 图中这些组件效果,均只需要一个 **FSuper** 组件就能够完成。 351 | 352 | ## 🍭 Neumorphism 风格 353 | 354 | ![](https://gw.alicdn.com/tfs/TB1F3evNpT7gK0jSZFpXXaTkpXa-720-1037.gif) 355 | 356 | ```dart 357 | FSuper( 358 | 359 | /// 开启 Neumorphism 支持 360 | /// 361 | /// Turn on Neumorphism support 362 | isSupportNeumorphism: true, 363 | 364 | /// 配置光源方向 365 | /// 366 | /// Configure light source direction 367 | lightOrientation: lightOrientation, 368 | 369 | /// 配置暗部阴影 370 | /// 371 | /// Configure dark shadows 372 | shadowColor: Colors.black87, 373 | 374 | /// 配置亮部阴影 375 | /// 376 | /// Configure highlight shadow 377 | highlightShadowColor: Colors.white24, 378 | 379 | /// 是否呈浮起视效 380 | /// 381 | /// Whether it is floating visual effect 382 | float: false, 383 | shadowOffset: Offset(0.0, 1.0), 384 | width: 50, 385 | height: 50, 386 | backgroundColor: Color(0xff28292f), 387 | corner: FCorner.all(40), 388 | child1: Icon( 389 | Icons.star, 390 | color: Color(0xfffff176), 391 | size: 23, 392 | ), 393 | ), 394 | ``` 395 | 396 | **FSuper** 为开发者带来了不可思议的,超高质感的 **Neumorphism** 风格。 397 | 398 | 开发者只需要简单的通过配置 `isSupportNeumorphism` 参数,就可以开启和关闭 **Neumorphism** 风格。 399 | 400 | 如果想要调整 **Neumorphism** 的样式,可以通过 Shadow 相关的几个属性进行细微的调整,其中: 401 | 402 | - shadowColor: 配置暗部阴影 403 | 404 | - highlightShadowColor:配置亮部阴影 405 | 406 | **FSuper** 还提供了 `lightOrientation` 参数,甚至使得开发者能够调整关照角度,已获得不同的 **Neumorphism** 效果。 407 | 408 | 通过 `float` 参数,开发者轻松的在 **浮起视效** 和 **凹陷视效** 间任意切换。 409 | 410 | 411 | ## 🎞 更多示例 412 | 413 | ![](https://gw.alicdn.com/tfs/TB1__eItHj1gK0jSZFOXXc7GpXa-854-1542.png) 414 | 415 | 不要惊讶,图中效果都使用 **FSuper** 来实现。 416 | 417 | 子组件的设计使得 **FSuper** 的灵活性有了质的飞跃,绝大部分复杂视图,均可胜任。 418 | 419 | 比如图中的聊天框气泡,不需要使用背景图,直接使用 **FSuper** 即可实现。这使得这样的组件获得了极大的灵活性,易于修改。 420 | 421 | ``` 422 | FSuper( 423 | maxWidth: 220, 424 | textAlign: TextAlign.left, 425 | text: "I'm created by FSuper 😄", 426 | padding: EdgeInsets.only( 427 | left: 12, right: 12, top: 15, bottom: 15), 428 | backgroundColor: Color(0xffa5ed7e), 429 | corner: FCorner.all(6), 430 | child1: Transform.rotate( 431 | angle: pi / 4, 432 | child: FSuper( 433 | width: 10, 434 | height: 10, 435 | backgroundColor: Color(0xffa5ed7e), 436 | corner: FCorner.all(1.5), 437 | ), 438 | ), 439 | child1Alignment: Alignment.topRight, 440 | child1Margin: EdgeInsets.only(right: -4, top: 20), 441 | shadowColor: Color(0xffa5ed7e), 442 | shadowBlur: 5, 443 | ), 444 | ``` 445 | 446 | 447 | # 😃 如何使用? 448 | 449 | 在项目 `pubspec.yaml` 文件中添加依赖: 450 | 451 | ## 🌐 pub 依赖方式 452 | 453 | ``` 454 | dependencies: 455 | fsuper: ^<版本号> 456 | ``` 457 | 458 | > ⚠️ 注意,请到 [**pub**](https://pub.dev/packages/fsuper) 获取 **FSuper** 最新版本号 459 | 460 | ## 🖥 git 依赖方式 461 | 462 | ``` 463 | dependencies: 464 | fsuper: 465 | git: 466 | url: 'git@github.com:Fliggy-Mobile/fsuper.git' 467 | ref: '<分支号 或 tag>' 468 | ``` 469 | 470 | > ⚠️ 注意,分支号 或 tag 请以 [**FSuper**](https://github.com/Fliggy-Mobile/fsuper) 官方项目为准。 471 | 472 | # 💡 License 473 | 474 | ``` 475 | Copyright 2020-present Fliggy Android Team . 476 | 477 | Licensed under the Apache License, Version 2.0 (the "License"); 478 | you may not use this file except in compliance with the License. 479 | You may obtain a copy of the License at following link. 480 | 481 | http://www.apache.org/licenses/LICENSE-2.0 482 | 483 | Unless required by applicable law or agreed to in writing, software 484 | distributed under the License is distributed on an "AS IS" BASIS, 485 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 486 | See the License for the specific language governing permissions and 487 | limitations under the License. 488 | 489 | ``` 490 | 491 | ### 感觉还不错?请投出您的 [**Star**](https://github.com/Fliggy-Mobile/fsuper) 吧 🥰 ! 492 | 493 | 494 | --- 495 | 496 | # 如何运行 Demo 工程? 497 | 498 | 1.**clone** 工程到本地 499 | 500 | 2.进入工程 `example` 目录,运行以下命令 501 | 502 | ``` 503 | flutter create . 504 | ``` 505 | 506 | 3.运行 `example` 中的 Demo 507 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | .metadata 33 | # Web related 34 | lib/generated_plugin_registrant.dart 35 | 36 | # Exceptions to above rules. 37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 38 | 39 | android/ 40 | ios/ 41 | test/ 42 | web/ 43 | README.md 44 | macos/ 45 | 46 | pubspec.lock 47 | 48 | -------------------------------------------------------------------------------- /example/assets/icon_light_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fliggy-Mobile/fsuper/747fe60d1b00e9e491af7424436fae5731557e4f/example/assets/icon_light_selected.png -------------------------------------------------------------------------------- /example/assets/icon_light_unselected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fliggy-Mobile/fsuper/747fe60d1b00e9e491af7424436fae5731557e4f/example/assets/icon_light_unselected.png -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:fcommon/color.dart'; 4 | import 'package:fcommon/part.dart'; 5 | import 'package:fcontrol/fdefine.dart'; 6 | import 'package:flutter/cupertino.dart'; 7 | import 'package:flutter/gestures.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter/widgets.dart'; 10 | import 'package:fradio/fradio.dart'; 11 | import 'package:fsuper/fsuper.dart'; 12 | 13 | void main() => runApp(MyApp()); 14 | 15 | class MyApp extends StatefulWidget { 16 | @override 17 | _MyAppState createState() => _MyAppState(); 18 | } 19 | 20 | class _MyAppState extends State { 21 | @override 22 | void initState() { 23 | super.initState(); 24 | } 25 | 26 | var text = 27 | "FWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidgetFWidget"; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return MaterialApp( 32 | home: FSuperPage(), 33 | ); 34 | } 35 | } 36 | 37 | class FSuperPage extends StatefulWidget { 38 | @override 39 | State createState() { 40 | return _FSuperPageState(); 41 | } 42 | } 43 | 44 | class _FSuperPageState extends State { 45 | @override 46 | Widget build(BuildContext context) { 47 | return Scaffold( 48 | backgroundColor: mainBackgroundColor, 49 | appBar: AppBar( 50 | backgroundColor: mainBackgroundColor, 51 | title: const Text( 52 | 'FSuper', 53 | style: TextStyle(color: mainTextTitleColor), 54 | ), 55 | centerTitle: true, 56 | ), 57 | body: InteractiveViewer( 58 | maxScale: 8.0, 59 | minScale: 1.0, 60 | child: Builder( 61 | builder: (context) { 62 | return SingleChildScrollView( 63 | physics: BouncingScrollPhysics(), 64 | child: Column( 65 | children: [ 66 | buildTitle("Size & Rich Text"), 67 | buildSmallMargin(), 68 | FSuper( 69 | text: "This is FSuper!", 70 | backgroundColor: Color(0xffffc900), 71 | ), 72 | buildDesc("在不设置 width、height 属性的情况下,FSuper 大小为 text 的大小\n" 73 | "在这种情况下,textAlignment 将会失效,text 始终看起来是居中的"), 74 | FSuper( 75 | margin: EdgeInsets.fromLTRB(12, 0, 12, 0), 76 | width: double.infinity, 77 | maxHeight: 300, 78 | text: "This is FSuper!", 79 | backgroundColor: Color(0xffffc900), 80 | ), 81 | buildDesc('设置 "width: double.infinity",可让 FSuper 宽充满可用空间\n' 82 | '但不要将 height 设置为 double.infinity,你应该给他一个具体值,\n' 83 | '或者干脆就空着'), 84 | FSuper( 85 | text: "En.. ", 86 | spans: [ 87 | TextSpan( 88 | text: "FWidget", 89 | style: TextStyle( 90 | color: Color(0xffffc900), 91 | backgroundColor: Colors.black38, 92 | fontSize: 20, 93 | )), 94 | TextSpan(text: " are really "), 95 | TextSpan( 96 | text: "delicious", 97 | style: TextStyle( 98 | color: Colors.blue, 99 | fontSize: 20, 100 | fontStyle: FontStyle.italic, 101 | )), 102 | TextSpan(text: "!"), 103 | TextSpan( 104 | text: "\nYou can try to ", 105 | ), 106 | TextSpan( 107 | text: "click here", 108 | style: TextStyle( 109 | color: Colors.redAccent, 110 | fontSize: 18, 111 | decoration: TextDecoration.underline, 112 | decorationColor: Colors.blue, 113 | decorationStyle: TextDecorationStyle.wavy, 114 | ), 115 | recognizer: TapGestureRecognizer() 116 | ..onTap = () { 117 | toast(context, "YA! How dare you clicked me?"); 118 | }, 119 | ), 120 | TextSpan(text: " !"), 121 | ], 122 | ), 123 | buildDesc("通过 spans 属性,可以传入一个 List 来实现富文本"), 124 | buildTitle("Corner"), 125 | buildSmallMargin(), 126 | Container( 127 | width: 200, 128 | child: Row( 129 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 130 | children: [ 131 | FSuper( 132 | padding: EdgeInsets.fromLTRB(9, 6, 9, 6), 133 | text: '¥', 134 | style: TextStyle(color: Colors.white, fontSize: 11), 135 | spans: [ 136 | TextSpan( 137 | text: '370起 ', 138 | style: TextStyle( 139 | fontSize: 16, fontWeight: FontWeight.w800), 140 | ), 141 | TextSpan( 142 | text: '2.4折', 143 | style: TextStyle(fontSize: 11), 144 | ), 145 | ], 146 | backgroundColor: Colors.redAccent, 147 | corner: FCorner.all(20), 148 | ), 149 | FSuper( 150 | text: 'GO', 151 | style: TextStyle(color: Color(0xffB44431)), 152 | padding: 153 | EdgeInsets.fromLTRB(9, 6, 3.0 + 12.0 + 6.0, 6), 154 | backgroundColor: Color(0xfffeebc5), 155 | corner: FCorner.all(15), 156 | child1: Icon( 157 | Icons.arrow_forward_ios, 158 | size: 12, 159 | color: Color(0xffB44431), 160 | ), 161 | child1Alignment: Alignment.centerRight, 162 | child1Margin: EdgeInsets.only(right: 6), 163 | onClick: () { 164 | toast(context, "HA.. Go Go Go!🏃️🏃️🏃️"); 165 | }, 166 | ), 167 | ], 168 | ), 169 | ), 170 | buildDesc("corner 属性能够赋予 FSuper 边角\n" 171 | "默认情况下,FSuper 的边角是圆角"), 172 | FSuper( 173 | width: 130, 174 | padding: EdgeInsets.only(top: 16, bottom: 16), 175 | text: 'Corner FSuper', 176 | backgroundColor: Color(0xffFF7043), 177 | corner: FCorner.all(12), 178 | cornerStyle: FCornerStyle.bevel, 179 | ), 180 | buildDesc('设置 cornerStyle: FCornerStyle.bevel 将变成斜切角'), 181 | Container( 182 | width: 300, 183 | child: Row( 184 | mainAxisAlignment: MainAxisAlignment.spaceAround, 185 | children: [ 186 | FSuper( 187 | width: 130, 188 | text: 'Corner FSuper', 189 | backgroundColor: Color(0xff00B0FF), 190 | padding: EdgeInsets.only(top: 16, bottom: 16), 191 | corner: FCorner( 192 | leftTopCorner: 25, 193 | leftBottomCorner: 25, 194 | rightBottomCorner: 25), 195 | ), 196 | FSuper( 197 | width: 130, 198 | text: 'Corner FSuper', 199 | backgroundColor: Color(0xffFFA726), 200 | padding: EdgeInsets.only(top: 16, bottom: 16), 201 | corner: FCorner(rightTopCorner: 10), 202 | cornerStyle: FCornerStyle.bevel, 203 | ), 204 | ], 205 | ), 206 | ), 207 | buildDesc("通过设置单个 Corner 值,可以控制每个圆角"), 208 | buildTitle("Stroke"), 209 | buildSmallMargin(), 210 | Container( 211 | width: 300, 212 | child: Row( 213 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 214 | children: [ 215 | FSuper( 216 | text: '私享假日时光', 217 | style: TextStyle(color: Color(0xffe56837)), 218 | padding: EdgeInsets.fromLTRB(9, 2, 12.0 + 6.0, 2), 219 | corner: FCorner.all(16), 220 | strokeColor: Color(0xffd76d44), 221 | strokeWidth: 1, 222 | child1: Icon( 223 | Icons.arrow_forward_ios, 224 | size: 12, 225 | color: Color(0xffe56837), 226 | ), 227 | child1Alignment: Alignment.centerRight, 228 | child1Margin: EdgeInsets.only(right: 6), 229 | ), 230 | FSuper( 231 | height: 12, 232 | text: '情侣出游', 233 | style: TextStyle( 234 | color: Color(0xffc2bfc2), 235 | height: 1.05, 236 | fontSize: 12), 237 | textAlignment: Alignment.center, 238 | corner: FCorner.all(3), 239 | strokeColor: Color(0xffc2bfc2), 240 | strokeWidth: 0.5, 241 | ), 242 | FSuper( 243 | height: 20, 244 | text: '明日可定', 245 | style: TextStyle( 246 | color: Color(0xffc2bfc2), 247 | fontSize: 20, 248 | height: 1.05), 249 | corner: FCorner.all(3), 250 | strokeColor: Color(0xffc2bfc2), 251 | strokeWidth: 1, 252 | ), 253 | FSuper( 254 | text: '4.4', 255 | height: 12, 256 | style: TextStyle( 257 | color: Colors.white, 258 | fontSize: 12, 259 | height: 1.05, 260 | fontWeight: FontWeight.w600, 261 | ), 262 | // strutStyle: StrutStyle( 263 | // fontWeight: FontWeight.w600, 264 | // fontSize: 12, 265 | // height: 1.0, 266 | // forceStrutHeight: true, 267 | // ), 268 | padding: EdgeInsets.only(left: 2, right: 2), 269 | corner: FCorner( 270 | leftTopCorner: 6.5, 271 | leftBottomCorner: 6.5, 272 | rightBottomCorner: 6.5), 273 | gradient: LinearGradient(colors: [ 274 | Color(0xff1ED5A5), 275 | Color(0xff00B0C5), 276 | ]), 277 | ), 278 | ], 279 | ), 280 | ), 281 | buildDesc("FSuper 支持边框效果,只需要 strokeWidth > 0 即可"), 282 | buildTitle("Gradient"), 283 | buildSmallMargin(), 284 | FSuper( 285 | width: 280, 286 | height: 45, 287 | text: 'Search Flight', 288 | textAlignment: Alignment.center, 289 | style: TextStyle(color: Color(0xff333333), fontSize: 16), 290 | corner: FCorner.all(23), 291 | gradient: LinearGradient(colors: [ 292 | Color(0xfffed83a), 293 | Color(0xfffcad2c), 294 | ]), 295 | ), 296 | buildDesc("gradient 属性能够为 FSuper 设置渐变色背景\n" 297 | "这会覆盖 backgroundColor 属性"), 298 | buildTitle("Shadow"), 299 | buildSmallMargin(), 300 | FSuper( 301 | text: 'Overview', 302 | backgroundColor: Colors.white, 303 | padding: EdgeInsets.fromLTRB(6.0 + 18.0 + 6.0, 9, 9, 9), 304 | corner: 305 | FCorner(rightTopCorner: 20, rightBottomCorner: 20), 306 | child1: Icon( 307 | Icons.subject, 308 | size: 18, 309 | color: Color(0xffa6a4a7), 310 | ), 311 | child1Alignment: Alignment.centerLeft, 312 | child1Margin: EdgeInsets.only(left: 3), 313 | shadowColor: Colors.black38, 314 | shadowBlur: 10, 315 | onClick: () { 316 | toast(context, "Disco"); 317 | }, 318 | isSupportNeumorphism: true, 319 | ), 320 | buildDesc('shadowColor 和 shadowBlur 属性能够设置阴影颜色和样式\n' 321 | '而 shadowOffset 则可以控制阴影的偏移量'), 322 | buildTitle("RedPoint"), 323 | buildMiddleMargin(), 324 | Container( 325 | width: 360, 326 | child: Row( 327 | mainAxisAlignment: MainAxisAlignment.spaceAround, 328 | children: [ 329 | FSuper( 330 | width: 60, 331 | height: 60, 332 | backgroundColor: Colors.white, 333 | corner: FCorner.all(6), 334 | redPoint: true, 335 | redPointSize: 20, 336 | ), 337 | FSuper( 338 | width: 60, 339 | height: 60, 340 | backgroundColor: Colors.white, 341 | corner: FCorner.all(6), 342 | redPoint: true, 343 | redPointSize: 20, 344 | redPointText: "5", 345 | ), 346 | FSuper( 347 | width: 60, 348 | height: 60, 349 | backgroundColor: Colors.white, 350 | corner: FCorner.all(6), 351 | redPoint: true, 352 | redPointSize: 20, 353 | redPointText: "红包", 354 | ), 355 | ], 356 | ), 357 | ), 358 | buildDesc('通过设置 "redPoint: true" 可以展示一个小红点\n' 359 | '使用 redPointSize 属性可以设置小红点的大小\n' 360 | 'redPointText 属性可以设置小红点中的文字\n'), 361 | Container( 362 | width: 360, 363 | child: Row( 364 | crossAxisAlignment: CrossAxisAlignment.center, 365 | mainAxisAlignment: MainAxisAlignment.spaceAround, 366 | children: [ 367 | FSuper( 368 | text: "Home", 369 | width: 60, 370 | height: 60, 371 | textAlignment: Alignment.bottomCenter, 372 | padding: EdgeInsets.only(bottom: 6), 373 | corner: FCorner.all(6), 374 | child1: Icon( 375 | Icons.home, 376 | size: 28, 377 | ), 378 | child1Alignment: Alignment.topCenter, 379 | child1Margin: EdgeInsets.only(top: 6), 380 | redPoint: true, 381 | redPointSize: 10, 382 | redPointOffset: Offset(-5, 0), 383 | ), 384 | FSuper( 385 | width: 36, 386 | height: 36, 387 | corner: FCorner.all(6), 388 | redPoint: true, 389 | redPointSize: 15, 390 | redPointText: "99+", 391 | redPointTextStyle: TextStyle(fontSize: 11), 392 | child1: Icon( 393 | Icons.chat_bubble, 394 | size: 36, 395 | ), 396 | ), 397 | FSuper( 398 | text: "You have messages", 399 | style: TextStyle(color: Colors.white), 400 | redPoint: true, 401 | redPointSize: 10, 402 | redPointOffset: Offset(5, 3), 403 | shadowColor: Color(0xffffc900), 404 | shadowBlur: 18, 405 | ), 406 | ], 407 | ), 408 | ), 409 | buildDesc("通过 redPointOffset 可以控制小红点的位置\n" 410 | "(0, 0) 点在 FSuper 的右上角"), 411 | buildTitle("Child1 & Child2"), 412 | buildSmallMargin(), 413 | SizedBox( 414 | width: 300, 415 | child: Row( 416 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 417 | children: [ 418 | FSuper( 419 | text: "综合排序", 420 | style: TextStyle(color: Colors.blue), 421 | // backgroundColor: Colors.blue, 422 | padding: EdgeInsets.fromLTRB( 423 | 8, 6, (4 + 12 + 8).toDouble(), 6), 424 | corner: FCorner.all(18), 425 | child1: Icon( 426 | Icons.keyboard_arrow_down, 427 | color: Colors.blue, 428 | size: 15, 429 | ), 430 | child1Alignment: Alignment.centerRight, 431 | child1Margin: EdgeInsets.fromLTRB(0, 0, 8, 0), 432 | onClick: () { 433 | toast(context, "Sorting.."); 434 | }, 435 | ), 436 | FSuper( 437 | text: "标签", 438 | style: TextStyle(color: Colors.white), 439 | backgroundColor: Color(0xffffc900), 440 | padding: EdgeInsets.fromLTRB( 441 | 8, 6, (4 + 12 + 8).toDouble(), 6), 442 | corner: FCorner.all(18), 443 | child1: Icon( 444 | Icons.close, 445 | color: Colors.white, 446 | size: 11, 447 | ), 448 | child1Alignment: Alignment.centerRight, 449 | child1Margin: EdgeInsets.fromLTRB(0, 0, 8, 0), 450 | onChild1Click: () { 451 | toast(context, "Close"); 452 | }, 453 | ), 454 | FSuper( 455 | text: "9999+", 456 | height: 15, 457 | style: TextStyle(color: Color(0xff333333)), 458 | padding: EdgeInsets.fromLTRB(22.0 + 3.0, 0, 0, 0), 459 | child1: Icon( 460 | Icons.message, 461 | color: Color(0xff333333), 462 | size: 22, 463 | ), 464 | child1Alignment: Alignment.centerLeft, 465 | onClick: () { 466 | toast(context, "Message count: >9999"); 467 | }, 468 | ), 469 | ], 470 | ), 471 | ), 472 | buildDesc("FSuper 允许设置最多两个确定大小的子组件\n" 473 | "而且你能够通过 child1Alignment、child2Alignment\n" 474 | "以及 child1Margin、child2Margin 来控制它们的位置"), 475 | FSuper( 476 | width: double.infinity, 477 | padding: EdgeInsets.fromLTRB( 478 | (12.0 + 15.0 + 8.0), 8, (15.0 + 12.0), 8), 479 | margin: EdgeInsets.all(10), 480 | corner: FCorner.all(6), 481 | backgroundColor: Color(0xfff3faf1), 482 | strokeColor: Color(0xffe8f6e2), 483 | strokeWidth: 1, 484 | text: '成功提示的文案', 485 | style: TextStyle(color: Colors.grey), 486 | textAlignment: Alignment.centerLeft, 487 | child1: Transform.rotate( 488 | angle: 0, 489 | child: Icon( 490 | Icons.check_circle, 491 | size: 15, 492 | color: Color(0xff89cf6d), 493 | ), 494 | ), 495 | child1Alignment: Alignment.centerLeft, 496 | child1Margin: EdgeInsets.fromLTRB(12, 0, 0, 0), 497 | ), 498 | FSuper( 499 | width: double.infinity, 500 | padding: EdgeInsets.fromLTRB( 501 | (16.0 + 25.0 + 12), 8, (0.0 + 8.0), 8), 502 | margin: EdgeInsets.fromLTRB(10, 10, 10, 0), 503 | corner: FCorner.all(6), 504 | backgroundColor: Color(0xfffff0e7), 505 | strokeColor: Color(0xfffee0cd), 506 | strokeWidth: 1, 507 | text: '警告提示的文案', 508 | style: TextStyle(color: Color(0xff7e7c7a)), 509 | textAlignment: Alignment.centerLeft, 510 | textAlign: TextAlign.left, 511 | spans: [ 512 | FSuper.VerticalSpace(8), 513 | TextSpan( 514 | text: "错误提示的辅助性文字介绍错误提示的辅助性文字介绍错误提" 515 | "示的辅助性文字介绍错误提示的辅助性文字介绍错误提示的辅" 516 | "助性文字介绍错误提示的辅助性文字介绍", 517 | style: TextStyle( 518 | color: Color(0xffc2c9cc), 519 | fontSize: 11, 520 | ), 521 | ) 522 | ], 523 | child1: Transform.rotate( 524 | angle: pi, 525 | child: Icon( 526 | Icons.info, 527 | size: 25, 528 | color: Color(0xfffd6721), 529 | ), 530 | ), 531 | child1Alignment: Alignment.centerLeft, 532 | child1Margin: EdgeInsets.fromLTRB(16, 0, 0, 0), 533 | child2: Icon( 534 | Icons.close, 535 | size: 15, 536 | color: Colors.black38, 537 | ), 538 | child2Alignment: Alignment.topRight, 539 | child2Margin: EdgeInsets.fromLTRB(0, 8, 12, 0), 540 | onChild2Click: () { 541 | toast(context, "关闭警告⚠️"); 542 | }, 543 | ), 544 | buildDesc("你需要确定 FSuper 的大小,或者让 FSuper 自适应大小\n" 545 | "通过 padding 来为子组件留够位置\n" 546 | "当然,你可以为子组件设置点击事件监听\n" 547 | "通过 onChild1Click 和 onChild2Click"), 548 | buildTitle('FSuper Power'), 549 | buildSmallMargin(), 550 | FSuper( 551 | width: double.infinity, 552 | text: "FWidget", 553 | textAlign: TextAlign.left, 554 | style: 555 | TextStyle(fontSize: 18, fontWeight: FontWeight.w600), 556 | padding: 557 | EdgeInsets.fromLTRB(12.0 + 60.0 + 12.0, 20, 12, 20), 558 | margin: EdgeInsets.fromLTRB(12, 0, 12, 0), 559 | strokeWidth: 1, 560 | strokeColor: Colors.grey[300], 561 | backgroundColor: Colors.white, 562 | corner: FCorner.all(9), 563 | spans: [ 564 | FSuper.VerticalSpace(9), 565 | TextSpan( 566 | text: "构建精美 Flutter 应用的绝佳利器 ⚔️", 567 | style: TextStyle( 568 | color: Colors.grey[500], 569 | fontSize: 11, 570 | fontWeight: FontWeight.w300)) 571 | ], 572 | child1: SizedBox( 573 | width: 70, 574 | height: 70, 575 | child: Image.network( 576 | "https://gw.alicdn.com/tfs/TB1a288sxD1gK0jSZFKXXcJrVXa-360-360.png")), 577 | child1Alignment: Alignment.centerLeft, 578 | child1Margin: EdgeInsets.only(left: 9), 579 | child2: FSuper( 580 | text: 'Start Now', 581 | style: TextStyle(color: Color(0xffffad2f)), 582 | padding: EdgeInsets.fromLTRB(6, 3, 6, 3), 583 | corner: FCorner.all(14), 584 | strokeColor: Color(0xffffc900), 585 | strokeWidth: 1, 586 | ), 587 | child2Alignment: Alignment.centerRight, 588 | child2Margin: EdgeInsets.only(right: 9), 589 | onChild2Click: () { 590 | toast(context, "Welcome to use FWidget ⚔"); 591 | }, 592 | ), 593 | buildSmallMargin(), 594 | buildDesc('使用 FSuper 能够快速构建出精美的组件\n' 595 | '你甚至可以将 child1、child2 也设置成 FSuper\n' 596 | '再复杂的视图也能轻松迎刃而解 😉'), 597 | buildSmallMargin(), 598 | FSuper( 599 | height: 86, 600 | padding: EdgeInsets.fromLTRB(12.0 + 80.0 + 12.0, 0, 0, 0), 601 | text: '泰国之行', 602 | style: 603 | TextStyle(fontSize: 15, fontWeight: FontWeight.w600), 604 | textAlignment: Alignment.centerLeft, 605 | textAlign: TextAlign.left, 606 | spans: [ 607 | FSuper.VerticalSpace(8), 608 | TextSpan( 609 | text: '**月**日-**月**日 | **天', 610 | style: TextStyle( 611 | fontSize: 12, 612 | color: Color(0xffb8b4b8), 613 | fontWeight: FontWeight.normal)), 614 | ], 615 | corner: FCorner.all(10), 616 | backgroundColor: Colors.white, 617 | margin: EdgeInsets.only(left: 12, right: 12), 618 | shadowColor: Colors.black38.withOpacity(0.12), 619 | shadowBlur: 5, 620 | child1: Container( 621 | width: 80, 622 | height: 53, 623 | decoration: BoxDecoration( 624 | borderRadius: BorderRadius.circular(10.0), 625 | shape: BoxShape.rectangle, 626 | image: DecorationImage( 627 | fit: BoxFit.cover, 628 | image: NetworkImage( 629 | "https://bkimg.cdn.bcebos.com/pic/023b5bb5c9ea15cef6ee42feb7003af33b87b2c1@wm_1,g_7,k_d2F0ZXIvYmFpa2U5Mg==,xp_5,yp_5", 630 | ), 631 | ), 632 | ), 633 | ), 634 | child1Alignment: Alignment.centerLeft, 635 | child1Margin: EdgeInsets.only(left: 12), 636 | child2: FSuper( 637 | width: 76, 638 | height: 30, 639 | text: '查看', 640 | style: TextStyle(color: Color(0xff333333)), 641 | textAlignment: Alignment.center, 642 | corner: FCorner.all(15), 643 | strokeWidth: 1, 644 | strokeColor: Color(0xfff3f1f7), 645 | ), 646 | child2Alignment: Alignment.centerRight, 647 | child2Margin: EdgeInsets.only(right: 12), 648 | onChild2Click: () { 649 | toast(context, "HAHAHAHA.. 😄"); 650 | }, 651 | ), 652 | buildSmallMargin(), 653 | buildDesc("尝试使用 FSuper 构建精美的组件吧!"), 654 | buildSmallMargin(), 655 | FSuper( 656 | width: double.infinity, 657 | height: 78, 658 | text: '舌尖风味', 659 | style: TextStyle( 660 | fontWeight: FontWeight.w600, 661 | fontSize: 20, 662 | color: Colors.white), 663 | spans: [ 664 | FSuper.VerticalSpace(6), 665 | TextSpan( 666 | text: '时令甄选美食 VIP专属价', 667 | style: TextStyle( 668 | fontSize: 11, 669 | fontWeight: FontWeight.normal, 670 | )) 671 | ], 672 | textAlignment: Alignment.centerLeft, 673 | textAlign: TextAlign.left, 674 | padding: EdgeInsets.only(left: 12), 675 | margin: EdgeInsets.only(left: 12, right: 12), 676 | corner: FCorner(leftTopCorner: 10, rightTopCorner: 10), 677 | backgroundImage: NetworkImage( 678 | 'https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=4186127082,3602109448&fm=15&gp=0.jpg'), 679 | child1: Icon( 680 | Icons.arrow_forward_ios, 681 | color: Colors.white, 682 | ), 683 | child1Alignment: Alignment.centerRight, 684 | child1Margin: EdgeInsets.only(right: 12), 685 | shadowColor: Colors.black38.withOpacity(0.28), 686 | shadowBlur: 5, 687 | ), 688 | Container( 689 | margin: EdgeInsets.only(left: 12, right: 12), 690 | padding: EdgeInsets.all(12), 691 | decoration: BoxDecoration( 692 | borderRadius: BorderRadius.circular(6), 693 | color: Color(0xfff3f0f3), 694 | ), 695 | child: Column( 696 | children: [ 697 | Align( 698 | alignment: Alignment.topRight, 699 | child: FSuper( 700 | maxWidth: 220, 701 | text: 'Hi, Welcome to use FWidget.', 702 | padding: EdgeInsets.only( 703 | left: 12, right: 12, top: 15, bottom: 15), 704 | backgroundColor: Color(0xffa5ed7e), 705 | corner: FCorner.all(6), 706 | child1: Transform.rotate( 707 | angle: pi / 4, 708 | child: FSuper( 709 | width: 10, 710 | height: 10, 711 | backgroundColor: Color(0xffa5ed7e), 712 | corner: FCorner.all(1.5), 713 | ), 714 | ), 715 | child1Alignment: Alignment.topRight, 716 | child1Margin: EdgeInsets.only(right: -4, top: 20), 717 | shadowColor: Color(0xffa5ed7e), 718 | shadowBlur: 5, 719 | ), 720 | ), 721 | buildMiddleMargin(), 722 | Align( 723 | alignment: Alignment.topLeft, 724 | child: FSuper( 725 | maxWidth: 220, 726 | text: 'FWidget is great!Very useful!', 727 | textAlign: TextAlign.left, 728 | padding: EdgeInsets.only( 729 | left: 12, right: 12, top: 15, bottom: 15), 730 | backgroundColor: Colors.white, 731 | corner: FCorner.all(6), 732 | child1: Transform.rotate( 733 | angle: pi / 4, 734 | child: FSuper( 735 | width: 10, 736 | height: 10, 737 | backgroundColor: Colors.white, 738 | corner: FCorner.all(1.5), 739 | ), 740 | ), 741 | child1Alignment: Alignment.topLeft, 742 | child1Margin: EdgeInsets.only(left: -4, top: 20), 743 | shadowColor: Colors.white, 744 | shadowBlur: 5, 745 | ), 746 | ), 747 | buildMiddleMargin(), 748 | Align( 749 | alignment: Alignment.topRight, 750 | child: FSuper( 751 | maxWidth: 220, 752 | textAlign: TextAlign.left, 753 | text: "I'm created by FSuper 😄", 754 | padding: EdgeInsets.only( 755 | left: 12, right: 12, top: 15, bottom: 15), 756 | backgroundColor: Color(0xffa5ed7e), 757 | corner: FCorner.all(6), 758 | child1: Transform.rotate( 759 | angle: pi / 4, 760 | child: FSuper( 761 | width: 10, 762 | height: 10, 763 | backgroundColor: Color(0xffa5ed7e), 764 | corner: FCorner.all(1.5), 765 | ), 766 | ), 767 | child1Alignment: Alignment.topRight, 768 | child1Margin: EdgeInsets.only(right: -4, top: 20), 769 | shadowColor: Color(0xffa5ed7e), 770 | shadowBlur: 5, 771 | ), 772 | ), 773 | buildMiddleMargin(), 774 | Align( 775 | alignment: Alignment.topLeft, 776 | child: FSuper( 777 | maxWidth: 220, 778 | text: "So am I 😄", 779 | textAlign: TextAlign.left, 780 | padding: EdgeInsets.only( 781 | left: 12, right: 12, top: 15, bottom: 15), 782 | backgroundColor: Colors.white, 783 | corner: FCorner.all(6), 784 | child1: Transform.rotate( 785 | angle: pi / 4, 786 | child: FSuper( 787 | width: 10, 788 | height: 10, 789 | backgroundColor: Colors.white, 790 | corner: FCorner.all(1.5), 791 | ), 792 | ), 793 | child1Alignment: Alignment.topLeft, 794 | child1Margin: EdgeInsets.only(left: -4, top: 20), 795 | shadowColor: Colors.white, 796 | shadowBlur: 5, 797 | ), 798 | ), 799 | ], 800 | ), 801 | ), 802 | buildMiddleMargin(), 803 | 804 | buildTitle("Neumorphism"), 805 | buildBigMargin(), 806 | 807 | /// Neumorphism 808 | neumorphismDemo(), 809 | buildMiddleMargin(), 810 | buildMiddleMargin(), 811 | ], 812 | ), 813 | ); 814 | }, 815 | ), 816 | )); 817 | } 818 | 819 | FLightOrientation lightOrientation = FLightOrientation.LeftTop; 820 | 821 | Stack neumorphismDemo() { 822 | return Stack( 823 | children: [ 824 | Padding( 825 | padding: EdgeInsets.only(left: 20, right: 20, top: 50, bottom: 0), 826 | child: Column( 827 | children: [ 828 | /// Neumorphism Style 1 829 | FSuper( 830 | width: 250, 831 | height: 400, 832 | backgroundColor: Color(0xff28292f), 833 | shadowColor: Colors.black54, 834 | corner: FCorner.all(6.0), 835 | shadowOffset: Offset(3.0, 3.0), 836 | shadowBlur: 3.0, 837 | child1: FSuper( 838 | lightOrientation: lightOrientation, 839 | width: 108, 840 | height: 108, 841 | backgroundColor: Color(0xff28292f), 842 | isSupportNeumorphism: true, 843 | shadowColor: Colors.black87, 844 | highlightShadowColor: Colors.white24, 845 | shadowOffset: Offset(0.0, 2.0), 846 | corner: FCorner.all(80), 847 | float: false, 848 | child1: FSuper( 849 | lightOrientation: lightOrientation, 850 | width: 70, 851 | height: 70, 852 | backgroundColor: Color(0xff28292f), 853 | isSupportNeumorphism: true, 854 | shadowColor: Colors.black87, 855 | highlightShadowColor: Colors.white24, 856 | shadowOffset: Offset(0.0, 1.0), 857 | corner: FCorner.all(60), 858 | child1: Icon( 859 | Icons.play_arrow, 860 | color: Colors.grey[300], 861 | size: 30, 862 | ), 863 | ), 864 | child2: FSuper( 865 | lightOrientation: lightOrientation, 866 | width: 150, 867 | height: 40, 868 | text: "FWidget..", 869 | textAlignment: Alignment.center, 870 | style: TextStyle( 871 | color: Color(0xffc8e6c9), 872 | fontSize: 11.5, 873 | shadows: [ 874 | BoxShadow( 875 | color: Color(0xfffff59d), 876 | blurRadius: 6, 877 | offset: Offset(1, 1)) 878 | ], 879 | ), 880 | backgroundColor: Color(0xff28292f), 881 | isSupportNeumorphism: true, 882 | shadowColor: Colors.black87, 883 | highlightShadowColor: Colors.white24, 884 | shadowOffset: Offset(0.0, 1.0), 885 | corner: FCorner.all(40), 886 | child1: FSuper( 887 | width: 30, 888 | height: 30, 889 | text: "+", 890 | style: TextStyle( 891 | color: Colors.grey[300], 892 | fontSize: 23, 893 | height: 1, 894 | ), 895 | textAlignment: Alignment.center, 896 | ), 897 | child1Alignment: Alignment.centerLeft, 898 | child1Margin: EdgeInsets.only(left: 9), 899 | child2: FSuper( 900 | width: 30, 901 | height: 30, 902 | text: "-", 903 | style: TextStyle( 904 | color: Colors.grey[300], 905 | fontSize: 26, 906 | height: 1, 907 | ), 908 | textAlignment: Alignment.center, 909 | ), 910 | child2Alignment: Alignment.centerRight, 911 | child2Margin: EdgeInsets.only(right: 9), 912 | ), 913 | child2Alignment: Alignment.bottomCenter, 914 | child2Margin: EdgeInsets.only(top: 80), 915 | ), 916 | child1Margin: EdgeInsets.only(bottom: 100), 917 | child2: Row( 918 | children: [ 919 | FSuper( 920 | lightOrientation: lightOrientation, 921 | width: 40, 922 | height: 40, 923 | backgroundColor: Color(0xff28292f), 924 | isSupportNeumorphism: true, 925 | shadowColor: Colors.black87, 926 | highlightShadowColor: Colors.white24, 927 | shadowOffset: Offset(0.0, 1.0), 928 | corner: FCorner.all(40), 929 | child1: Icon( 930 | Icons.arrow_back_ios, 931 | color: Colors.grey[300], 932 | size: 16, 933 | ), 934 | ), 935 | const SizedBox(width: 20), 936 | FSuper( 937 | lightOrientation: lightOrientation, 938 | width: 50, 939 | height: 50, 940 | backgroundColor: Color(0xff28292f), 941 | isSupportNeumorphism: true, 942 | shadowColor: Colors.black87, 943 | highlightShadowColor: Colors.white24, 944 | shadowOffset: Offset(0.0, 1.0), 945 | corner: FCorner.all(40), 946 | child1: Icon( 947 | Icons.star, 948 | color: Color(0xfffff176), 949 | size: 23, 950 | ), 951 | float: false, 952 | ), 953 | const SizedBox(width: 20), 954 | FSuper( 955 | lightOrientation: lightOrientation, 956 | width: 40, 957 | height: 40, 958 | backgroundColor: Color(0xff28292f), 959 | isSupportNeumorphism: true, 960 | shadowColor: Colors.black87, 961 | highlightShadowColor: Colors.white24, 962 | shadowOffset: Offset(0.0, 1.0), 963 | corner: FCorner.all(40), 964 | child1: Icon( 965 | Icons.arrow_forward_ios, 966 | color: Colors.grey[300], 967 | size: 16, 968 | ), 969 | ), 970 | ], 971 | ), 972 | child2Alignment: Alignment.bottomCenter, 973 | child2Margin: EdgeInsets.only(bottom: 45), 974 | ), 975 | buildMiddleMargin(), 976 | buildMiddleMargin(), 977 | 978 | /// Neumorphism Style 2 979 | ], 980 | ), 981 | ), 982 | Positioned( 983 | left: 0, 984 | top: 0, 985 | child: Transform.rotate( 986 | angle: -pi / 4.0, 987 | alignment: Alignment.center, 988 | child: FRadio.custom( 989 | value: FLightOrientation.LeftTop, 990 | groupValue: lightOrientation, 991 | normal: Image.asset("assets/icon_light_unselected.png"), 992 | selected: Image.asset("assets/icon_light_selected.png"), 993 | onChanged: (value) { 994 | setState(() { 995 | lightOrientation = value; 996 | }); 997 | }, 998 | ), 999 | )), 1000 | Positioned( 1001 | right: 0, 1002 | top: 0, 1003 | child: Transform.rotate( 1004 | angle: pi / 4.0, 1005 | alignment: Alignment.center, 1006 | child: FRadio.custom( 1007 | value: FLightOrientation.RightTop, 1008 | groupValue: lightOrientation, 1009 | normal: Image.asset("assets/icon_light_unselected.png"), 1010 | selected: Image.asset("assets/icon_light_selected.png"), 1011 | onChanged: (value) { 1012 | setState(() { 1013 | lightOrientation = value; 1014 | }); 1015 | }, 1016 | ), 1017 | )), 1018 | Positioned( 1019 | right: 0, 1020 | bottom: 0, 1021 | child: Transform.rotate( 1022 | angle: -pi / (3.0 / 4.0), 1023 | alignment: Alignment.center, 1024 | child: FRadio.custom( 1025 | value: FLightOrientation.RightBottom, 1026 | groupValue: lightOrientation, 1027 | normal: Image.asset("assets/icon_light_unselected.png"), 1028 | selected: Image.asset("assets/icon_light_selected.png"), 1029 | onChanged: (value) { 1030 | setState(() { 1031 | lightOrientation = value; 1032 | }); 1033 | }, 1034 | ), 1035 | )), 1036 | Positioned( 1037 | left: 0, 1038 | bottom: 0, 1039 | child: Transform.rotate( 1040 | angle: pi / (3.0 / 4.0), 1041 | alignment: Alignment.center, 1042 | child: FRadio.custom( 1043 | value: FLightOrientation.LeftBottom, 1044 | groupValue: lightOrientation, 1045 | normal: Image.asset("assets/icon_light_unselected.png"), 1046 | selected: Image.asset("assets/icon_light_selected.png"), 1047 | onChanged: (value) { 1048 | setState(() { 1049 | lightOrientation = value; 1050 | }); 1051 | }, 1052 | ), 1053 | )), 1054 | ], 1055 | ); 1056 | } 1057 | } 1058 | 1059 | class Test extends StatelessWidget { 1060 | String text; 1061 | 1062 | Test(this.text); 1063 | 1064 | @override 1065 | Widget build(BuildContext context) { 1066 | return FSuper( 1067 | text: text, 1068 | // padding: EdgeInsets.fromLTRB(50, 50, 50, 50), 1069 | backgroundColor: Colors.blue, 1070 | child1: Icon(Icons.check_circle), 1071 | child1Alignment: Alignment.bottomRight, 1072 | // child1Margin: EdgeInsets.all(20), 1073 | margin: EdgeInsets.all(50), 1074 | // padding: EdgeInsets.all(50), 1075 | redPoint: true, 1076 | onChild1Click: () {}, 1077 | ); 1078 | } 1079 | } 1080 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: fsuper_example 2 | description: Demonstrates how to use the fsuper plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | fcommon: 13 | git: 14 | url: 'git@github.com:Fliggy-Mobile/fcommon.git' 15 | ref: 'master' 16 | 17 | # The following adds the Cupertino Icons font to your application. 18 | # Use with the CupertinoIcons class for iOS style icons. 19 | cupertino_icons: ^0.1.2 20 | fbutton: ^1.0.4 21 | fswitch: ^1.1.2 22 | fradio: ^1.0.1 23 | ffloat: ^1.0.1 24 | frefresh: ^1.1.0 25 | fdottedline: ^1.0.0 26 | fsearch: ^1.0.0 27 | 28 | dev_dependencies: 29 | flutter_test: 30 | sdk: flutter 31 | 32 | fsuper: 33 | path: ../ 34 | 35 | # For information on the generic Dart part of this file, see the 36 | # following page: https://dart.dev/tools/pub/pubspec 37 | 38 | # The following section is specific to Flutter. 39 | flutter: 40 | 41 | # The following line ensures that the Material Icons font is 42 | # included with your application, so that you can use the icons in 43 | # the material Icons class. 44 | uses-material-design: true 45 | 46 | # To add assets to your application, add an assets section, like this: 47 | # assets: 48 | # - images/a_dot_burr.jpeg 49 | # - images/a_dot_ham.jpeg 50 | assets: 51 | - assets/ 52 | # An image asset can refer to one or more resolution-specific "variants", see 53 | # https://flutter.dev/assets-and-images/#resolution-aware. 54 | 55 | # For details regarding adding assets from package dependencies, see 56 | # https://flutter.dev/assets-and-images/#from-packages 57 | 58 | # To add custom fonts to your application, add a fonts section here, 59 | # in this "flutter" section. Each entry in this list should have a 60 | # "family" key with the font family name, and a "fonts" key with a 61 | # list giving the asset and other descriptors for the font. For 62 | # example: 63 | # fonts: 64 | # - family: Schyler 65 | # fonts: 66 | # - asset: fonts/Schyler-Regular.ttf 67 | # - asset: fonts/Schyler-Italic.ttf 68 | # style: italic 69 | # - family: Trajan Pro 70 | # fonts: 71 | # - asset: fonts/TrajanPro.ttf 72 | # - asset: fonts/TrajanPro_Bold.ttf 73 | # weight: 700 74 | # 75 | # For details regarding fonts from package dependencies, 76 | # see https://flutter.dev/custom-fonts/#from-packages 77 | -------------------------------------------------------------------------------- /lib/fsuper.dart: -------------------------------------------------------------------------------- 1 | /// Copyright 1999-2020 Fliggy Android Team . 2 | /// 3 | /// Licensed under the Apache License, Version 2.0 (the "License"); 4 | /// you may not use this file except in compliance with the License. 5 | /// You may obtain a copy of the License at following link. 6 | /// 7 | /// http://www.apache.org/licenses/LICENSE-2.0 8 | /// 9 | /// Unless required by applicable law or agreed to in writing, software 10 | /// distributed under the License is distributed on an "AS IS" BASIS, 11 | /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | /// See the License for the specific language governing permissions and 13 | /// limitations under the License. 14 | import 'dart:math' as math; 15 | 16 | import 'package:fcontrol/fcontrol.dart'; 17 | import 'package:fcontrol/fdefine.dart'; 18 | import 'package:flutter/material.dart'; 19 | import 'package:flutter/rendering.dart'; 20 | import 'package:flutter/scheduler.dart'; 21 | 22 | export 'package:fcontrol/fdefine.dart'; 23 | 24 | /// FSuper 是一个强大的组件,能够支持富文本、圆角、边框、图片、小红点、以及同时设置多达两个子组件,且能够通过相对位置控制它们。 25 | /// FSuper 能够帮助开发者快速舒适的构建复杂视图。 26 | /// 27 | /// FSuper is a powerful component that can support rich text, rounded corners, borders, pictures, small red dots, and set up to two sub-components at the same time, and control their relative positions. 28 | /// FSuper can help developers build complex views quickly and comfortably. 29 | // ignore: must_be_immutable 30 | class FSuper extends StatefulWidget { 31 | /// 宽。null 将会自适应文字大小。设置 double.infinity 将会充满父容器。 32 | /// 33 | /// Width,null will adapt the text size. Setting double.infinity will fill the parent container. 34 | final double? width; 35 | 36 | /// 宽。null 将会自适应文字大小。 37 | /// 38 | /// Height, null will adapt the text size. 39 | final double? height; 40 | 41 | /// 组件最大宽度。 42 | /// 43 | /// Maximum component width. 44 | final double? maxWidth; 45 | 46 | /// 组件最大高度 47 | /// 48 | /// Maximum component height 49 | final double? maxHeight; 50 | 51 | /// 文字内容 52 | /// 53 | /// Text content 54 | final String? text; 55 | 56 | /// Widget 文本样式 57 | /// 58 | /// Widget text style 59 | final TextStyle? style; 60 | 61 | /// Widget 的文本区域样式 62 | /// 63 | /// Widget strut style 64 | final StrutStyle? strutStyle; 65 | 66 | /// 文字整体相对位置。详见 [Alignment] 67 | /// 68 | /// The overall relative position of the text. See [Alignment] for details 69 | final Alignment? textAlignment; 70 | 71 | /// 文字对齐方式。详见 [Align] 72 | /// 73 | /// Text alignment. See [Align] for details 74 | final TextAlign? textAlign; 75 | 76 | /// 富文本内容。详见 [TextSpan] 。 77 | /// [TextSpan] 元素默认会使用 [textColor],[textSize],[textStyle],[textWeight] 的配置。 78 | /// 79 | /// Rich text content. See [TextSpan] for more details. 80 | /// The [TextSpan] element uses the configuration of [textColor], [textSize], [textStyle], [textWeight] by default. 81 | final List? spans; 82 | 83 | /// 点击监听回调 84 | /// 85 | /// Click listen callback 86 | final GestureTapCallback? onClick; 87 | 88 | /// 组件背景颜色。 89 | /// 90 | /// Component background color 91 | final Color? backgroundColor; 92 | 93 | /// 设置组件渐变色背景。会覆盖 [backgroundColor] 94 | /// 你可选择 [LinearGradient],[RadialGradient],[SweepGradient] 等.. 95 | /// 96 | /// Sets the gradient background of the component. [BackgroundColor] 97 | /// You can choose [LinearGradient], [RadialGradient], [SweepGradient], etc .. 98 | final Gradient? gradient; 99 | 100 | /// 组件背景图片,会覆盖 [backgroundColor] 和 [gradient]。详见 [ImageProvider] 101 | /// 102 | /// Component background image, overwrites [backgroundColor] and [gradient]. 103 | /// See [ImageProvider] for details 104 | final ImageProvider? backgroundImage; 105 | 106 | /// 设置组件圆角。详见 [FCorner] 107 | /// 108 | /// Sets the component fillet. See [FCorner] for details 109 | final FCorner? corner; 110 | 111 | /// 设置圆角风格,默认 [FCornerStyle.round] 112 | /// 113 | /// Set rounded corner style, default [FCornerStyle.round] 114 | final FCornerStyle cornerStyle; 115 | 116 | /// 设置组件内间距,只影响文字。通过该属性可以设置文字和 [FSuper.child1]、[FSuper.child2] 的间距。 117 | /// 118 | /// Sets the spacing between components, affecting only text. 119 | /// This property can set the space between the text and [FSuper.child1], [FSuper.child2]. 120 | final EdgeInsetsGeometry? padding; 121 | 122 | /// 设置组件与父容器边缘的间距。详见 [EdgeInsets] 123 | /// 124 | /// Sets the distance between the component and the edge of the parent container. See [EdgeInsets] for details 125 | final EdgeInsets? margin; 126 | 127 | /// 子组件。子组件允许是任意类型的 Widget。 128 | /// 通过 [child1Alignment] 和 [child1Margin] 可以控制它在组件中的位置。 129 | /// 130 | /// Subassembly. Child components are allowed to be any type of widget. 131 | /// [Child1Alignment] and [child1Margin] can control its position in the component. 132 | final Widget? child1; 133 | 134 | /// 指定 [child1] 在组件中的相对位置 135 | /// 136 | /// Specify the relative position of the child component in the component 137 | final Alignment? child1Alignment; 138 | 139 | /// 基于 [child1] 的相对位置,为它设置偏移 140 | /// 141 | /// Set an offset for the child component based on its relative position 142 | final EdgeInsets? child1Margin; 143 | 144 | /// 为 [child1] 设置点击监听器 145 | /// 146 | /// Set click listener for [child1] 147 | final GestureTapCallback? onChild1Click; 148 | 149 | /// 子组件。子组件允许是任意类型的 Widget。 150 | /// 通过 [child1Alignment] 和 [child1Margin] 可以控制它在组件中的位置。 151 | /// 152 | /// Subassembly. Child components are allowed to be any type of widget. 153 | /// [Child1Alignment] and [child1Margin] can control its position in the component. 154 | final Widget? child2; 155 | 156 | /// 指定 [child2] 在组件中的相对位置 157 | /// 158 | /// Specify the relative position of the child component in the component 159 | final Alignment? child2Alignment; 160 | 161 | /// 基于 [child2] 的相对位置,为它设置偏移 162 | /// 163 | /// Set an offset for the child component based on its relative position 164 | final EdgeInsets? child2Margin; 165 | 166 | /// 为 [child2] 设置点击监听器 167 | /// 168 | /// Set click listener for [child2] 169 | final GestureTapCallback? onChild2Click; 170 | 171 | /// 是否显示小红点。默认不展示。 172 | /// 173 | /// Whether to show the Red Point.Not displayed by default. 174 | final bool redPoint; 175 | 176 | /// 小红点颜色。默认 [Colors.redAccent] 177 | /// 178 | /// Red Point color. Default [Colors.redAccent] 179 | final Color redPointColor; 180 | 181 | /// 设置 Red Point 大小。Red Point 的 宽=高 182 | /// 183 | /// Set the Red Point size. Red Point width = height 184 | final double redPointSize; 185 | 186 | /// 设置 Red Point 中的文字。比如消息数量.. 187 | /// 188 | /// Set the text in Red Point. Like number of messages ... 189 | final String? redPointText; 190 | 191 | /// 小红点文本样式 192 | /// 193 | /// red point text style 194 | final TextStyle redPointTextStyle; 195 | 196 | /// 基于组件右上角设置 Red Point 的位置偏移。 197 | /// 默认 Red Point 会向右上方偏移 [redPointSize] / 2 198 | /// 199 | /// Sets the position of the Red Point based on the upper right corner of the component. 200 | /// The default Red Point is shifted to the upper right [redPointSize] / 2 201 | final Offset? redPointOffset; 202 | 203 | /// 设置边框颜色。 204 | /// 205 | /// Set the border color. 206 | final Color? strokeColor; 207 | 208 | /// 设置边框宽 209 | /// 210 | /// Set border width 211 | final double? strokeWidth; 212 | 213 | /// 设置组件阴影颜色 214 | /// 215 | /// Set component shadow color 216 | final Color? shadowColor; 217 | 218 | /// 设置组件阴影偏移 219 | /// 220 | /// Set component shadow offset 221 | final Offset? shadowOffset; 222 | 223 | /// 设置组件高斯与阴影形状卷积的标准偏差。 224 | /// 225 | /// Sets the standard deviation of the component's Gaussian convolution with the shadow shape. 226 | final double shadowBlur; 227 | 228 | /// 是否支持 Neumorphism 风格。开启该项 [highlightColor] 将会失效 229 | /// 230 | /// Whether to support the Neumorphism style. Open this item [highlightColor] will be invalid 231 | final bool isSupportNeumorphism; 232 | 233 | /// 当 [isSupportNeumorphism] 为 true 时有效。光源方向,分为左上、左下、右上、右下四个方向。用来控制光源照射方向,会影响高亮方向和阴影方向 234 | /// 235 | /// Valid when [isSupportNeumorphism] is true. The direction of the light source is divided into four directions: upper left, lower left, upper right, and lower right. Used to control the illumination direction of the light source, which will affect the highlight direction and shadow direction 236 | final FLightOrientation? lightOrientation; 237 | 238 | /// 开启 Neumorphism 风格后,亮部阴影颜色 239 | /// 240 | /// After the Neumorphism style is turned on, the bright shadow color 241 | final Color? highlightShadowColor; 242 | 243 | /// 开启 Neumorphism 风格后,是否呈浮起效果,否则为凹陷效果,默认为 true 244 | /// 245 | /// Whether the Neumorphism style is turned on, whether it is a floating effect, otherwise it is a concave effect, the default is true 246 | final bool float; 247 | 248 | FSuper({ 249 | this.width, 250 | this.height, 251 | this.maxWidth, 252 | this.maxHeight, 253 | this.text, 254 | this.textAlignment, 255 | this.textAlign, 256 | this.spans, 257 | this.onClick, 258 | this.backgroundColor, 259 | this.backgroundImage, 260 | this.child1, 261 | this.child1Alignment, 262 | this.child1Margin, 263 | this.onChild1Click, 264 | this.child2, 265 | this.child2Alignment, 266 | this.child2Margin, 267 | this.onChild2Click, 268 | this.redPoint = false, 269 | this.redPointColor = Colors.redAccent, 270 | this.redPointSize = 20, 271 | this.redPointOffset, 272 | this.redPointText, 273 | this.redPointTextStyle = const TextStyle(color: Colors.white, fontSize: 11), 274 | this.gradient, 275 | this.padding, 276 | this.corner, 277 | this.cornerStyle = FCornerStyle.round, 278 | this.strokeColor, 279 | this.strokeWidth, 280 | this.shadowColor, 281 | this.shadowOffset, 282 | this.shadowBlur = 1, 283 | this.margin, 284 | this.style, 285 | this.strutStyle, 286 | this.isSupportNeumorphism = false, 287 | this.highlightShadowColor, 288 | this.lightOrientation, 289 | this.float = true, 290 | }); 291 | 292 | @override 293 | State createState() { 294 | return _FSuperState(); 295 | } 296 | 297 | /// 在富文本 [spans] 中插入垂直方向的文字间距 298 | /// 299 | /// Insert vertical spacing in rich text [spans] 300 | static TextSpan VerticalSpace(double space) { 301 | return TextSpan( 302 | text: '\n \n', 303 | style: TextStyle( 304 | fontSize: 1, 305 | height: space, 306 | )); 307 | } 308 | 309 | /// 在富文本 [spans] 中插入水平方向的文字间距 310 | /// 311 | /// Insert horizontal spacing in rich text [spans] 312 | static TextSpan HorizontalSpace(double space) { 313 | return TextSpan( 314 | text: " ", 315 | style: TextStyle( 316 | fontSize: 0.1, 317 | wordSpacing: (space - 1), 318 | )); 319 | } 320 | } 321 | 322 | class _FSuperState extends State { 323 | GlobalKey rootKey = GlobalKey(); 324 | Size? containerSize; 325 | 326 | /// [FSuper] 在初始化时会注册一帧回调,当组件首次测量完成后,将会根据现有尺寸根据再次确定尺寸配置。 327 | /// 接着会确定 [FSuper.child1],[FSuper.child2] 的位置。 328 | /// 最后会再次触发绘制。 329 | /// 330 | /// [FSuper] A frame callback is registered during initialization. 331 | /// After the component's first measurement is completed, the size configuration will be determined again based on the existing size. 332 | /// Then the location of [FSuper.child1], [FSuper.child2] will be determined. 333 | /// Finally, the drawing will be triggered again. 334 | @override 335 | void initState() { 336 | WidgetsBinding.instance?.addPostFrameCallback(_handleSizeChanged); 337 | super.initState(); 338 | } 339 | 340 | void _handleSizeChanged(duration) { 341 | if (!mounted) return; 342 | RenderBox rootBox = rootKey.currentContext?.findRenderObject() as RenderBox; 343 | if (rootBox != null && containerSize != rootBox.size) { 344 | setState(() { 345 | containerSize = rootBox.size; 346 | }); 347 | } 348 | WidgetsBinding.instance?.addPostFrameCallback(_handleSizeChanged); 349 | } 350 | 351 | @override 352 | Widget build(BuildContext context) { 353 | BorderRadius borderRadius = widget.corner == null 354 | ? BorderRadius.all(Radius.circular(0)) 355 | : BorderRadius.only( 356 | topLeft: Radius.circular(widget.corner!.leftTopCorner), 357 | topRight: Radius.circular(widget.corner!.rightTopCorner), 358 | bottomRight: Radius.circular(widget.corner!.rightBottomCorner), 359 | bottomLeft: Radius.circular(widget.corner!.leftBottomCorner), 360 | ); 361 | var sideColor = widget.strokeColor ?? Colors.transparent; 362 | var borderSide = BorderSide( 363 | width: widget.strokeWidth ?? 0, 364 | color: sideColor, 365 | style: BorderStyle.solid, 366 | ); 367 | var shape = widget.cornerStyle == FCornerStyle.round 368 | ? RoundedRectangleBorder(borderRadius: borderRadius) 369 | : BeveledRectangleBorder(borderRadius: borderRadius); 370 | 371 | /// shape 372 | FShape fShape = FShape( 373 | borderShape: widget.cornerStyle == FCornerStyle.round 374 | ? FBorderShape.RoundedRectangle 375 | : FBorderShape.BeveledRectangle, 376 | side: borderSide, 377 | borderRadius: borderRadius, 378 | ); 379 | 380 | var decorationImage = widget.backgroundImage != null 381 | ? DecorationImage( 382 | fit: BoxFit.cover, 383 | image: widget.backgroundImage!, 384 | ) 385 | : null; 386 | var decoration = decorationImage != null 387 | ? ShapeDecoration(image: decorationImage, shape: shape) 388 | : null; 389 | var textPart = Text.rich( 390 | TextSpan(text: widget.text, children: widget.spans), 391 | textAlign: widget.textAlign ?? TextAlign.center, 392 | style: widget.style, 393 | strutStyle: widget.strutStyle, 394 | textHeightBehavior: TextHeightBehavior( 395 | applyHeightToFirstAscent: true, 396 | applyHeightToLastDescent: true, 397 | ), 398 | ); 399 | List children = []; 400 | 401 | /// Build base container 402 | Widget containerPart = FControl( 403 | key: rootKey, 404 | lightOrientation: widget.lightOrientation ?? FLightOrientation.LeftTop, 405 | width: widget.width, 406 | height: widget.height, 407 | shape: fShape, 408 | gradient: widget.gradient, 409 | supportDropShadow: 410 | (widget.shadowColor != null && widget.shadowBlur != 0.0) || 411 | widget.isSupportNeumorphism, 412 | dropShadow: FShadow( 413 | highlightColor: widget.isSupportNeumorphism 414 | ? widget.highlightShadowColor ?? Colors.white.withOpacity(0.83) 415 | : Colors.transparent, 416 | highlightBlur: widget.isSupportNeumorphism ? _shadowBlur : 0.0, 417 | highlightDistance: shadowDistance, 418 | shadowColor: widget.shadowColor ?? Color(0xffd1d9e6), 419 | shadowBlur: _shadowBlur, 420 | shadowDistance: shadowDistance, 421 | shadowOffset: widget.shadowOffset, 422 | ), 423 | supportInnerShadow: widget.isSupportNeumorphism, 424 | innerShadow: FShadow( 425 | highlightColor: 426 | widget.highlightShadowColor ?? Colors.white.withOpacity(0.83), 427 | highlightBlur: _shadowBlur, 428 | highlightDistance: shadowDistance, 429 | shadowColor: widget.shadowColor ?? Color(0xffd1d9e6), 430 | shadowBlur: _shadowBlur, 431 | shadowDistance: shadowDistance, 432 | ), 433 | appearance: widget.isSupportNeumorphism 434 | ? FAppearance.Neumorphism 435 | : FAppearance.Material, 436 | colorForCallback: (sender, state) { 437 | return widget.backgroundColor ?? Colors.transparent; 438 | }, 439 | userInteractive: false, 440 | isSelected: !widget.float, 441 | controlType: FType.Toggle, 442 | maskColor: Colors.transparent, 443 | child: Container( 444 | alignment: widget.textAlignment, 445 | decoration: decoration, 446 | padding: widget.padding, 447 | child: textPart, 448 | ), 449 | ); 450 | if (widget.maxWidth != null || widget.maxHeight != null) { 451 | containerPart = LimitedBox( 452 | maxWidth: widget.maxWidth ?? double.infinity, 453 | maxHeight: widget.maxHeight ?? double.infinity, 454 | child: Container( 455 | width: widget.maxWidth ?? double.infinity, child: containerPart), 456 | ); 457 | } 458 | children.add(Container( 459 | margin: widget.margin, 460 | child: GestureDetector( 461 | onTap: widget.onClick, 462 | child: containerPart, 463 | ), 464 | )); 465 | 466 | /// if getted [containerSize], [child1] or [child2] not null,build child part 467 | if (containerSize != null) { 468 | if (widget.child1 != null || widget.child2 != null) { 469 | children.addAll(buildChildPart()); 470 | } 471 | } 472 | 473 | /// build red point 474 | if (widget.redPoint) { 475 | var redPointPart = buildRedPoint(); 476 | children.add(redPointPart); 477 | } 478 | 479 | /// build up 480 | var stackPart = _Stack( 481 | clipBehavior: Clip.none, 482 | children: children, 483 | ); 484 | return stackPart; 485 | } 486 | 487 | double get _shadowBlur { 488 | if ((widget.shadowBlur == null || widget.shadowBlur == 0.0) && 489 | widget.isSupportNeumorphism) { 490 | return 6.0; 491 | } else { 492 | return widget.shadowBlur; 493 | } 494 | } 495 | 496 | double get shadowDistance { 497 | if (widget.isSupportNeumorphism) { 498 | return widget.shadowOffset?.dy ?? 3.0; 499 | } else { 500 | return widget.shadowOffset?.dy ?? 0.0; 501 | } 502 | } 503 | 504 | Size child1Size = Size.zero; 505 | Size child2Size = Size.zero; 506 | 507 | List buildChildPart() { 508 | List children = []; 509 | if (widget.child1 != null) { 510 | Offset offset = computeChildOffset( 511 | widget.child1Alignment, child1Size, widget.child1Margin); 512 | children.add(_MeasureSize( 513 | onChange: (size) { 514 | setState(() { 515 | child1Size = size; 516 | }); 517 | }, 518 | child: Positioned( 519 | left: offset.dx, 520 | top: offset.dy, 521 | child: GestureDetector( 522 | /// 当子 Child 不处理事件时,作为整体,应该响应 FSuper 的事件 523 | /// 524 | /// When Child does not handle events, as a whole, it should respond to FSuper events 525 | onTap: widget.onChild1Click ?? widget.onClick, 526 | child: widget.child1, 527 | ), 528 | ))); 529 | } 530 | if (widget.child2 != null) { 531 | Offset offset = computeChildOffset( 532 | widget.child2Alignment, child2Size, widget.child2Margin); 533 | children.add(_MeasureSize( 534 | onChange: (size) { 535 | setState(() { 536 | child2Size = size; 537 | }); 538 | }, 539 | child: Positioned( 540 | left: offset.dx, 541 | top: offset.dy, 542 | child: GestureDetector( 543 | /// 当子 Child 不处理事件时,作为整体,应该响应 FSuper 的事件 544 | /// 545 | /// When Child does not handle events, as a whole, it should respond to FSuper events 546 | onTap: widget.onChild2Click ?? widget.onClick, 547 | child: widget.child2, 548 | ), 549 | ))); 550 | } 551 | return children; 552 | } 553 | 554 | Offset computeChildOffset( 555 | Alignment? alignment, Size childSize, EdgeInsets? childMargin) { 556 | Offset childOffset = Offset.zero; 557 | Size size = containerSize ?? Size.zero; 558 | if (alignment == Alignment.topLeft) { 559 | childOffset = Offset(0, 0); 560 | } else if (alignment == Alignment.topCenter) { 561 | childOffset = Offset(size.width / 2 - childSize.width / 2, 0); 562 | } else if (alignment == Alignment.topRight) { 563 | childOffset = Offset(size.width - childSize.width, 0); 564 | } else if (alignment == Alignment.centerRight) { 565 | childOffset = Offset( 566 | size.width - childSize.width, size.height / 2 - childSize.height / 2); 567 | } else if (alignment == Alignment.bottomRight) { 568 | childOffset = 569 | Offset(size.width - childSize.width, size.height - childSize.height); 570 | } else if (alignment == Alignment.bottomCenter) { 571 | childOffset = Offset( 572 | size.width / 2 - childSize.width / 2, size.height - childSize.height); 573 | } else if (alignment == Alignment.bottomLeft) { 574 | childOffset = Offset(0, size.height - childSize.height); 575 | } else if (alignment == Alignment.centerLeft) { 576 | childOffset = Offset(0, size.height / 2 - childSize.height / 2); 577 | } else { 578 | childOffset = Offset(size.width / 2 - childSize.width / 2, 579 | size.height / 2 - childSize.height / 2); 580 | } 581 | if (childMargin != null) { 582 | childOffset = childOffset.translate(childMargin.left - childMargin.right, 583 | childMargin.top - childMargin.bottom); 584 | } 585 | if (widget.margin != null) { 586 | childOffset = 587 | childOffset.translate(widget.margin!.left, widget.margin!.top); 588 | } 589 | return childOffset; 590 | } 591 | 592 | Widget buildRedPoint() { 593 | bool redPointTextEmpty = 594 | widget.redPointText == null || widget.redPointText == ""; 595 | Color redPointTextColor = widget.redPointTextStyle.color ?? Colors.white; 596 | double redPointTextSize = 597 | redPointTextEmpty ? 0.0 : (widget.redPointTextStyle.fontSize ?? 0.0); 598 | var offset = widget.redPointOffset ?? 599 | Offset(widget.redPointSize / 2.0, widget.redPointSize / 2.0); 600 | var redPointPart = Positioned( 601 | right: -offset.dx, 602 | top: -offset.dy, 603 | child: Container( 604 | padding: EdgeInsets.fromLTRB( 605 | redPointTextSize / 2.0, 0, redPointTextSize / 2.0, 0), 606 | constraints: BoxConstraints( 607 | minWidth: widget.redPointSize, 608 | maxHeight: widget.redPointSize, 609 | ), 610 | alignment: Alignment.center, 611 | decoration: BoxDecoration( 612 | color: widget.redPointColor, 613 | borderRadius: 614 | BorderRadius.all(Radius.circular(widget.redPointSize / 2.0)), 615 | ), 616 | child: redPointTextEmpty 617 | ? null 618 | : Text( 619 | widget.redPointText ?? "", 620 | style: widget.redPointTextStyle.copyWith( 621 | color: redPointTextColor, fontSize: redPointTextSize), 622 | ), 623 | ), 624 | ); 625 | return redPointPart; 626 | } 627 | } 628 | 629 | typedef void _OnChildSizeChange(Size size); 630 | 631 | class _MeasureSize extends StatefulWidget { 632 | final Widget child; 633 | final _OnChildSizeChange onChange; 634 | 635 | const _MeasureSize({ 636 | Key? key, 637 | required this.onChange, 638 | required this.child, 639 | }) : super(key: key); 640 | 641 | @override 642 | _MeasureSizeState createState() => _MeasureSizeState(); 643 | } 644 | 645 | class _MeasureSizeState extends State<_MeasureSize> { 646 | GlobalKey key = GlobalKey(); 647 | Size oldSize = Size.zero; 648 | 649 | @override 650 | Widget build(BuildContext context) { 651 | SchedulerBinding.instance?.addPostFrameCallback(postFrameCallback); 652 | return Container( 653 | key: key, 654 | child: widget.child, 655 | ); 656 | } 657 | 658 | void postFrameCallback(_) { 659 | var context = key.currentContext; 660 | if (context == null) return; 661 | 662 | var newSize = context.size; 663 | if (oldSize == newSize) return; 664 | 665 | oldSize = newSize ?? Size.zero; 666 | widget.onChange(newSize ?? Size.zero); 667 | } 668 | } 669 | 670 | class _Stack extends MultiChildRenderObjectWidget { 671 | /// Creates a stack layout widget. 672 | /// 673 | /// By default, the non-positioned children of the stack are aligned by their 674 | /// top left corners. 675 | _Stack({ 676 | Key? key, 677 | this.alignment = AlignmentDirectional.topStart, 678 | this.textDirection, 679 | this.fit = StackFit.loose, 680 | this.clipBehavior = Clip.hardEdge, 681 | List children = const [], 682 | }) : super(key: key, children: children); 683 | 684 | /// How to align the non-positioned and partially-positioned children in the 685 | /// stack. 686 | /// 687 | /// The non-positioned children are placed relative to each other such that 688 | /// the points determined by [alignment] are co-located. For example, if the 689 | /// [alignment] is [Alignment.topLeft], then the top left corner of 690 | /// each non-positioned child will be located at the same global coordinate. 691 | /// 692 | /// Partially-positioned children, those that do not specify an alignment in a 693 | /// particular axis (e.g. that have neither `top` nor `bottom` set), use the 694 | /// alignment to determine how they should be positioned in that 695 | /// under-specified axis. 696 | /// 697 | /// Defaults to [AlignmentDirectional.topStart]. 698 | /// 699 | /// See also: 700 | /// 701 | /// * [Alignment], a class with convenient constants typically used to 702 | /// specify an [AlignmentGeometry]. 703 | /// * [AlignmentDirectional], like [Alignment] for specifying alignments 704 | /// relative to text direction. 705 | final AlignmentGeometry alignment; 706 | 707 | /// The text direction with which to resolve [alignment]. 708 | /// 709 | /// Defaults to the ambient [Directionality]. 710 | final TextDirection? textDirection; 711 | 712 | /// How to size the non-positioned children in the stack. 713 | /// 714 | /// The constraints passed into the [Stack] from its parent are either 715 | /// loosened ([StackFit.loose]) or tightened to their biggest size 716 | /// ([StackFit.expand]). 717 | final StackFit fit; 718 | 719 | /// Whether overflowing children should be clipped. See [Overflow]. 720 | /// 721 | /// Some children in a stack might overflow its box. When this flag is set to 722 | /// [Overflow.clip], children cannot paint outside of the stack's box. 723 | final Clip clipBehavior; 724 | 725 | @override 726 | _RenderStack createRenderObject(BuildContext context) { 727 | return _RenderStack( 728 | alignment: alignment, 729 | textDirection: textDirection ?? Directionality.of(context), 730 | fit: fit, 731 | clipBehavior: clipBehavior, 732 | ); 733 | } 734 | 735 | @override 736 | void updateRenderObject(BuildContext context, _RenderStack renderObject) { 737 | renderObject 738 | ..alignment = alignment 739 | ..textDirection = textDirection ?? Directionality.of(context) 740 | ..fit = fit 741 | ..clipBehavior = clipBehavior; 742 | } 743 | 744 | @override 745 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 746 | super.debugFillProperties(properties); 747 | properties 748 | .add(DiagnosticsProperty('alignment', alignment)); 749 | properties.add(EnumProperty('textDirection', textDirection, 750 | defaultValue: null)); 751 | properties.add(EnumProperty('fit', fit)); 752 | properties.add(EnumProperty('clipBehavior', clipBehavior)); 753 | } 754 | } 755 | 756 | class _RenderStack extends RenderBox 757 | with 758 | ContainerRenderObjectMixin, 759 | RenderBoxContainerDefaultsMixin { 760 | /// Creates a stack render object. 761 | /// 762 | /// By default, the non-positioned children of the stack are aligned by their 763 | /// top left corners. 764 | _RenderStack({ 765 | List? children, 766 | AlignmentGeometry alignment = AlignmentDirectional.topStart, 767 | required TextDirection textDirection, 768 | StackFit fit = StackFit.loose, 769 | Clip clipBehavior = Clip.hardEdge, 770 | }) : assert(alignment != null), 771 | assert(fit != null), 772 | assert(clipBehavior != null), 773 | _alignment = alignment, 774 | _textDirection = textDirection, 775 | _fit = fit, 776 | _clipBehavior = clipBehavior { 777 | addAll(children); 778 | } 779 | 780 | bool _hasVisualOverflow = false; 781 | 782 | @override 783 | void setupParentData(RenderBox child) { 784 | if (child.parentData is! StackParentData) 785 | child.parentData = StackParentData(); 786 | } 787 | 788 | Alignment? _resolvedAlignment; 789 | 790 | void _resolve() { 791 | if (_resolvedAlignment != null) return; 792 | _resolvedAlignment = alignment.resolve(textDirection); 793 | } 794 | 795 | void _markNeedResolution() { 796 | _resolvedAlignment = null; 797 | markNeedsLayout(); 798 | } 799 | 800 | /// How to align the non-positioned or partially-positioned children in the 801 | /// stack. 802 | /// 803 | /// The non-positioned children are placed relative to each other such that 804 | /// the points determined by [alignment] are co-located. For example, if the 805 | /// [alignment] is [Alignment.topLeft], then the top left corner of 806 | /// each non-positioned child will be located at the same global coordinate. 807 | /// 808 | /// Partially-positioned children, those that do not specify an alignment in a 809 | /// particular axis (e.g. that have neither `top` nor `bottom` set), use the 810 | /// alignment to determine how they should be positioned in that 811 | /// under-specified axis. 812 | /// 813 | /// If this is set to an [AlignmentDirectional] object, then [textDirection] 814 | /// must not be null. 815 | AlignmentGeometry get alignment => _alignment; 816 | AlignmentGeometry _alignment; 817 | 818 | set alignment(AlignmentGeometry value) { 819 | assert(value != null); 820 | if (_alignment == value) return; 821 | _alignment = value; 822 | _markNeedResolution(); 823 | } 824 | 825 | /// The text direction with which to resolve [alignment]. 826 | /// 827 | /// This may be changed to null, but only after the [alignment] has been changed 828 | /// to a value that does not depend on the direction. 829 | TextDirection get textDirection => _textDirection; 830 | TextDirection _textDirection; 831 | 832 | set textDirection(TextDirection value) { 833 | if (_textDirection == value) return; 834 | _textDirection = value; 835 | _markNeedResolution(); 836 | } 837 | 838 | /// How to size the non-positioned children in the stack. 839 | /// 840 | /// The constraints passed into the [RenderStack] from its parent are either 841 | /// loosened ([StackFit.loose]) or tightened to their biggest size 842 | /// ([StackFit.expand]). 843 | StackFit get fit => _fit; 844 | StackFit _fit; 845 | 846 | set fit(StackFit value) { 847 | assert(value != null); 848 | if (_fit != value) { 849 | _fit = value; 850 | markNeedsLayout(); 851 | } 852 | } 853 | 854 | /// Whether overflowing children should be clipped. See [Overflow]. 855 | /// 856 | /// Some children in a stack might overflow its box. When this flag is set to 857 | /// [Overflow.clip], children cannot paint outside of the stack's box. 858 | Clip get clipBehavior => _clipBehavior; 859 | Clip _clipBehavior; 860 | 861 | set clipBehavior(Clip value) { 862 | assert(value != null); 863 | if (_clipBehavior != value) { 864 | _clipBehavior = value; 865 | markNeedsPaint(); 866 | } 867 | } 868 | 869 | /// Helper function for calculating the intrinsics metrics of a Stack. 870 | static double getIntrinsicDimension( 871 | RenderBox? firstChild, double mainChildSizeGetter(RenderBox child)) { 872 | double extent = 0.0; 873 | RenderBox? child = firstChild; 874 | while (child != null) { 875 | final StackParentData childParentData = 876 | child.parentData as StackParentData; 877 | if (!childParentData.isPositioned) 878 | extent = math.max(extent, mainChildSizeGetter(child)); 879 | assert(child.parentData == childParentData); 880 | child = childParentData.nextSibling; 881 | } 882 | return extent; 883 | } 884 | 885 | @override 886 | double computeMinIntrinsicWidth(double height) { 887 | return getIntrinsicDimension( 888 | firstChild, (RenderBox child) => child.getMinIntrinsicWidth(height)); 889 | } 890 | 891 | @override 892 | double computeMaxIntrinsicWidth(double height) { 893 | return getIntrinsicDimension( 894 | firstChild, (RenderBox child) => child.getMaxIntrinsicWidth(height)); 895 | } 896 | 897 | @override 898 | double computeMinIntrinsicHeight(double width) { 899 | return getIntrinsicDimension( 900 | firstChild, (RenderBox child) => child.getMinIntrinsicHeight(width)); 901 | } 902 | 903 | @override 904 | double computeMaxIntrinsicHeight(double width) { 905 | return getIntrinsicDimension( 906 | firstChild, (RenderBox child) => child.getMaxIntrinsicHeight(width)); 907 | } 908 | 909 | @override 910 | double? computeDistanceToActualBaseline(TextBaseline baseline) { 911 | return defaultComputeDistanceToHighestActualBaseline(baseline); 912 | } 913 | 914 | /// Lays out the positioned `child` according to `alignment` within a Stack of `size`. 915 | /// 916 | /// Returns true when the child has visual overflow. 917 | static bool layoutPositionedChild(RenderBox child, 918 | StackParentData childParentData, Size size, Alignment alignment) { 919 | assert(childParentData.isPositioned); 920 | assert(child.parentData == childParentData); 921 | 922 | bool hasVisualOverflow = false; 923 | BoxConstraints childConstraints = const BoxConstraints(); 924 | 925 | if (childParentData.left != null && childParentData.right != null) 926 | childConstraints = childConstraints.tighten( 927 | width: size.width - (childParentData.right ?? 0) - (childParentData.left ?? 0)); 928 | else if (childParentData.width != null) 929 | childConstraints = childConstraints.tighten(width: childParentData.width); 930 | 931 | if (childParentData.top != null && childParentData.bottom != null) 932 | childConstraints = childConstraints.tighten( 933 | height: size.height - (childParentData.bottom ?? 0) - 934 | (childParentData.top ?? 0)); 935 | else if (childParentData.height != null) 936 | childConstraints = 937 | childConstraints.tighten(height: childParentData.height); 938 | 939 | child.layout(childConstraints, parentUsesSize: true); 940 | 941 | double x; 942 | if (childParentData.left != null) { 943 | x = (childParentData.left ?? 0); 944 | } else if (childParentData.right != null) { 945 | x = size.width - (childParentData.right ?? 0) - child.size.width; 946 | } else { 947 | x = alignment.alongOffset(size - child.size as Offset).dx; 948 | } 949 | 950 | if (x < 0.0 || x + child.size.width > size.width) hasVisualOverflow = true; 951 | 952 | double y; 953 | if (childParentData.top != null) { 954 | y = (childParentData.top ?? 0); 955 | } else if (childParentData.bottom != null) { 956 | y = size.height - (childParentData.bottom ?? 0) - child.size.height; 957 | } else { 958 | y = alignment.alongOffset(size - child.size as Offset).dy; 959 | } 960 | 961 | if (y < 0.0 || y + child.size.height > size.height) 962 | hasVisualOverflow = true; 963 | 964 | childParentData.offset = Offset(x, y); 965 | 966 | return hasVisualOverflow; 967 | } 968 | 969 | @override 970 | void performLayout() { 971 | final BoxConstraints constraints = this.constraints; 972 | _resolve(); 973 | assert(_resolvedAlignment != null); 974 | _hasVisualOverflow = false; 975 | bool hasNonPositionedChildren = false; 976 | if (childCount == 0) { 977 | size = constraints.biggest; 978 | assert(size.isFinite); 979 | return; 980 | } 981 | 982 | double width = constraints.minWidth; 983 | double height = constraints.minHeight; 984 | 985 | BoxConstraints nonPositionedConstraints; 986 | assert(fit != null); 987 | switch (fit) { 988 | case StackFit.loose: 989 | nonPositionedConstraints = constraints.loosen(); 990 | break; 991 | case StackFit.expand: 992 | nonPositionedConstraints = BoxConstraints.tight(constraints.biggest); 993 | break; 994 | case StackFit.passthrough: 995 | nonPositionedConstraints = constraints; 996 | break; 997 | } 998 | assert(nonPositionedConstraints != null); 999 | 1000 | RenderBox? child = firstChild; 1001 | while (child != null) { 1002 | final StackParentData childParentData = 1003 | child.parentData as StackParentData; 1004 | 1005 | if (!childParentData.isPositioned) { 1006 | hasNonPositionedChildren = true; 1007 | 1008 | child.layout(nonPositionedConstraints, parentUsesSize: true); 1009 | 1010 | final Size childSize = child.size; 1011 | width = math.max(width, childSize.width); 1012 | height = math.max(height, childSize.height); 1013 | } 1014 | 1015 | child = childParentData.nextSibling; 1016 | } 1017 | 1018 | if (hasNonPositionedChildren) { 1019 | size = Size(width, height); 1020 | assert(size.width == constraints.constrainWidth(width)); 1021 | assert(size.height == constraints.constrainHeight(height)); 1022 | } else { 1023 | size = constraints.biggest; 1024 | } 1025 | 1026 | assert(size.isFinite); 1027 | 1028 | child = firstChild; 1029 | while (child != null) { 1030 | final StackParentData childParentData = 1031 | child.parentData as StackParentData; 1032 | 1033 | if (!childParentData.isPositioned) { 1034 | childParentData.offset = 1035 | _resolvedAlignment!.alongOffset(size - child.size as Offset); 1036 | } else { 1037 | _hasVisualOverflow = layoutPositionedChild( 1038 | child, childParentData, size, _resolvedAlignment!) || 1039 | _hasVisualOverflow; 1040 | } 1041 | 1042 | assert(child.parentData == childParentData); 1043 | child = childParentData.nextSibling; 1044 | } 1045 | } 1046 | 1047 | @override 1048 | bool hitTestChildren(BoxHitTestResult result, {required Offset position}) { 1049 | return defaultHitTestChildren(result, position: position); 1050 | } 1051 | 1052 | /// Override in subclasses to customize how the stack paints. 1053 | /// 1054 | /// By default, the stack uses [defaultPaint]. This function is called by 1055 | /// [paint] after potentially applying a clip to contain visual overflow. 1056 | @protected 1057 | void paintStack(PaintingContext context, Offset offset) { 1058 | defaultPaint(context, offset); 1059 | } 1060 | 1061 | @override 1062 | void paint(PaintingContext context, Offset offset) { 1063 | if (_clipBehavior != Clip.none && _hasVisualOverflow) { 1064 | context.pushClipRect( 1065 | needsCompositing, offset, Offset.zero & size, paintStack); 1066 | } else { 1067 | paintStack(context, offset); 1068 | } 1069 | } 1070 | 1071 | @override 1072 | Rect? describeApproximatePaintClip(RenderObject child) => 1073 | _hasVisualOverflow ? (Offset.zero & size) : null; 1074 | 1075 | @override 1076 | void debugFillProperties(DiagnosticPropertiesBuilder properties) { 1077 | super.debugFillProperties(properties); 1078 | properties 1079 | .add(DiagnosticsProperty('alignment', alignment)); 1080 | properties.add(EnumProperty('textDirection', textDirection)); 1081 | properties.add(EnumProperty('fit', fit)); 1082 | properties.add(EnumProperty('clipBehavior', clipBehavior)); 1083 | } 1084 | 1085 | bool hitTest(BoxHitTestResult result, {required Offset position}) { 1086 | if (hitTestChildren(result, position: position) || hitTestSelf(position)) { 1087 | result.add(BoxHitTestEntry(this, position)); 1088 | return true; 1089 | } 1090 | return false; 1091 | } 1092 | } 1093 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | dart pub publish --dry-run 4 | 5 | echo "" 6 | echo "\033[33m This package will publish after 10s. Please make sure everything is ok. \033[0m" 7 | 8 | count=1 9 | while(($count<10)) 10 | do 11 | echo "$count.." 12 | let "count++" 13 | sleep 1 14 | done 15 | 16 | dart pub publish --server=https://pub.dartlang.org 17 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: fsuper 2 | description: FSuper can help developers build complex views quickly and comfortably. 3 | version: 2.1.1 4 | author: CoorChice 5 | homepage: https://github.com/Fliggy-Mobile/fsuper 6 | 7 | environment: 8 | sdk: ">=2.12.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | fcontrol: ^2.0.0 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://dart.dev/tools/pub/pubspec 22 | 23 | # The following section is specific to Flutter. 24 | flutter: 25 | # This section identifies this Flutter project as a plugin project. 26 | # The androidPackage and pluginClass identifiers should not ordinarily 27 | # be modified. They are used by the tooling to maintain consistency when 28 | # adding or updating assets for this project. 29 | # plugin: 30 | # androidPackage: com.taobao.fapi.fsuper 31 | # pluginClass: FsuperPlugin 32 | 33 | # To add assets to your plugin package, add an assets section, like this: 34 | # assets: 35 | # - images/a_dot_burr.jpeg 36 | # - images/a_dot_ham.jpeg 37 | # 38 | # For details regarding assets in packages, see 39 | # https://flutter.dev/assets-and-images/#from-packages 40 | # 41 | # An image asset can refer to one or more resolution-specific "variants", see 42 | # https://flutter.dev/assets-and-images/#resolution-aware. 43 | 44 | # To add custom fonts to your plugin package, add a fonts section here, 45 | # in this "flutter" section. Each entry in this list should have a 46 | # "family" key with the font family name, and a "fonts" key with a 47 | # list giving the asset and other descriptors for the font. For 48 | # example: 49 | # fonts: 50 | # - family: Schyler 51 | # fonts: 52 | # - asset: fonts/Schyler-Regular.ttf 53 | # - asset: fonts/Schyler-Italic.ttf 54 | # style: italic 55 | # - family: Trajan Pro 56 | # fonts: 57 | # - asset: fonts/TrajanPro.ttf 58 | # - asset: fonts/TrajanPro_Bold.ttf 59 | # weight: 700 60 | # 61 | # For details regarding fonts in packages, see 62 | # https://flutter.dev/custom-fonts/#from-packages 63 | -------------------------------------------------------------------------------- /test/fsuper_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:flutter_test/flutter_test.dart'; 7 | import 'package:fsuper/fsuper.dart'; 8 | 9 | void main() { 10 | const MethodChannel channel = MethodChannel('fsuper'); 11 | 12 | TestWidgetsFlutterBinding.ensureInitialized(); 13 | 14 | setUp(() { 15 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 16 | return '42'; 17 | }); 18 | }); 19 | 20 | tearDown(() { 21 | channel.setMockMethodCallHandler(null); 22 | }); 23 | 24 | test('getPlatformVersion', () async { 25 | }); 26 | } 27 | --------------------------------------------------------------------------------