├── .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 └── fbutton.dart └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .idea/ 10 | 11 | .metadata 12 | 13 | *.iml 14 | 15 | pubspec.lock 16 | 17 | 18 | android/ 19 | ios/ 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.0.1 2 | 3 | - optimization 4 | 5 | ## 2.0.0 6 | 7 | - Delete the following parameters and use **style** instead: 8 | - `textColor` 9 | - `fontSize` 10 | - `fontStyle` 11 | - `fontHeight` 12 | - `textWeight` 13 | 14 | - Delete the following parameters: 15 | - `splashColor` 16 | - `focusNode` 17 | - `autofocus` 18 | 19 | - Delete the `effect` parameter 20 | 21 | - Delete `disabledTextColor` and use `disableStyle` instead 22 | 23 | - **FButtonCorner** changed to **FCorner** 24 | 25 | - **FButtonCornerStyle** changed to **FornerStyle** 26 | 27 | - Add the `activeMaskColor` parameter. The color of the mask when the button is pressed. Adjust the alpha of the color value to ensure that the view behind can be displayed. 28 | 29 | - Add **Hover** support. 30 | - The color in **hover** state can be configured through `hoverColor`. 31 | - `onHover` can help developers perceive **hover** state changes. 32 | 33 | - Add `onPressedDown` and `onPressedUp` and `onPressedCancel` to help developers to make corresponding changes when pressing, lifting or canceling the press. 34 | 35 | - Add `loadingWidget` parameter, so that developers can define their own loading style 36 | 37 | - Add Neumorphism style support. 38 | - Neumorphism style support can be turned on/off through the `isSupportNeumorphism` parameter 39 | - The `highlightShadowColor` parameter can configure the bright shadow color after enabling the Neumorphism style 40 | - The `lightOrientation` parameter can adjust the direction of the light source 41 | 42 | - Add `surfaceStyle` parameter to support the definition of surface style. 43 | - Flat 44 | - Convex 45 | - Concave 46 | 47 | 48 | ## 1.1.0 49 | 50 | - Add **style** parameter to configure text style. The following parameters will be removed in subsequent versions: 51 | - `textColor` 52 | - `fontSize` 53 | - `fontStyle` 54 | - `fontHeight` 55 | - `textWeight` 56 | 57 | - Add the **alignment** parameter to cancel the default centering 58 | 59 | - Remove default Padding 60 | 61 | - Remove the default background color 62 | 63 | - `onPress' is no longer a required parameter 64 | 65 | ## 1.0.4 66 | 67 | - add @immutable to fbutton.dart 68 | 69 | - minSDK 2.2 70 | 71 | ## 1.0.3 72 | 73 | - Remove a few bad attributes 74 | 75 | - Increase text configurability 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2020-present 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 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

FButton

8 | 9 | 10 |
11 | 12 |

From then on, developers only need to master one Button component, which is enough.

13 | 14 |

Support corners, borders, icons, special effects, loading mode, 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/fbutton/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 | - **Gradient effect** 64 | 65 | - Flexible **icon** support 66 | 67 | - Intimate **Loading** mode 68 | 69 | - Cool interaction **Special effects** 70 | 71 | - More sense of space **Shadow** 72 | 73 | - High-quality **Neumorphism** style 74 | 75 | # 🛠 Guide 76 | 77 | ## ⚙️ Parameters 78 | 79 | 80 | ### 🔩 Basic parameters 81 | 82 | |Param|Type|Necessary|Default|desc| 83 | |---|---|:---:|---|---| 84 | |onPressed|VoidCallback|true|null|Click callback. If null, FButton will enter an unavailable state| 85 | |onPressedDown|VoidCallback|false|null|Callback when pressed| 86 | |onPressedUp|VoidCallback|false|null|Callback when lifted| 87 | |onPressedCancel|VoidCallback|false|null|Callback when cancel is pressed| 88 | |height|double|false|null|height| 89 | |width|double|false|null|width| 90 | |style|TextStyle|false|null|text style| 91 | |disableStyle|TextStyle|false|null|Unavailable text style| 92 | |alignment|Alignment|false|null|alignment| 93 | |text|String|false|null|button text| 94 | |color|Color|false|null|Button color| 95 | |disabledColor|Color|false|null|Color when FButton is unavailable| 96 | |padding|EdgeInsetsGeometry|false|null|FButton internal spacing| 97 | |corner|FCorner|false|null|Configure corners of Widget| 98 | |cornerStyle|FCornerStyle|false|FCornerStyle.round|Configure the corner style of Widget. round-rounded corners, bevel-beveled| 99 | |strokeColor|Color|false|Colors.black|Border color| 100 | |strokeWidth|double|false|0|Border width. The border will appear when `strokeWidth > 0`| 101 | |gradient|Gradient|false|null|Configure gradient colors. Will override the `color`| 102 | |activeMaskColor|Color|否|Colors.transparent|The color of the mask when pressed| 103 | |surfaceStyle|FSurface|false|FSurface.Flat|Surface style. Default [FSurface.Flat]. See [FSurface] for details| 104 | 105 | ### 💫 Effect parameters 106 | |Param|Type|Necessary|Default|desc| 107 | |---|---|:---:|---|---| 108 | |clickEffect|bool|false|false|Whether to enable click effects| 109 | |hoverColor|Color|false|null|FButton color when hovering| 110 | |onHover|ValueChanged|false|null|Callback when the mouse enters/exits the component range| 111 | |highlightColor|Color|false|null|The color of the FButton when touched. `effect:true` required| 112 | 113 | 114 | ### 🔳 Shadow parameters 115 | |Param|Type|Necessary|Default|desc| 116 | |---|---|:---:|---|---| 117 | |shadowColor|Color|false|Colors.grey|Shadow color| 118 | |shadowOffset|Offset|false|Offset.zero|Shadow offset| 119 | |shadowBlur|double|false|1.0|Shadow blur degree, the larger the value, the larger the shadow range| 120 | 121 | ### 🖼 Icon & Loading parameters 122 | |Param|Type|Necessary|Default|desc| 123 | |---|---|:---:|---|---| 124 | |image|Widget|false|null|An icon can be configured for FButton| 125 | |imageMargin|double|false|6.0|Spacing between icon and text| 126 | |imageAlignment|ImageAlignment|false|ImageAlignment.left|Relative position of icon and text| 127 | |loading|bool|false|false|Whether to enter the Loading state| 128 | |loadingWidget|Widget|false|null|Loading widget in loading state. Will override the default Loading effect| 129 | |clickLoading|bool|false|false|Whether to enter Loading state after clicking FButton| 130 | |loadingColor|Color|false|null|Loading colors| 131 | |loadingStrokeWidth|double|false|4.0|Loading width| 132 | |hideTextOnLoading|bool|false|false|Whether to hide text in the loading state| 133 | |loadingText|String|false|null|Loading text| 134 | |loadingSize|double|false|12|Loading size| 135 | 136 | ### 🍭 Neumorphism Style 137 | 138 | |Param|Type|Necessary|Default|desc| 139 | |---|---|:---:|---|---| 140 | |isSupportNeumorphism|bool|false|false|Whether to support the Neumorphism style. Open this item [highlightColor] will be invalid| 141 | |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| 142 | |highlightShadowColor|Color|false|null|After the Neumorphism style is turned on, the bright shadow color| 143 | 144 | ## 📺 Demo 145 | 146 | ### 🔩 Basic Demo 147 | 148 | ![](https://gw.alicdn.com/tfs/TB1fUw0NoY1gK0jSZFCXXcwqXXa-720-298.png) 149 | 150 | ```dart 151 | // FButton #1 152 | FButton( 153 | height: 40, 154 | alignment: Alignment.center, 155 | text: "FButton #1", 156 | style: TextStyle(color: Colors.white), 157 | color: Color(0xffffab91), 158 | onPressed: () {}, 159 | ) 160 | 161 | // FButton #2 162 | FButton( 163 | padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), 164 | text: "FButton #2", 165 | style: TextStyle(color: Colors.white), 166 | color: Color(0xffffab91), 167 | corner: FCorner.all(6.0), 168 | ) 169 | 170 | // FButton #3 171 | FButton( 172 | padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), 173 | text: "FButton #3", 174 | style: TextStyle(color: Colors.white), 175 | disableStyle: TextStyle(color: Colors.black38), 176 | color: Color(0xffF8AD36), 177 | 178 | /// set disable Color 179 | disabledColor: Colors.grey[300], 180 | corner: FCorner.all(6.0), 181 | ) 182 | ``` 183 | By simply configuring `text` and` onPressed`, you can construct an available **FButton**. 184 | 185 | If `onPressed` is not set, **FButton** will be automatically recognized as not unavailable. At this time, ** FButton ** will have a default unavailable status style. 186 | 187 | You can also freely configure the style of **FButton** when it is not available via the `disabledXXX` attribute. 188 | 189 | ### 🎈 Corner & Stroke 190 | 191 | ![](https://gw.alicdn.com/tfs/TB1qFejbggP7K4jSZFqXXamhVXa-698-598.gif) 192 | 193 | 194 | 195 | ```dart 196 | // #1 197 | FButton( 198 | width: 130, 199 | text: "FButton #1", 200 | style: TextStyle(color: Colors.white), 201 | color: Color(0xffFF7043), 202 | onPressed: () {}, 203 | clickEffect: true, 204 | 205 | /// 配置边角大小 206 | /// 207 | /// set corner size 208 | corner: FCorner.all(25), 209 | ), 210 | 211 | // #2 212 | FButton( 213 | width: 130, 214 | text: "FButton #2", 215 | style: TextStyle(color: Colors.white), 216 | color: Color(0xffFFA726), 217 | onPressed: () {}, 218 | clickEffect: true, 219 | corner: FCorner( 220 | leftBottomCorner: 40, 221 | leftTopCorner: 6, 222 | rightTopCorner: 40, 223 | rightBottomCorner: 6, 224 | ), 225 | ), 226 | 227 | // #3 228 | FButton( 229 | width: 130, 230 | text: "FButton #3", 231 | style: TextStyle(color: Colors.white), 232 | color: Color(0xffFFc900), 233 | onPressed: () {}, 234 | clickEffect: true, 235 | corner: FCorner(leftTopCorner: 10), 236 | 237 | /// 设置边角风格 238 | /// 239 | /// set corner style 240 | cornerStyle: FCornerStyle.bevel, 241 | strokeWidth: 0.5, 242 | strokeColor: Color(0xffF9A825), 243 | ), 244 | 245 | // #4 246 | FButton( 247 | width: 130, 248 | padding: EdgeInsets.fromLTRB(6, 16, 30, 16), 249 | text: "FButton #4", 250 | style: TextStyle(color: Colors.white), 251 | color: Color(0xff00B0FF), 252 | onPressed: () {}, 253 | clickEffect: true, 254 | corner: FCorner( 255 | rightTopCorner: 25, 256 | rightBottomCorner: 25), 257 | cornerStyle: FCornerStyle.bevel, 258 | strokeWidth: 0.5, 259 | strokeColor: Color(0xff000000), 260 | ), 261 | ``` 262 | 263 | You can add rounded corners to **FButton** via the `corner` property. You can even control each fillet individually。 264 | 265 | By default, the corners of **FButton** are rounded. By setting `cornerStyle: FCornerStyle.bevel`, you can get a bevel effect. 266 | 267 | **FButton** supports control borders, provided that `strokeWidth> 0` can get the effect 🥳. 268 | 269 | ### 🌈 Gradient 270 | 271 | 272 | ![](https://gw.alicdn.com/tfs/TB1YgA.NoT1gK0jSZFrXXcNCXXa-486-518.png) 273 | 274 | ```dart 275 | 276 | FButton( 277 | width: 100, 278 | height: 60, 279 | text: "#1", 280 | style: TextStyle(color: Colors.white), 281 | color: Color(0xffFFc900), 282 | 283 | /// 配置渐变色 284 | /// 285 | /// set gradient 286 | gradient: LinearGradient(colors: [ 287 | Color(0xff00B0FF), 288 | Color(0xffFFc900), 289 | ]), 290 | onPressed: () {}, 291 | clickEffect: true, 292 | corner: FCorner.all(8), 293 | ) 294 | ``` 295 | 296 | Through the `gradient` attribute, you can build **FButton** with gradient colors. You can freely build many types of gradient colors. 297 | 298 | ### 🍭 Icon 299 | 300 | ![](https://gw.alicdn.com/tfs/TB1YBUVNoz1gK0jSZLeXXb9kVXa-528-302.png) 301 | 302 | ```dart 303 | FButton( 304 | width: 88, 305 | height: 38, 306 | padding: EdgeInsets.all(0), 307 | text: "Back", 308 | style: TextStyle(color: Colors.white), 309 | color: Color(0xffffc900), 310 | onPressed: () { 311 | toast(context, "Back!"); 312 | }, 313 | clickEffect: true, 314 | corner: FCorner( 315 | leftTopCorner: 25, 316 | leftBottomCorner: 25,), 317 | 318 | /// 配置图标 319 | /// 320 | /// set icon 321 | image: Icon( 322 | Icons.arrow_back_ios, 323 | color: Colors.white, 324 | size: 12, 325 | ), 326 | 327 | /// 配置图标与文字的间距 328 | /// 329 | /// Configure the spacing between icon and text 330 | imageMargin: 8, 331 | ), 332 | 333 | FButton( 334 | onPressed: () {}, 335 | image: Icon( 336 | Icons.print, 337 | color: Colors.grey, 338 | ), 339 | imageMargin: 8, 340 | 341 | /// 配置图标与文字相对位置 342 | /// 343 | /// Configure the relative position of icons and text 344 | imageAlignment: ImageAlignment.top, 345 | text: "Print", 346 | style: TextStyle(color: textColor), 347 | color: Colors.transparent, 348 | ), 349 | ``` 350 | 351 | The `image` property can set an image for **FButton** and you can adjust the position of the image relative to the text, through` imageAlignment`. 352 | 353 | If the button does not need a background, just set `color: Colors.transparent`. 354 | 355 | 356 | ### 🔥 Effect 357 | 358 | ![](https://gw.alicdn.com/tfs/TB1IKhaNBr0gK0jSZFnXXbRRXXa-698-178.gif) 359 | 360 | ```dart 361 | 362 | FButton( 363 | width: 200, 364 | text: "Try Me!", 365 | style: TextStyle(color: textColor), 366 | color: Color(0xffffc900), 367 | onPressed: () {}, 368 | clickEffect: true, 369 | corner: FCorner.all(9), 370 | 371 | /// 配置按下时颜色 372 | /// 373 | /// set pressed color 374 | highlightColor: Color(0xffE65100).withOpacity(0.20), 375 | 376 | /// 配置 hover 状态时颜色 377 | /// 378 | /// set hover color 379 | hoverColor: Colors.redAccent.withOpacity(0.16), 380 | ), 381 | ``` 382 | 383 | The highlight color of **FButton** can be configured through the `highlightColor` property。 384 | 385 | `hoverColor` can configure the color when the mouse moves to the range of **FButton**, which will be used during Web development. 386 | 387 | ### 🔆 Loading 388 | 389 | ![](https://gw.alicdn.com/tfs/TB1dbvTXODsXe8jSZR0XXXK6FXa-698-698.gif) 390 | 391 | ```dart 392 | FButton( 393 | text: "Click top loading", 394 | style: TextStyle(color: textColor), 395 | color: Color(0xffffc900), 396 | ... 397 | 398 | /// 配置 loading 大小 399 | /// 400 | /// set loading size 401 | loadingSize: 15, 402 | 403 | /// 配置 loading 与文本的间距 404 | /// 405 | // Configure the spacing between loading and text 406 | imageMargin: 6, 407 | 408 | /// 配置 loading 的宽 409 | /// 410 | /// set loading width 411 | loadingStrokeWidth: 2, 412 | 413 | /// 是否支持点击自动开始 loading 414 | /// 415 | /// Whether to support automatic loading by clicking 416 | clickLoading: true, 417 | 418 | /// 配置 loading 的颜色 419 | /// 420 | /// set loading color 421 | loadingColor: Colors.white, 422 | 423 | /// 配置 loading 状态时的文本 424 | /// 425 | /// set loading text 426 | loadingText: "Loading...", 427 | 428 | /// 配置 loading 与文本的相对位置 429 | /// 430 | /// Configure the relative position of loading and text 431 | imageAlignment: ImageAlignment.top, 432 | ), 433 | 434 | // #2 435 | FButton( 436 | width: 170, 437 | height: 70, 438 | text: "Click to loading", 439 | style: TextStyle(color: textColor), 440 | color: Color(0xffffc900), 441 | onPressed: () { }, 442 | ... 443 | imageMargin: 8, 444 | loadingSize: 15, 445 | loadingStrokeWidth: 2, 446 | clickLoading: true, 447 | loadingColor: Colors.white, 448 | loadingText: "Loading...", 449 | 450 | /// loading 时隐藏文本 451 | /// 452 | /// Hide text when loading 453 | hideTextOnLoading: true, 454 | ) 455 | 456 | 457 | FButton( 458 | width: 170, 459 | height: 70, 460 | alignment: Alignment.center, 461 | text: "Click to loading", 462 | style: TextStyle(color: Colors.white), 463 | color: Color(0xff90caf9), 464 | ... 465 | imageMargin: 8, 466 | clickLoading: true, 467 | hideTextOnLoading: true, 468 | 469 | /// 配置自定义 loading 样式 470 | /// 471 | /// Configure custom loading style 472 | loadingWidget: CupertinoActivityIndicator(), 473 | ), 474 | ``` 475 | 476 | Through the `loading` attribute, you can configure **Loading** effects for ** FButton **. 477 | 478 | When **FButton** is in **Loading** state, **FButton** will enter an unavailable state, onPress will no longer be triggered, and unavailable styles will also be applied. 479 | 480 | At the same time `loadingText` will overwrite` text` if it is not **null**. 481 | 482 | The click start **Loading** effect can be achieved through the `clickLoading` attribute. 483 | 484 | The position of `loading` will be affected by the` imageAlignment` attribute. 485 | 486 | When `hideTextOnLoading: true`, if **FButton** is in` loading` state, its text will be hidden. 487 | 488 | Through `loadingWidget`, developers can set completely customized loading styles. 489 | 490 | ## Shadow 491 | 492 | ![](https://gw.alicdn.com/tfs/TB11OxcNBr0gK0jSZFnXXbRRXXa-698-368.gif) 493 | 494 | ```dart 495 | 496 | FButton( 497 | width: 200, 498 | text: "Shadow", 499 | textColor: Colors.white, 500 | color: Color(0xffffc900), 501 | onPressed: () {}, 502 | clickEffect: true, 503 | corner: FCorner.all(28), 504 | 505 | /// 配置阴影颜色 506 | /// 507 | /// set shadow color 508 | shadowColor: Colors.black87, 509 | 510 | /// 设置组件高斯与阴影形状卷积的标准偏差。 511 | /// 512 | /// Sets the standard deviation of the component's Gaussian convolution with the shadow shape. 513 | shadowBlur: _shadowBlur, 514 | ), 515 | ``` 516 | 517 | **FButton** allows you to configure the color, size, and position of the shadow. 518 | 519 | 520 | ### 🍭 Neumorphism Style 521 | 522 | ![](https://gw.alicdn.com/tfs/TB18CN4dTM11u4jSZPxXXahcXXa-832-644.gif) 523 | 524 | ```dart 525 | FButton( 526 | 527 | /// 开启 Neumorphism 支持 528 | /// 529 | /// Turn on Neumorphism support 530 | isSupportNeumorphism: true, 531 | 532 | /// 配置光源方向 533 | /// 534 | /// Configure light source direction 535 | lightOrientation: lightOrientation, 536 | 537 | /// 配置亮部阴影 538 | /// 539 | /// Configure highlight shadow 540 | highlightShadowColor: Colors.white, 541 | 542 | /// 配置暗部阴影 543 | /// 544 | /// Configure dark shadows 545 | shadowColor: mainShadowColor, 546 | strokeColor: mainBackgroundColor, 547 | strokeWidth: 3.0, 548 | width: 190, 549 | height: 60, 550 | text: "FWidget", 551 | style: TextStyle( 552 | color: mainTextTitleColor, fontSize: neumorphismSize_2_2), 553 | alignment: Alignment.center, 554 | color: mainBackgroundColor, 555 | ... 556 | ) 557 | ``` 558 | **FButton** brings an incredible, ultra-high texture **Neumorphism** style to developers. 559 | 560 | Developers only need to configure the `isSupportNeumorphism` parameter to enable and disable the **Neumorphism** style. 561 | 562 | If you want to adjust the style of **Neumorphism**, you can make subtle adjustments through several attributes related to Shadow, among which: 563 | 564 | - shadowColor: configure the shadow of the shadow 565 | 566 | - highlightShadowColor: configure highlight shadow 567 | 568 | **FButton** also provides `lightOrientation` parameters, and even allows developers to adjust the care angle, and has obtained different **Neumorphism** effects. 569 | 570 | # 😃 How to use? 571 | 572 | Add dependencies in the project `pubspec.yaml` file: 573 | 574 | ## 🌐 pub dependency 575 | 576 | ``` 577 | dependencies: 578 | fbutton: ^ 579 | ``` 580 | 581 | > ⚠️ Attention,please go to [**pub**] (https://pub.dev/packages/fbutton) to get the latest version number of **FButton** 582 | 583 | ## 🖥 git dependencies 584 | 585 | ``` 586 | dependencies: 587 | fbutton: 588 | git: 589 | url: 'git@github.com:Fliggy-Mobile/fbutton.git' 590 | ref: '' 591 | ``` 592 | 593 | 594 | > ⚠️ Attention,please refer to [**FButton**] (https://github.com/Fliggy-Mobile/fbutton) official project for branch number or tag. 595 | 596 | 597 | # 💡 License 598 | 599 | ``` 600 | Copyright 2020-present Fliggy Android Team . 601 | 602 | Licensed under the Apache License, Version 2.0 (the "License"); 603 | you may not use this file except in compliance with the License. 604 | You may obtain a copy of the License at following link. 605 | 606 | http://www.apache.org/licenses/LICENSE-2.0 607 | 608 | Unless required by applicable law or agreed to in writing, software 609 | distributed under the License is distributed on an "AS IS" BASIS, 610 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 611 | See the License for the specific language governing permissions and 612 | limitations under the License. 613 | 614 | ``` 615 | 616 | 617 | ### Like it? Please cast your [**Star**](https://github.com/Fliggy-Mobile/fbutton) 🥰 ! 618 | 619 | 620 | --- 621 | 622 | # How to run Demo project? 623 | 624 | 1. **clone** project to local 625 | 626 | 2. Enter the project `example` directory and run the following command 627 | 628 | ``` 629 | flutter create . 630 | ``` 631 | 632 | 3. Run the demo in `example` 633 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 | 5 |

6 | 7 |

FButton

8 | 9 | 10 |
11 | 12 |

从此开发者只用掌握一种 Button 组件,就够了。

13 | 14 |

支持圆角、边框、图标、特效、Loading 模式、高质感的 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 | **[English](https://github.com/Fliggy-Mobile/fbutton) | 简体中文** 54 | 55 | > 感觉还不错?请投出您的 **Star** 吧 🥰 ! 56 | 57 | # ✨ 特性 58 | 59 | - 丰富的 **边角** 效果 60 | 61 | - 精美的 **边框** 装饰 62 | 63 | - **渐变效果** 也不在话下 64 | 65 | - 灵活的 **图标** 支持 66 | 67 | - 贴心的 **Loading** 模式 68 | 69 | - 炫酷的交互 **特效** 70 | 71 | - 更具空间感的 **阴影** 72 | 73 | - 高质感的 **Neumorphism** 风格 74 | 75 | 76 | # 🛠 使用指南 77 | 78 | ## ⚙️ 参数 79 | 80 | 81 | ### 🔩 基础参数 82 | 83 | |参数|类型|必要|默认值|说明| 84 | |---|---|:---:|---|---| 85 | |onPressed|VoidCallback|是|null|点击回调。如果为 null,FButton 会进入不可用状态| 86 | |onPressedDown|VoidCallback|false|null|按下时会回调| 87 | |onPressedUp|VoidCallback|false|null|抬起时会回调| 88 | |onPressedCancel|VoidCallback|false|null|按下取消时会回调| 89 | |height|double|否|null|高度| 90 | |width|double|否|null|宽度| 91 | |style|TextStyle|false|null|文本样式| 92 | |disableStyle|TextStyle|false|null|不可用文本样式| 93 | |alignment|Alignment|false|null|对齐方式| 94 | |text|String|否|null|按钮文本| 95 | |color|Color|否|null|按钮颜色| 96 | |disabledColor|Color|否|null|FButton 不可用状态时的颜色| 97 | |padding|EdgeInsetsGeometry|否|null|FButton 内间距| 98 | |corner|FCorner|否|null|配置 Widget 的边角| 99 | |cornerStyle|FCornerStyle|否|FCornerStyle.round|配置 Widget 的边角样式。round-圆角,bevel-斜切| 100 | |strokeColor|Color|否|Colors.black|边框颜色| 101 | |strokeWidth|double|否|0|边框宽度。当 strokeWidth>0 时边框就会出现| 102 | |gradient|Gradient|否|null|配置渐变色。会覆盖 color 属性| 103 | |activeMaskColor|Color|否|Colors.transparent|按下时的蒙层颜色| 104 | |surfaceStyle|FSurface|false|FSurface.Flat|表面的风格。默认 [FSurface.Flat]。详见 [FSurface]| 105 | 106 | ### 💫 Effect 参数 107 | |参数|类型|必要|默认值|说明| 108 | |---|---|:---:|---|---| 109 | |clickEffect|bool|否|false|是否启用点击特效| 110 | |hoverColor|Color|否|null|鼠标悬停状态时 FButton 的颜色| 111 | |onHover|ValueChanged|false|null|鼠标进入/退出组件范围时会回调| 112 | |highlightColor|Color|否|null|触摸时 FButton 的颜色。需要 effect=true| 113 | 114 | 115 | ### 🔳 阴影参数 116 | |参数|类型|必要|默认值|说明| 117 | |---|---|:---:|---|---| 118 | |shadowColor|Color|否|Colors.grey|阴影颜色| 119 | |shadowOffset|Offset|否|Offset.zero|阴影偏移| 120 | |shadowBlur|double|否|1.0|阴影模糊程度,值越大,阴影范围越大| 121 | 122 | ### 🖼 图标和 Loading 参数 123 | |参数|类型|必要|默认值|说明| 124 | |---|---|:---:|---|---| 125 | |image|Widget|否|null|可为 FButton 配置一个图标| 126 | |imageMargin|double|否|6.0|图标与文本的间距| 127 | |imageAlignment|ImageAlignment|否|ImageAlignment.left|图标与文本的相对位置| 128 | |loading|bool|否|false|是否进入 Loading 状态| 129 | |loadingWidget|Widget|false|null|loading 状态时的 Loading 小部件。会覆盖默认的 Loading 效果| 130 | |clickLoading|bool|否|false|是否在点击 FButton 后进入 Loading 状态| 131 | |loadingColor|Color|否|null|Loading 的颜色| 132 | |loadingStrokeWidth|double|否|4.0|Loading 的宽度| 133 | |hideTextOnLoading|bool|否|false|Loading 状态下是否隐藏文本| 134 | |loadingText|String|否|null|Loading 状态下的文本| 135 | |loadingSize|double|否|12|Loading 的大小| 136 | 137 | ### 🍭 Neumorphism 风格 138 | 139 | |参数|类型|必要|默认值|说明| 140 | |---|---|:---:|---|---| 141 | |isSupportNeumorphism|bool|false|false|是否支持 Neumorphism 风格。开启该项 [highlightColor] 将会失效| 142 | |lightOrientation|FLightOrientation|false|FLightOrientation.LeftTop|当 [isSupportNeumorphism] 为 true 时有效。光源方向,分为左上、左下、右上、右下四个方向。用来控制光源照射方向,会影响高亮方向和阴影方向| 143 | |highlightShadowColor|Color|false|null|开启 Neumorphism 风格后的,亮部阴影颜色| 144 | 145 | ## 📺 使用示例 146 | 147 | ### 🔩 基本使用 148 | 149 | ![](https://gw.alicdn.com/tfs/TB1fUw0NoY1gK0jSZFCXXcwqXXa-720-298.png) 150 | 151 | ```dart 152 | 153 | // FButton #1 154 | FButton( 155 | height: 40, 156 | alignment: Alignment.center, 157 | text: "FButton #1", 158 | style: TextStyle(color: Colors.white), 159 | color: Color(0xffffab91), 160 | onPressed: () {}, 161 | ) 162 | 163 | // FButton #2 164 | FButton( 165 | padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), 166 | text: "FButton #2", 167 | style: TextStyle(color: Colors.white), 168 | color: Color(0xffffab91), 169 | corner: FCorner.all(6.0), 170 | ) 171 | 172 | // FButton #3 173 | FButton( 174 | padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), 175 | text: "FButton #3", 176 | style: TextStyle(color: Colors.white), 177 | disableStyle: TextStyle(color: Colors.black38), 178 | color: Color(0xffF8AD36), 179 | 180 | /// 配置不可用颜色 181 | disabledColor: Colors.grey[300], 182 | corner: FCorner.all(6.0), 183 | ) 184 | ``` 185 | 通过简单的配置 `text` 以及 `onPressed`,即可构造一个可用 **FButton**。 186 | 187 | 如果没有设置 `onPressed`,**FButton** 会自动被识别 **未不可用状态** 。此时 **FButton** 会有一个默认的不可用状态样式。 188 | 189 | 你也可以自由的配置 **FButton** 不可用状态下的样式通过 `disabledXXX` 属性。 190 | 191 | ### 🎈 边角 & 边框 192 | 193 | ![](https://gw.alicdn.com/tfs/TB1qFejbggP7K4jSZFqXXamhVXa-698-598.gif) 194 | 195 | 196 | ```dart 197 | // #1 198 | FButton( 199 | width: 130, 200 | text: "FButton #1", 201 | style: TextStyle(color: Colors.white), 202 | color: Color(0xffFF7043), 203 | onPressed: () {}, 204 | clickEffect: true, 205 | 206 | /// 配置边角大小 207 | /// 208 | /// set corner size 209 | corner: FCorner.all(25), 210 | ), 211 | 212 | // #2 213 | FButton( 214 | width: 130, 215 | text: "FButton #2", 216 | style: TextStyle(color: Colors.white), 217 | color: Color(0xffFFA726), 218 | onPressed: () {}, 219 | clickEffect: true, 220 | corner: FCorner( 221 | leftBottomCorner: 40, 222 | leftTopCorner: 6, 223 | rightTopCorner: 40, 224 | rightBottomCorner: 6, 225 | ), 226 | ), 227 | 228 | // #3 229 | FButton( 230 | width: 130, 231 | text: "FButton #3", 232 | style: TextStyle(color: Colors.white), 233 | color: Color(0xffFFc900), 234 | onPressed: () {}, 235 | clickEffect: true, 236 | corner: FCorner(leftTopCorner: 10), 237 | 238 | /// 设置边角风格 239 | /// 240 | /// set corner style 241 | cornerStyle: FCornerStyle.bevel, 242 | strokeWidth: 0.5, 243 | strokeColor: Color(0xffF9A825), 244 | ), 245 | 246 | // #4 247 | FButton( 248 | width: 130, 249 | padding: EdgeInsets.fromLTRB(6, 16, 30, 16), 250 | text: "FButton #4", 251 | style: TextStyle(color: Colors.white), 252 | color: Color(0xff00B0FF), 253 | onPressed: () {}, 254 | clickEffect: true, 255 | corner: FCorner( 256 | rightTopCorner: 25, 257 | rightBottomCorner: 25), 258 | cornerStyle: FCornerStyle.bevel, 259 | strokeWidth: 0.5, 260 | strokeColor: Color(0xff000000), 261 | ), 262 | ``` 263 | 264 | 你可以为 **FButton** 添加圆角,通过 `corner` 属性。甚至,你可以单独控制每一个圆角。 265 | 266 | 默认情况下,**FButton** 的边角为圆角。通过设置 `cornerStyle: FCornerStyle.bevel`,可以获得斜角效果。 267 | 268 | **FButton** 支持控件边框,前提是 `strokeWidth > 0` 即可获得效果 🥳。 269 | 270 | ### 🌈 渐变色 271 | 272 | ![](https://gw.alicdn.com/tfs/TB1YgA.NoT1gK0jSZFrXXcNCXXa-486-518.png) 273 | 274 | ```dart 275 | 276 | FButton( 277 | width: 100, 278 | height: 60, 279 | text: "#1", 280 | style: TextStyle(color: Colors.white), 281 | color: Color(0xffFFc900), 282 | 283 | /// 配置渐变色 284 | /// 285 | /// set gradient 286 | gradient: LinearGradient(colors: [ 287 | Color(0xff00B0FF), 288 | Color(0xffFFc900), 289 | ]), 290 | onPressed: () {}, 291 | clickEffect: true, 292 | corner: FCorner.all(8), 293 | ) 294 | ``` 295 | 296 | 通过 `gradient` 属性,可以构建带有渐变色的 **FButton** 你可以自由构建多种类型的渐变色。 297 | 298 | ### 🍭 图标 299 | 300 | ![](https://gw.alicdn.com/tfs/TB1YBUVNoz1gK0jSZLeXXb9kVXa-528-302.png) 301 | 302 | ```dart 303 | 304 | FButton( 305 | width: 88, 306 | height: 38, 307 | padding: EdgeInsets.all(0), 308 | text: "Back", 309 | style: TextStyle(color: Colors.white), 310 | color: Color(0xffffc900), 311 | onPressed: () { 312 | toast(context, "Back!"); 313 | }, 314 | clickEffect: true, 315 | corner: FCorner( 316 | leftTopCorner: 25, 317 | leftBottomCorner: 25,), 318 | 319 | /// 配置图标 320 | /// 321 | /// set icon 322 | image: Icon( 323 | Icons.arrow_back_ios, 324 | color: Colors.white, 325 | size: 12, 326 | ), 327 | 328 | /// 配置图标与文字的间距 329 | /// 330 | /// Configure the spacing between icon and text 331 | imageMargin: 8, 332 | ), 333 | 334 | FButton( 335 | onPressed: () {}, 336 | image: Icon( 337 | Icons.print, 338 | color: Colors.grey, 339 | ), 340 | imageMargin: 8, 341 | 342 | /// 配置图标与文字相对位置 343 | /// 344 | /// Configure the relative position of icons and text 345 | imageAlignment: ImageAlignment.top, 346 | text: "Print", 347 | style: TextStyle(color: textColor), 348 | color: Colors.transparent, 349 | ), 350 | 351 | ``` 352 | 353 | `image` 属性能够为 **FButton** 设置一个图片而且你能够调整图片相对与文本的位置,通过 `imageAlignment`。 354 | 355 | 如果按钮不需要背景,设置 `color: Colors.transparent` 即可。 356 | 357 | 358 | ### 🔥 特效 359 | 360 | ![](https://gw.alicdn.com/tfs/TB1IKhaNBr0gK0jSZFnXXbRRXXa-698-178.gif) 361 | 362 | ```dart 363 | 364 | FButton( 365 | width: 200, 366 | text: "Try Me!", 367 | style: TextStyle(color: textColor), 368 | color: Color(0xffffc900), 369 | onPressed: () {}, 370 | clickEffect: true, 371 | corner: FCorner.all(9), 372 | 373 | /// 配置按下时颜色 374 | /// 375 | /// set pressed color 376 | highlightColor: Color(0xffE65100).withOpacity(0.20), 377 | 378 | /// 配置 hover 状态时颜色 379 | /// 380 | /// set hover color 381 | hoverColor: Colors.redAccent.withOpacity(0.16), 382 | ), 383 | ``` 384 | 385 | 通过 `highlightColor` 属性可以配置 **FButton** 的按压时的高亮颜色 386 | 387 | `hoverColor` 可配置鼠标移到 **FButton** 范围中时的颜色,这在 Web 开发时会被用到。 388 | 389 | ### 🔆 Loading 390 | 391 | ![](https://gw.alicdn.com/tfs/TB1dbvTXODsXe8jSZR0XXXK6FXa-698-698.gif) 392 | 393 | ```dart 394 | FButton( 395 | text: "Click top loading", 396 | style: TextStyle(color: textColor), 397 | color: Color(0xffffc900), 398 | ... 399 | 400 | /// 配置 loading 大小 401 | /// 402 | /// set loading size 403 | loadingSize: 15, 404 | 405 | /// 配置 loading 与文本的间距 406 | /// 407 | // Configure the spacing between loading and text 408 | imageMargin: 6, 409 | 410 | /// 配置 loading 的宽 411 | /// 412 | /// set loading width 413 | loadingStrokeWidth: 2, 414 | 415 | /// 是否支持点击自动开始 loading 416 | /// 417 | /// Whether to support automatic loading by clicking 418 | clickLoading: true, 419 | 420 | /// 配置 loading 的颜色 421 | /// 422 | /// set loading color 423 | loadingColor: Colors.white, 424 | 425 | /// 配置 loading 状态时的文本 426 | /// 427 | /// set loading text 428 | loadingText: "Loading...", 429 | 430 | /// 配置 loading 与文本的相对位置 431 | /// 432 | /// Configure the relative position of loading and text 433 | imageAlignment: ImageAlignment.top, 434 | ), 435 | 436 | // #2 437 | FButton( 438 | width: 170, 439 | height: 70, 440 | text: "Click to loading", 441 | style: TextStyle(color: textColor), 442 | color: Color(0xffffc900), 443 | onPressed: () { }, 444 | ... 445 | imageMargin: 8, 446 | loadingSize: 15, 447 | loadingStrokeWidth: 2, 448 | clickLoading: true, 449 | loadingColor: Colors.white, 450 | loadingText: "Loading...", 451 | 452 | /// loading 时隐藏文本 453 | /// 454 | /// Hide text when loading 455 | hideTextOnLoading: true, 456 | ) 457 | 458 | 459 | FButton( 460 | width: 170, 461 | height: 70, 462 | alignment: Alignment.center, 463 | text: "Click to loading", 464 | style: TextStyle(color: Colors.white), 465 | color: Color(0xff90caf9), 466 | ... 467 | imageMargin: 8, 468 | clickLoading: true, 469 | hideTextOnLoading: true, 470 | 471 | /// 配置自定义 loading 样式 472 | /// 473 | /// Configure custom loading style 474 | loadingWidget: CupertinoActivityIndicator(), 475 | ), 476 | ``` 477 | 478 | 通过 `loading` 属性,可为 **FButton** 配置 **Loading** 效果。 479 | 480 | 当 **FButton** 处于 **Loading** 状态时,**FButton** 将会进入不可用状态,`onPress` 将不会再被触发,不可用样式也将被应用。 481 | 482 | 同时 `loadingText` 将会覆盖 `text`,如果它不为 **null** 的话。 483 | 484 | 通过 `clickLoading` 属性可以实现点击开始 **Loading** 的效果。 485 | 486 | 其中 `loading` 的位置会受到 `imageAlignment` 属性的影响。 487 | 488 | 当 `hideTextOnLoading: true` 时,如果 **FButton** 处于 `loading` 状态,那么其文本将会被隐藏起来。 489 | 490 | 通过 `loadingWidget`,开发者可以设置完全自定义的 loading 样式。 491 | 492 | ## 阴影 493 | 494 | ![](https://gw.alicdn.com/tfs/TB11OxcNBr0gK0jSZFnXXbRRXXa-698-368.gif) 495 | 496 | ```dart 497 | 498 | FButton( 499 | width: 200, 500 | text: "Shadow", 501 | textColor: Colors.white, 502 | color: Color(0xffffc900), 503 | onPressed: () {}, 504 | clickEffect: true, 505 | corner: FCorner.all(28), 506 | 507 | /// 配置阴影颜色 508 | /// 509 | /// set shadow color 510 | shadowColor: Colors.black87, 511 | 512 | /// 设置组件高斯与阴影形状卷积的标准偏差。 513 | /// 514 | /// Sets the standard deviation of the component's Gaussian convolution with the shadow shape. 515 | shadowBlur: _shadowBlur, 516 | ), 517 | ``` 518 | 519 | **FButton** 允许配置阴影的颜色、大小、以及位置。 520 | 521 | ### 🍭 Neumorphism 风格 522 | 523 | ![](https://gw.alicdn.com/tfs/TB18CN4dTM11u4jSZPxXXahcXXa-832-644.gif) 524 | 525 | ```dart 526 | FButton( 527 | 528 | /// 开启 Neumorphism 支持 529 | /// 530 | /// Turn on Neumorphism support 531 | isSupportNeumorphism: true, 532 | 533 | /// 配置光源方向 534 | /// 535 | /// Configure light source direction 536 | lightOrientation: lightOrientation, 537 | 538 | /// 配置亮部阴影 539 | /// 540 | /// Configure highlight shadow 541 | highlightShadowColor: Colors.white, 542 | 543 | /// 配置暗部阴影 544 | /// 545 | /// Configure dark shadows 546 | shadowColor: mainShadowColor, 547 | strokeColor: mainBackgroundColor, 548 | strokeWidth: 3.0, 549 | width: 190, 550 | height: 60, 551 | text: "FWidget", 552 | style: TextStyle( 553 | color: mainTextTitleColor, fontSize: neumorphismSize_2_2), 554 | alignment: Alignment.center, 555 | color: mainBackgroundColor, 556 | ... 557 | ) 558 | ``` 559 | 560 | **FButton** 为开发者带来了不可思议的,超高质感的 **Neumorphism** 风格。 561 | 562 | 开发者只需要简单的通过配置 `isSupportNeumorphism` 参数,就可以开启和关闭 **Neumorphism** 风格。 563 | 564 | 如果想要调整 **Neumorphism** 的样式,可以通过 Shadow 相关的几个属性进行细微的调整,其中: 565 | 566 | - shadowColor: 配置暗部阴影 567 | 568 | - highlightShadowColor:配置亮部阴影 569 | 570 | **FButton** 还提供了 `lightOrientation` 参数,甚至使得开发者能够调整关照角度,已获得不同的 **Neumorphism** 效果。 571 | 572 | # 😃 如何使用? 573 | 574 | 在项目 `pubspec.yaml` 文件中添加依赖: 575 | 576 | ## 🌐 pub 依赖方式 577 | 578 | ``` 579 | dependencies: 580 | fbutton: ^<版本号> 581 | ``` 582 | 583 | > ⚠️ 注意,请到 [**pub**](https://pub.dev/packages/fbutton) 获取 **FButton** 最新版本号 584 | 585 | ## 🖥 git 依赖方式 586 | 587 | ``` 588 | dependencies: 589 | fbutton: 590 | git: 591 | url: 'git@github.com:Fliggy-Mobile/fbutton.git' 592 | ref: '<分支号 或 tag>' 593 | ``` 594 | 595 | 596 | > ⚠️ 注意,分支号 或 tag 请以 [**FButton**](https://github.com/Fliggy-Mobile/fbutton) 官方项目为准。 597 | 598 | 599 | # 💡 License 600 | 601 | ``` 602 | Copyright 2020-present Fliggy Android Team . 603 | 604 | Licensed under the Apache License, Version 2.0 (the "License"); 605 | you may not use this file except in compliance with the License. 606 | You may obtain a copy of the License at following link. 607 | 608 | http://www.apache.org/licenses/LICENSE-2.0 609 | 610 | Unless required by applicable law or agreed to in writing, software 611 | distributed under the License is distributed on an "AS IS" BASIS, 612 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 613 | See the License for the specific language governing permissions and 614 | limitations under the License. 615 | 616 | ``` 617 | 618 | 619 | ### 感觉还不错?请投出您的 [**Star**](https://github.com/Fliggy-Mobile/fbutton) 吧 🥰 ! 620 | 621 | 622 | --- 623 | 624 | # 如何运行 Demo 工程? 625 | 626 | 1.**clone** 工程到本地 627 | 628 | 2.进入工程 `example` 目录,运行以下命令 629 | 630 | ``` 631 | flutter create . 632 | ``` 633 | 634 | 3.运行 `example` 中的 Demo 635 | -------------------------------------------------------------------------------- /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 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | 75 | web/ 76 | .flutter-plugins-dependencies 77 | README.md 78 | test/ 79 | macos/ 80 | 81 | -------------------------------------------------------------------------------- /example/assets/icon_light_selected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fliggy-Mobile/fbutton/2103beae4fd2fcfb887d34654d3f5d24c7ba12ba/example/assets/icon_light_selected.png -------------------------------------------------------------------------------- /example/assets/icon_light_unselected.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Fliggy-Mobile/fbutton/2103beae4fd2fcfb887d34654d3f5d24c7ba12ba/example/assets/icon_light_unselected.png -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:fcontrol/fdefine.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:fbutton/fbutton.dart'; 5 | import 'package:fradio/fradio.dart'; 6 | import 'dart:math' as math; 7 | import 'package:fcommon/fcommon.dart'; 8 | 9 | void main() => runApp(MyApp()); 10 | 11 | class MyApp extends StatefulWidget { 12 | @override 13 | _MyAppState createState() => _MyAppState(); 14 | } 15 | 16 | class _MyAppState extends State { 17 | @override 18 | void initState() { 19 | super.initState(); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return MaterialApp( 25 | home: FButtonPage(), 26 | ); 27 | } 28 | } 29 | 30 | class FButtonPage extends StatefulWidget { 31 | @override 32 | _FButtonPageState createState() => _FButtonPageState(); 33 | } 34 | 35 | class _FButtonPageState extends State { 36 | var _shadowBlur; 37 | 38 | @override 39 | void initState() { 40 | _shadowBlur = 5.0; 41 | super.initState(); 42 | } 43 | 44 | @override 45 | Widget build(BuildContext context) { 46 | return Scaffold( 47 | backgroundColor: mainBackgroundColor, 48 | appBar: AppBar( 49 | backgroundColor: mainBackgroundColor, 50 | title: const Text( 51 | 'FButton', 52 | style: TextStyle(color: mainTextTitleColor), 53 | ), 54 | centerTitle: true, 55 | ), 56 | body: SingleChildScrollView( 57 | child: Container( 58 | child: Center( 59 | child: Column( 60 | mainAxisAlignment: MainAxisAlignment.center, 61 | children: [ 62 | /// Normal FButton 63 | buildTitle("FButton"), 64 | buildSmallMargin(), 65 | 66 | /// Normal FButton 1 67 | buildNormalButton1(), 68 | buildMiddleMargin(), 69 | 70 | /// Normal FButton 2 71 | buildNormalButton2(), 72 | buildMiddleMargin(), 73 | 74 | /// Normal FButton 3 75 | buildNormalButton3(), 76 | buildMiddleMargin(), 77 | 78 | buildTitle("Corner"), 79 | buildSmallMargin(), 80 | 81 | /// Corner FButton 82 | buildCornerButton1(), 83 | buildBigMargin(), 84 | 85 | /// Corner FButton 86 | buildCornerButton2(), 87 | buildMiddleMargin(), 88 | 89 | buildTitle("Gradient"), 90 | buildSmallMargin(), 91 | 92 | /// Gradient FButton 93 | buildGradientButton1(), 94 | buildMiddleMargin(), 95 | 96 | /// Gradient FButton 97 | buildGradientButton2(), 98 | buildMiddleMargin(), 99 | 100 | /// Gradient FButton 101 | buildGradientButton3(), 102 | buildMiddleMargin(), 103 | 104 | /// Stroke FButton 105 | buildTitle("Stroke"), 106 | buildSmallMargin(), 107 | buildStrokeButton(), 108 | buildMiddleMargin(), 109 | 110 | buildTitle("Image"), 111 | buildSmallMargin(), 112 | 113 | /// Image 114 | buildImageButton1(context), 115 | buildBigMargin(), 116 | 117 | /// Image 118 | buildImageButton2(), 119 | buildBigMargin(), 120 | buildTitle("Loading"), 121 | buildMiddleMargin(), 122 | 123 | /// Loading 124 | buildLoadingButton1(), 125 | buildMiddleMargin(), 126 | 127 | /// Loading 128 | buildLoadingButton2(), 129 | buildSmallMargin(), 130 | 131 | /// Effect FButton 132 | buildTitle("Effect"), 133 | buildSmallMargin(), 134 | 135 | /// Hover 136 | buildEffectButton(), 137 | buildMiddleMargin(), 138 | buildTitle("Shadow"), 139 | buildSmallMargin(), 140 | 141 | /// Shadow 142 | buildShadowButton(), 143 | /////// 144 | buildBigMargin(), 145 | buildTitle("Neumorphism Style"), 146 | buildMiddleMargin(), 147 | buildMiddleMargin(), 148 | 149 | /// Neumorphism 150 | neumorphismDemo(), 151 | 152 | buildBiggestMargin(), 153 | buildBiggestMargin(), 154 | ], 155 | ), 156 | ), 157 | ), 158 | )); 159 | } 160 | 161 | Stack neumorphismDemo() { 162 | return Stack( 163 | children: [ 164 | Padding( 165 | padding: EdgeInsets.only(left: 20, right: 20, top: 50, bottom: 50), 166 | child: Column( 167 | children: [ 168 | /// Neumorphism Style 1 169 | neumorphismDemo_1(), 170 | buildMiddleMargin(), 171 | buildMiddleMargin(), 172 | 173 | /// Neumorphism Style 2 174 | neumorphismDemo_2(), 175 | ], 176 | ), 177 | ), 178 | Positioned( 179 | left: 25, 180 | top: 0, 181 | child: Transform.rotate( 182 | angle: -math.pi / 4.0, 183 | alignment: Alignment.center, 184 | child: FRadio.custom( 185 | value: FLightOrientation.LeftTop, 186 | groupValue: lightOrientation, 187 | normal: Image.asset("assets/icon_light_unselected.png"), 188 | selected: Image.asset("assets/icon_light_selected.png"), 189 | onChanged: (value) { 190 | setState(() { 191 | lightOrientation = value; 192 | }); 193 | }, 194 | ), 195 | )), 196 | Positioned( 197 | right: 25, 198 | top: 0, 199 | child: Transform.rotate( 200 | angle: math.pi / 4.0, 201 | alignment: Alignment.center, 202 | child: FRadio.custom( 203 | value: FLightOrientation.RightTop, 204 | groupValue: lightOrientation, 205 | normal: Image.asset("assets/icon_light_unselected.png"), 206 | selected: Image.asset("assets/icon_light_selected.png"), 207 | onChanged: (value) { 208 | setState(() { 209 | lightOrientation = value; 210 | }); 211 | }, 212 | ), 213 | )), 214 | Positioned( 215 | right: 25, 216 | bottom: 0, 217 | child: Transform.rotate( 218 | angle: -math.pi / (3.0 / 4.0), 219 | alignment: Alignment.center, 220 | child: FRadio.custom( 221 | value: FLightOrientation.RightBottom, 222 | groupValue: lightOrientation, 223 | normal: Image.asset("assets/icon_light_unselected.png"), 224 | selected: Image.asset("assets/icon_light_selected.png"), 225 | onChanged: (value) { 226 | setState(() { 227 | lightOrientation = value; 228 | }); 229 | }, 230 | ), 231 | )), 232 | Positioned( 233 | left: 25, 234 | bottom: 0, 235 | child: Transform.rotate( 236 | angle: math.pi / (3.0 / 4.0), 237 | alignment: Alignment.center, 238 | child: FRadio.custom( 239 | value: FLightOrientation.LeftBottom, 240 | groupValue: lightOrientation, 241 | normal: Image.asset("assets/icon_light_unselected.png"), 242 | selected: Image.asset("assets/icon_light_selected.png"), 243 | onChanged: (value) { 244 | setState(() { 245 | lightOrientation = value; 246 | }); 247 | }, 248 | ), 249 | )), 250 | ], 251 | ); 252 | } 253 | 254 | FLightOrientation lightOrientation = FLightOrientation.LeftTop; 255 | double neumorphismSize_1 = 30; 256 | double neumorphismSize_2 = 30; 257 | double neumorphismSize_3 = 30; 258 | 259 | Widget neumorphismDemo_1() { 260 | return StatefulBuilder(builder: (context, setState) { 261 | return Row( 262 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 263 | children: [ 264 | FButton( 265 | lightOrientation: lightOrientation, 266 | 267 | /// 开启 Neumorphism 支持 268 | isSupportNeumorphism: true, 269 | highlightShadowColor: Colors.white, 270 | shadowColor: mainShadowColor, 271 | width: 60, 272 | height: 60, 273 | image: Icon( 274 | Icons.call, 275 | color: mainTextTitleColor, 276 | size: neumorphismSize_1, 277 | ), 278 | alignment: Alignment.center, 279 | color: mainBackgroundColor, 280 | corner: FCorner.all(8.0), 281 | onPressed: () {}, 282 | onPressedDown: () { 283 | setState(() { 284 | neumorphismSize_1 = 26; 285 | }); 286 | }, 287 | onPressedUp: () { 288 | setState(() { 289 | neumorphismSize_1 = 30; 290 | }); 291 | }, 292 | onPressedCancel: () { 293 | setState(() { 294 | neumorphismSize_1 = 30; 295 | }); 296 | }, 297 | ), 298 | FButton( 299 | lightOrientation: lightOrientation, 300 | 301 | /// 开启 Neumorphism 支持 302 | isSupportNeumorphism: true, 303 | highlightShadowColor: Colors.white, 304 | shadowColor: mainShadowColor, 305 | width: 60, 306 | height: 60, 307 | image: Icon( 308 | Icons.mic, 309 | color: mainTextTitleColor, 310 | size: neumorphismSize_2, 311 | ), 312 | alignment: Alignment.center, 313 | color: mainBackgroundColor, 314 | corner: FCorner.all(8.0), 315 | onPressed: () {}, 316 | onPressedDown: () { 317 | setState(() { 318 | neumorphismSize_2 = 26; 319 | }); 320 | }, 321 | onPressedUp: () { 322 | setState(() { 323 | neumorphismSize_2 = 30; 324 | }); 325 | }, 326 | onPressedCancel: () { 327 | setState(() { 328 | neumorphismSize_2 = 30; 329 | }); 330 | }, 331 | ), 332 | FButton( 333 | lightOrientation: lightOrientation, 334 | 335 | /// 开启 Neumorphism 支持 336 | isSupportNeumorphism: true, 337 | strokeColor: mainBackgroundColor, 338 | strokeWidth: 3.0, 339 | highlightShadowColor: Colors.white, 340 | shadowColor: mainShadowColor, 341 | width: 60, 342 | height: 60, 343 | image: Icon( 344 | Icons.photo_camera, 345 | color: mainTextTitleColor, 346 | size: neumorphismSize_3, 347 | ), 348 | alignment: Alignment.center, 349 | color: mainBackgroundColor, 350 | corner: FCorner.all(8.0), 351 | onPressed: () {}, 352 | onPressedDown: () { 353 | setState(() { 354 | neumorphismSize_3 = 26; 355 | }); 356 | }, 357 | onPressedUp: () { 358 | setState(() { 359 | neumorphismSize_3 = 30; 360 | }); 361 | }, 362 | onPressedCancel: () { 363 | setState(() { 364 | neumorphismSize_3 = 30; 365 | }); 366 | }, 367 | ), 368 | ], 369 | ); 370 | }); 371 | } 372 | 373 | double neumorphismSize_2_1 = 30; 374 | double neumorphismSize_2_2 = 18; 375 | 376 | Widget neumorphismDemo_2() { 377 | return StatefulBuilder(builder: (context, setState) { 378 | return Row( 379 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 380 | children: [ 381 | FButton( 382 | lightOrientation: lightOrientation, 383 | 384 | /// 开启 Neumorphism 支持 385 | isSupportNeumorphism: true, 386 | highlightShadowColor: Colors.white, 387 | shadowColor: mainShadowColor, 388 | strokeColor: mainBackgroundColor, 389 | strokeWidth: 3.0, 390 | width: 60, 391 | height: 60, 392 | image: Icon( 393 | Icons.play_arrow, 394 | color: mainTextTitleColor, 395 | size: neumorphismSize_2_1, 396 | ), 397 | alignment: Alignment.center, 398 | color: mainBackgroundColor, 399 | corner: FCorner.all(30.0), 400 | onPressed: () {}, 401 | onPressedDown: () { 402 | setState(() { 403 | neumorphismSize_2_1 = 26; 404 | }); 405 | }, 406 | onPressedUp: () { 407 | setState(() { 408 | neumorphismSize_2_1 = 30; 409 | }); 410 | }, 411 | onPressedCancel: () { 412 | setState(() { 413 | neumorphismSize_2_1 = 30; 414 | }); 415 | }, 416 | ), 417 | FButton( 418 | /// 配置光源方向 419 | /// 420 | /// Configure light source direction 421 | lightOrientation: lightOrientation, 422 | 423 | /// 开启 Neumorphism 支持 424 | /// 425 | /// Turn on Neumorphism support 426 | isSupportNeumorphism: true, 427 | 428 | /// 配置亮部阴影 429 | /// 430 | /// Configure highlight shadow 431 | highlightShadowColor: Colors.white, 432 | 433 | /// 配置暗部阴影 434 | /// 435 | /// Configure dark shadows 436 | shadowColor: mainShadowColor, 437 | strokeColor: mainBackgroundColor, 438 | strokeWidth: 3.0, 439 | width: 190, 440 | height: 60, 441 | text: "FWidget", 442 | style: TextStyle( 443 | color: mainTextTitleColor, fontSize: neumorphismSize_2_2), 444 | alignment: Alignment.center, 445 | color: mainBackgroundColor, 446 | corner: FCorner.all(30.0), 447 | onPressed: () {}, 448 | onPressedDown: () { 449 | setState(() { 450 | neumorphismSize_2_2 = 16; 451 | }); 452 | }, 453 | onPressedUp: () { 454 | setState(() { 455 | neumorphismSize_2_2 = 18; 456 | }); 457 | }, 458 | onPressedCancel: () { 459 | setState(() { 460 | neumorphismSize_2_2 = 18; 461 | }); 462 | }, 463 | ), 464 | ], 465 | ); 466 | }); 467 | } 468 | 469 | Widget buildShadowButton() { 470 | return StatefulBuilder(builder: (context, setState) { 471 | return Column( 472 | children: [ 473 | buildMiddleMargin(), 474 | FButton( 475 | width: 200, 476 | height: 50, 477 | alignment: Alignment.center, 478 | text: "Shadow", 479 | style: TextStyle(color: Colors.white), 480 | color: Color(0xffb39ddb), 481 | onPressed: () {}, 482 | clickEffect: true, 483 | corner: FCorner.all(28), 484 | shadowColor: Color(0xff3754AA).withOpacity(0.78), 485 | shadowBlur: _shadowBlur, 486 | ), 487 | buildMiddleMargin(), 488 | Container( 489 | width: 200, 490 | child: Slider( 491 | label: _shadowBlur.toString(), 492 | value: _shadowBlur, 493 | min: 0.0, 494 | max: 20, 495 | divisions: 40, 496 | activeColor: Color(0xff9fa8da), 497 | inactiveColor: Color(0xff9fa8da).withOpacity(0.38), 498 | onChanged: (v) { 499 | setState(() { 500 | _shadowBlur = v; 501 | }); 502 | }, 503 | ), 504 | ), 505 | ], 506 | ); 507 | }); 508 | } 509 | 510 | FButton buildEffectButton() { 511 | return FButton( 512 | width: 200, 513 | height: 50, 514 | alignment: Alignment.center, 515 | text: "Try Me!", 516 | style: TextStyle(color: Colors.white), 517 | color: Color(0xff81d4fa), 518 | onPressed: () {}, 519 | clickEffect: true, 520 | corner: FCorner.all(9), 521 | highlightColor: Color(0xffff8a65), 522 | hoverColor: Color(0xff80deea), 523 | shadowColor: mainShadowColor, 524 | shadowBlur: 6.0, 525 | shadowOffset: Offset(2.0, 2.0), 526 | ); 527 | } 528 | 529 | Widget buildLoadingButton2() { 530 | return StatefulBuilder(builder: (context, setState) { 531 | return Column( 532 | children: [ 533 | Row( 534 | mainAxisAlignment: MainAxisAlignment.center, 535 | children: [ 536 | FButton( 537 | padding: EdgeInsets.all(12.0), 538 | text: "Click left loading", 539 | style: TextStyle(color: Colors.white), 540 | color: Color(0xffffab91), 541 | onPressed: () { 542 | print("Loading..."); 543 | }, 544 | clickEffect: true, 545 | corner: FCorner.all(9), 546 | loadingSize: 15, 547 | imageMargin: 16, 548 | loadingStrokeWidth: 2, 549 | clickLoading: true, 550 | loadingColor: Colors.white, 551 | loadingText: "Loading...", 552 | ), 553 | SizedBox( 554 | width: 10, 555 | ), 556 | FButton( 557 | padding: EdgeInsets.all(12.0), 558 | text: "Click top loading", 559 | style: TextStyle(color: Colors.white), 560 | color: Color(0xffb39ddb), 561 | onPressed: () { 562 | print("Loading..."); 563 | }, 564 | clickEffect: true, 565 | corner: FCorner.all(9), 566 | loadingSize: 15, 567 | imageMargin: 6, 568 | loadingStrokeWidth: 2, 569 | clickLoading: true, 570 | loadingColor: Colors.white, 571 | loadingText: "Loading...", 572 | imageAlignment: ImageAlignment.top, 573 | loadingWidget: RefreshProgressIndicator(), 574 | ), 575 | ], 576 | ), 577 | buildMiddleMargin(), 578 | FButton( 579 | width: 170, 580 | height: 70, 581 | alignment: Alignment.center, 582 | text: "Click to loading", 583 | style: TextStyle(color: Colors.white), 584 | color: Color(0xff90caf9), 585 | onPressed: () { 586 | print("Loading..."); 587 | }, 588 | clickEffect: true, 589 | corner: FCorner.all(9), 590 | image: Icon( 591 | Icons.cloud_download, 592 | size: 18, 593 | color: Colors.white, 594 | ), 595 | imageMargin: 8, 596 | loadingSize: 15, 597 | loadingStrokeWidth: 2, 598 | clickLoading: true, 599 | loadingColor: Colors.white, 600 | loadingText: "Loading...", 601 | hideTextOnLoading: true, 602 | loadingWidget: CupertinoActivityIndicator(), 603 | ), 604 | buildMiddleMargin(), 605 | FButton( 606 | width: 100, 607 | height: 30, 608 | alignment: Alignment.center, 609 | text: "Reset", 610 | style: TextStyle(color: mainTextTitleColor), 611 | corner: FCorner.all(25), 612 | onPressed: () { 613 | setState(() {}); 614 | }, 615 | color: Color(0xffffab91), 616 | // clickEffect: true, 617 | highlightShadowColor: Color(0xfffbe9e7), 618 | shadowColor: Color(0xffff7043), 619 | isSupportNeumorphism: true, 620 | shadowBlur: 10.0, 621 | ), 622 | ], 623 | ); 624 | }); 625 | } 626 | 627 | Widget buildLoadingButton1() { 628 | return FButton( 629 | width: 200, 630 | height: 50, 631 | text: "loading", 632 | style: TextStyle(color: Colors.white), 633 | color: Color(0xffa5d6a7), 634 | onPressed: () { 635 | print("Loading..."); 636 | }, 637 | clickEffect: true, 638 | corner: FCorner.all(30), 639 | loadingSize: 15, 640 | imageMargin: 16, 641 | loadingStrokeWidth: 2, 642 | loading: true, 643 | loadingColor: Colors.white, 644 | loadingText: "Loading...", 645 | ); 646 | } 647 | 648 | Row buildImageButton2() { 649 | return Row( 650 | mainAxisAlignment: MainAxisAlignment.center, 651 | children: [ 652 | FButton( 653 | onPressed: () {}, 654 | image: Icon( 655 | Icons.add_a_photo, 656 | color: Color(0xffff8a65), 657 | ), 658 | imageMargin: 8, 659 | text: "Take Photo", 660 | style: TextStyle(color: mainTextTitleColor), 661 | color: Colors.transparent, 662 | ), 663 | SizedBox( 664 | width: 36, 665 | ), 666 | FButton( 667 | onPressed: () {}, 668 | image: Icon( 669 | Icons.print, 670 | color: Colors.grey, 671 | ), 672 | imageMargin: 8, 673 | imageAlignment: ImageAlignment.top, 674 | text: "Print", 675 | style: TextStyle(color: mainTextTitleColor), 676 | color: Colors.transparent, 677 | ), 678 | ], 679 | ); 680 | } 681 | 682 | Row buildImageButton1(BuildContext context) { 683 | return Row( 684 | mainAxisAlignment: MainAxisAlignment.center, 685 | children: [ 686 | FButton( 687 | width: 88, 688 | height: 38, 689 | padding: EdgeInsets.all(0), 690 | text: "Back", 691 | style: TextStyle(color: Colors.white), 692 | color: Color(0xff9ccc65), 693 | onPressed: () { 694 | toast(context, "Back!"); 695 | }, 696 | clickEffect: true, 697 | corner: FCorner(leftTopCorner: 25, leftBottomCorner: 25), 698 | image: Icon( 699 | Icons.arrow_back_ios, 700 | color: Colors.white, 701 | size: 12, 702 | ), 703 | imageMargin: 8, 704 | ), 705 | Container( 706 | height: 38, 707 | child: VerticalDivider(width: 0.25, color: Colors.black)), 708 | FButton( 709 | width: 88, 710 | height: 38, 711 | padding: EdgeInsets.all(0), 712 | text: "Forward", 713 | style: TextStyle(color: Colors.white), 714 | color: Color(0xffd4e157), 715 | onPressed: () { 716 | toast(context, "Forward!"); 717 | }, 718 | corner: FCorner(rightTopCorner: 25, rightBottomCorner: 25), 719 | image: Icon( 720 | Icons.arrow_forward_ios, 721 | color: Colors.white, 722 | size: 12, 723 | ), 724 | imageMargin: 8, 725 | imageAlignment: ImageAlignment.right, 726 | ), 727 | Padding( 728 | padding: const EdgeInsets.fromLTRB(16, 0, 0, 0), 729 | child: FButton( 730 | width: 38, 731 | height: 38, 732 | padding: EdgeInsets.all(0), 733 | color: Color(0xff26c6da), 734 | onPressed: () { 735 | toast(context, "Search!"); 736 | }, 737 | clickEffect: true, 738 | corner: FCorner.all(19), 739 | image: Icon( 740 | Icons.search, 741 | color: Colors.white, 742 | ), 743 | ), 744 | ), 745 | ], 746 | ); 747 | } 748 | 749 | Padding buildStrokeButton() { 750 | return Padding( 751 | padding: const EdgeInsets.fromLTRB(28, 0, 28, 0), 752 | child: FButton( 753 | text: "Stroke FButton", 754 | style: TextStyle(color: Colors.black87), 755 | color: Colors.white, 756 | onPressed: () {}, 757 | clickEffect: true, 758 | corner: FCorner.all(6), 759 | strokeWidth: 1, 760 | strokeColor: Colors.black87, 761 | padding: EdgeInsets.all(10.0), 762 | ), 763 | ); 764 | } 765 | 766 | Padding buildGradientButton3() { 767 | return Padding( 768 | padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), 769 | child: FButton( 770 | width: 200, 771 | height: 50, 772 | alignment: Alignment.center, 773 | text: "RadialGradient FButton", 774 | style: TextStyle(color: Colors.white), 775 | gradient: RadialGradient( 776 | center: const Alignment(0.6, 0.2), 777 | radius: 0.2, 778 | colors: [ 779 | const Color(0xFF0099FF), 780 | const Color(0xffff7043), 781 | ], 782 | stops: [0.4, 1.0], 783 | ), 784 | onPressed: () {}, 785 | clickEffect: true, 786 | corner: FCorner.all(9), 787 | ), 788 | ); 789 | } 790 | 791 | Padding buildGradientButton2() { 792 | return Padding( 793 | padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), 794 | child: FButton( 795 | width: 200, 796 | height: 90, 797 | alignment: Alignment.center, 798 | text: "SweepGradient FButton", 799 | style: TextStyle(color: Colors.white), 800 | color: Colors.black54, 801 | gradient: SweepGradient( 802 | center: Alignment.center, 803 | startAngle: 0.0, 804 | endAngle: math.pi * 2, 805 | colors: const [ 806 | Colors.blue, 807 | Colors.green, 808 | Colors.yellow, 809 | Colors.red, 810 | Colors.blueAccent, 811 | ], 812 | ), 813 | onPressed: () {}, 814 | clickEffect: true, 815 | corner: FCorner.all(9), 816 | ), 817 | ); 818 | } 819 | 820 | Padding buildGradientButton1() { 821 | return Padding( 822 | padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), 823 | child: FButton( 824 | width: 200, 825 | height: 50, 826 | alignment: Alignment.center, 827 | text: "LinearGradient FButton", 828 | style: TextStyle(color: Colors.white), 829 | color: Color(0xffFFc900), 830 | gradient: LinearGradient(colors: [ 831 | Color(0xff00B0FF), 832 | Color(0xffFFc900), 833 | ]), 834 | onPressed: () {}, 835 | clickEffect: true, 836 | corner: FCorner.all(9), 837 | ), 838 | ); 839 | } 840 | 841 | Widget buildCornerButton2() { 842 | return Container( 843 | width: 360, 844 | padding: const EdgeInsets.fromLTRB(18, 0, 18, 0), 845 | child: Column( 846 | children: [ 847 | Row( 848 | mainAxisAlignment: MainAxisAlignment.spaceAround, 849 | children: [ 850 | FButton( 851 | width: 150, 852 | height: 50, 853 | alignment: Alignment.center, 854 | text: "Corner FButton", 855 | style: TextStyle(color: Colors.white), 856 | color: Color(0xffFF7043), 857 | onPressed: () {}, 858 | clickEffect: true, 859 | corner: FCorner(leftTopCorner: 10), 860 | cornerStyle: FCornerStyle.bevel, 861 | strokeWidth: 0.5, 862 | strokeColor: Color(0xffD84315), 863 | ), 864 | FButton( 865 | width: 150, 866 | height: 50, 867 | alignment: Alignment.center, 868 | text: "Corner FButton", 869 | style: TextStyle(color: Colors.white), 870 | color: Color(0xffFFA726), 871 | onPressed: () {}, 872 | clickEffect: true, 873 | corner: FCorner( 874 | leftBottomCorner: 40, 875 | leftTopCorner: 6, 876 | rightTopCorner: 40, 877 | rightBottomCorner: 6, 878 | ), 879 | cornerStyle: FCornerStyle.bevel, 880 | strokeWidth: 0.5, 881 | strokeColor: Color(0xffEF6C00), 882 | ), 883 | ], 884 | ), 885 | buildSmallMargin(), 886 | Row( 887 | mainAxisAlignment: MainAxisAlignment.spaceAround, 888 | children: [ 889 | FButton( 890 | width: 150, 891 | height: 50, 892 | alignment: Alignment.center, 893 | text: "Corner FButton", 894 | style: TextStyle(color: Colors.white), 895 | color: Color(0xffFFc900), 896 | onPressed: () {}, 897 | clickEffect: true, 898 | corner: FCorner(rightTopCorner: 25, rightBottomCorner: 25), 899 | cornerStyle: FCornerStyle.bevel, 900 | strokeWidth: 0.5, 901 | strokeColor: Color(0xffF9A825), 902 | ), 903 | FButton( 904 | width: 150, 905 | height: 50, 906 | alignment: Alignment.center, 907 | text: "Corner FButton", 908 | style: TextStyle(color: Colors.white), 909 | color: Color(0xff00B0FF), 910 | onPressed: () {}, 911 | clickEffect: true, 912 | corner: FCorner(leftTopCorner: 35, rightTopCorner: 35), 913 | cornerStyle: FCornerStyle.bevel, 914 | strokeWidth: 0.5, 915 | strokeColor: Color(0xff0288D1), 916 | ), 917 | ], 918 | ), 919 | ], 920 | ), 921 | ); 922 | } 923 | 924 | Widget buildCornerButton1() { 925 | return Container( 926 | width: 360, 927 | padding: const EdgeInsets.fromLTRB(18, 0, 18, 0), 928 | child: Column( 929 | children: [ 930 | Row( 931 | mainAxisAlignment: MainAxisAlignment.spaceAround, 932 | children: [ 933 | FButton( 934 | width: 150, 935 | height: 50, 936 | alignment: Alignment.center, 937 | text: "Corner FButton", 938 | style: TextStyle(color: Colors.white), 939 | color: Color(0xffFF7043), 940 | onPressed: () {}, 941 | clickEffect: true, 942 | corner: FCorner(leftTopCorner: 18), 943 | ), 944 | FButton( 945 | width: 150, 946 | height: 50, 947 | alignment: Alignment.center, 948 | text: "Corner FButton", 949 | style: TextStyle(color: Colors.white), 950 | color: Color(0xffFFA726), 951 | onPressed: () {}, 952 | clickEffect: true, 953 | corner: FCorner( 954 | leftBottomCorner: 40, 955 | leftTopCorner: 6, 956 | rightTopCorner: 40, 957 | rightBottomCorner: 6, 958 | ), 959 | ), 960 | ], 961 | ), 962 | buildSmallMargin(), 963 | Row( 964 | mainAxisAlignment: MainAxisAlignment.spaceAround, 965 | children: [ 966 | FButton( 967 | width: 150, 968 | height: 50, 969 | alignment: Alignment.center, 970 | text: "Corner FButton", 971 | style: TextStyle(color: Colors.white), 972 | color: Color(0xffFFc900), 973 | onPressed: () {}, 974 | clickEffect: true, 975 | corner: FCorner(leftTopCorner: 25, leftBottomCorner: 25), 976 | ), 977 | FButton( 978 | width: 150, 979 | height: 50, 980 | alignment: Alignment.center, 981 | text: "Corner FButton", 982 | style: TextStyle(color: Colors.white), 983 | color: Color(0xff00B0FF), 984 | onPressed: () {}, 985 | clickEffect: true, 986 | corner: FCorner(leftTopCorner: 35, rightTopCorner: 35), 987 | ), 988 | ], 989 | ), 990 | ], 991 | ), 992 | ); 993 | } 994 | 995 | Padding buildNormalButton3() { 996 | return Padding( 997 | padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), 998 | child: FButton( 999 | padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), 1000 | text: "FButton #3", 1001 | style: TextStyle(color: Colors.white), 1002 | disableStyle: TextStyle(color: Colors.black38), 1003 | color: Color(0xffF8AD36), 1004 | disabledColor: Colors.grey[300], 1005 | corner: FCorner.all(6.0), 1006 | ), 1007 | ); 1008 | } 1009 | 1010 | Padding buildNormalButton2() { 1011 | return Padding( 1012 | padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), 1013 | child: FButton( 1014 | padding: const EdgeInsets.fromLTRB(12, 8, 12, 8), 1015 | text: "FButton #2", 1016 | style: TextStyle(color: Colors.white), 1017 | color: Color(0xffffab91), 1018 | corner: FCorner.all(6.0), 1019 | ), 1020 | ); 1021 | } 1022 | 1023 | Widget buildNormalButton1() { 1024 | return Padding( 1025 | padding: const EdgeInsets.fromLTRB(8, 0, 8, 0), 1026 | child: FButton( 1027 | height: 40, 1028 | alignment: Alignment.center, 1029 | text: "FButton #1", 1030 | style: TextStyle(color: Colors.white), 1031 | color: Color(0xffffab91), 1032 | onPressed: () {}, 1033 | clickEffect: true, 1034 | highlightColor: Colors.red, 1035 | hoverColor: Colors.blue, 1036 | corner: FCorner.all(6.0), 1037 | ), 1038 | ); 1039 | } 1040 | } 1041 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: fbutton_example 2 | description: Demonstrates how to use the fbutton 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 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^0.1.2 15 | fcommon: 16 | git: 17 | url: 'git@github.com:Fliggy-Mobile/fcommon.git' 18 | ref: 'master' 19 | 20 | fsuper: ^0.1.5 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 | 27 | dev_dependencies: 28 | flutter_test: 29 | sdk: flutter 30 | 31 | fbutton: 32 | path: ../ 33 | 34 | # For information on the generic Dart part of this file, see the 35 | # following page: https://dart.dev/tools/pub/pubspec 36 | 37 | # The following section is specific to Flutter. 38 | flutter: 39 | 40 | # The following line ensures that the Material Icons font is 41 | # included with your application, so that you can use the icons in 42 | # the material Icons class. 43 | uses-material-design: true 44 | 45 | # To add assets to your application, add an assets section, like this: 46 | assets: 47 | - assets/ 48 | # - images/a_dot_burr.jpeg 49 | # - images/a_dot_ham.jpeg 50 | 51 | # An image asset can refer to one or more resolution-specific "variants", see 52 | # https://flutter.dev/assets-and-images/#resolution-aware. 53 | 54 | # For details regarding adding assets from package dependencies, see 55 | # https://flutter.dev/assets-and-images/#from-packages 56 | 57 | # To add custom fonts to your application, add a fonts section here, 58 | # in this "flutter" section. Each entry in this list should have a 59 | # "family" key with the font family name, and a "fonts" key with a 60 | # list giving the asset and other descriptors for the font. For 61 | # example: 62 | # fonts: 63 | # - family: Schyler 64 | # fonts: 65 | # - asset: fonts/Schyler-Regular.ttf 66 | # - asset: fonts/Schyler-Italic.ttf 67 | # style: italic 68 | # - family: Trajan Pro 69 | # fonts: 70 | # - asset: fonts/TrajanPro.ttf 71 | # - asset: fonts/TrajanPro_Bold.ttf 72 | # weight: 700 73 | # 74 | # For details regarding fonts from package dependencies, 75 | # see https://flutter.dev/custom-fonts/#from-packages 76 | -------------------------------------------------------------------------------- /lib/fbutton.dart: -------------------------------------------------------------------------------- 1 | export 'package:fcontrol/fdefine.dart'; 2 | import 'package:fcontrol/fcontrol.dart'; 3 | import 'package:fcontrol/fdefine.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | /// [FButton] 图片相对与文字的位置 7 | /// 8 | /// [FButton] The position of the picture relative to the text 9 | enum ImageAlignment { 10 | /// 文字左边 11 | /// 12 | /// Left of text 13 | left, 14 | 15 | /// 文字顶部 16 | /// 17 | /// Top of text 18 | top, 19 | 20 | /// 文字右边 21 | /// 22 | /// Right side of text 23 | right, 24 | 25 | /// 文字底部 26 | /// 27 | /// Bottom of text 28 | bottom, 29 | } 30 | 31 | /// FButton 提供了一个常用按钮效果的实现集合。支持配置圆角、各种特效、边角、边框以及 Loading等。 32 | /// FButton 让开发者可以只熟悉一个按钮组件,就能实现常见的按钮。而无需掌握多种不同类型的按钮组件。 33 | /// 34 | /// FButton provides a collection of commonly used button effects. 35 | /// Support for configuring rounded corners, various special effects, corners, borders, and loading. 36 | /// FButton allows developers to familiarize themselves with just one button component and implement common buttons. 37 | /// No need to master many different types of button components. 38 | // ignore: must_be_immutable 39 | class FButton extends StatefulWidget { 40 | /// 当按下按钮时,会触发该函数。如果未设置该函数,按钮将进入不可用状态。 41 | /// 42 | /// When the button is pressed, the function is triggered.If this function is not set, the button will enter an unavailable state. 43 | final VoidCallback onPressed; 44 | 45 | /// 按钮上的文字。 46 | /// 47 | /// The text on the button. 48 | final String text; 49 | 50 | /// 按钮文本样式 51 | /// 52 | /// Button text style 53 | final TextStyle style; 54 | 55 | /// 禁用按钮文本样式 56 | /// 57 | /// Disable button text style 58 | final TextStyle disableStyle; 59 | 60 | /// 文本在组件中的相对位置。[Alignment] 61 | /// 62 | /// The relative position of the text in the component. [Alignment] 63 | final Alignment alignment; 64 | 65 | /// 按钮的颜色 66 | /// 67 | /// Button color 68 | final Color color; 69 | 70 | /// 按钮不可用颜色 71 | /// 72 | /// Button is not available in color 73 | final Color disabledColor; 74 | 75 | /// 鼠标进入按钮范围时,按钮的颜色。 76 | /// 77 | /// the color of the button when the mouse enters the button range. 78 | final Color hoverColor; 79 | 80 | /// 按压按钮时的按钮颜色。 81 | /// 82 | /// the button color when the button is pressed. 83 | final Color highlightColor; 84 | 85 | /// 按压按钮时的蒙层颜色。调整颜色值的 alpha,以确保背后的视图能够展示。 86 | /// 87 | /// The color of the mask when the button is pressed. Adjust the alpha of the color value to ensure that the view behind can be displayed. 88 | final Color activeMaskColor; 89 | 90 | /// 内间距。 91 | /// 92 | /// Internal spacing 93 | final EdgeInsetsGeometry padding; 94 | 95 | /// 宽度。 96 | /// 97 | /// width 98 | final double width; 99 | 100 | /// 高度。 101 | /// 102 | /// height. 103 | final double height; 104 | 105 | /// 为组件设置边角。 106 | /// 107 | /// Set corners for widget 108 | final FCorner corner; 109 | 110 | /// 设置边角风格,默认 [FCornerStyle.round] 111 | /// 112 | /// Set rounded corner style, default [FCornerStyle.round] 113 | final FCornerStyle cornerStyle; 114 | 115 | /// 设置边框颜色。 116 | /// 117 | /// Set the border color. 118 | final Color strokeColor; 119 | 120 | /// 设置边框宽 121 | /// 122 | /// Set border width 123 | final double strokeWidth; 124 | 125 | /// 设置组件阴影颜色 126 | /// 127 | /// Set component shadow color 128 | final Color shadowColor; 129 | 130 | /// 开启 Neumorphism 风格后的,亮部阴影颜色 131 | /// 132 | /// After the Neumorphism style is turned on, the bright shadow color 133 | final Color highlightShadowColor; 134 | 135 | /// 设置组件阴影偏移 136 | /// 137 | /// Set component shadow offset 138 | final Offset shadowOffset; 139 | 140 | /// 设置组件高斯与阴影形状卷积的标准偏差。 141 | /// 142 | /// Sets the standard deviation of the component's Gaussian convolution with the shadow shape. 143 | final double shadowBlur; 144 | 145 | /// 是否开启基于阴影的点击特效。 146 | /// 147 | /// Whether to enable shadow-based click effects. 148 | final bool clickEffect; 149 | 150 | /// 设置组件渐变色背景。会覆盖 [color] 配置 151 | /// 你可选择 [LinearGradient],[RadialGradient],[SweepGradient] 等.. 152 | /// 153 | /// Sets the gradient background of the component. [BackgroundColor] 154 | /// You can choose [LinearGradient], [RadialGradient], [SweepGradient], etc .. 155 | final Gradient gradient; 156 | 157 | /// 设置图标 158 | /// 159 | /// Settings icon 160 | final Widget image; 161 | 162 | /// 设置图标与文本的间距 163 | /// 164 | /// Set the distance between the icon and the text 165 | final double imageMargin; 166 | 167 | /// 设置图标与文本的相对位置。详见 [ImageAlignment] 168 | /// 169 | /// Set the relative position of the icon and the text. See [ImageAlignment] for details 170 | final ImageAlignment imageAlignment; 171 | 172 | /// 是否启动 Loading 状态。Loading 状态会覆盖 [image] 配置 173 | /// 174 | /// Whether to start the loading state. Loading status will override [image] configuration 175 | final bool loading; 176 | 177 | /// loading 状态时的 Loading 小部件。会覆盖默认的 Loading 效果 178 | /// 179 | /// Loading widget in loading state. Will override the default Loading effect 180 | final Widget loadingWidget; 181 | 182 | /// Loading 的颜色 183 | /// 184 | /// Loading colors 185 | final Color loadingColor; 186 | 187 | /// Loading 的宽度 188 | /// 189 | /// Loading width 190 | final double loadingStrokeWidth; 191 | 192 | /// Loading 的大小 193 | /// 194 | /// Loading size 195 | final double loadingSize; 196 | 197 | /// 是否启用点击进入 Loading 状态的模式 198 | /// 199 | /// Whether to enable click to enter the loading mode 200 | final bool clickLoading; 201 | 202 | /// Loading 状态下是否隐藏文本 203 | /// 204 | /// Whether to hide text in the loading state 205 | final bool hideTextOnLoading; 206 | 207 | /// Loading 状态下展示的文本 208 | /// 209 | /// Text displayed under Loading 210 | final String loadingText; 211 | 212 | /// 表面的风格。默认 [FSurface.Flat]。详见 [FSurface] 213 | /// 214 | /// Surface style. Default [FSurface.Flat]. See [FSurface] for details 215 | final FSurface surfaceStyle; 216 | 217 | /// 鼠标进入/退出组件范围时会回调 218 | /// 219 | /// Callback when the mouse enters/exits the component range 220 | final ValueChanged onHover; 221 | 222 | /// 按下时会回调 223 | /// 224 | /// Callback when pressed 225 | final VoidCallback onPressedDown; 226 | 227 | /// 抬起时会回调 228 | /// 229 | /// Callback when lifted 230 | final VoidCallback onPressedUp; 231 | 232 | /// 按下取消时会回调 233 | /// 234 | /// Callback when cancel is pressed 235 | final VoidCallback onPressedCancel; 236 | 237 | /// 是否支持 Neumorphism 风格。开启该项 [highlightColor] 将会失效 238 | /// 239 | /// Whether to support the Neumorphism style. Open this item [highlightColor] will be invalid 240 | final bool isSupportNeumorphism; 241 | 242 | /// 当 [isSupportNeumorphism] 为 true 时有效。光源方向,分为左上、左下、右上、右下四个方向。用来控制光源照射方向,会影响高亮方向和阴影方向 243 | /// 244 | /// 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 245 | final FLightOrientation lightOrientation; 246 | 247 | FButton({ 248 | Key key, 249 | this.onPressed, 250 | this.text, 251 | this.color = Colors.transparent, 252 | this.disabledColor, 253 | this.hoverColor, 254 | this.highlightColor, 255 | this.padding, 256 | this.width, 257 | this.height, 258 | this.corner, 259 | this.cornerStyle = FCornerStyle.round, 260 | this.strokeColor, 261 | this.strokeWidth, 262 | this.shadowColor, 263 | this.shadowOffset, 264 | this.shadowBlur = 0.0, 265 | this.gradient, 266 | this.image, 267 | this.imageMargin = 6.0, 268 | this.imageAlignment = ImageAlignment.left, 269 | this.loading = false, 270 | this.loadingColor, 271 | this.loadingStrokeWidth = 4.0, 272 | this.clickLoading = false, 273 | this.hideTextOnLoading = false, 274 | this.loadingText, 275 | this.loadingSize = 12, 276 | this.clickEffect = false, 277 | this.style, 278 | this.disableStyle, 279 | this.alignment, 280 | this.activeMaskColor = Colors.transparent, 281 | this.surfaceStyle = FSurface.Flat, 282 | this.onHover, 283 | this.onPressedDown, 284 | this.onPressedUp, 285 | this.onPressedCancel, 286 | this.isSupportNeumorphism = false, 287 | this.highlightShadowColor, 288 | this.loadingWidget, 289 | this.lightOrientation = FLightOrientation.LeftTop, 290 | }) : super(key: key); 291 | 292 | @override 293 | State createState() { 294 | return _FButton(); 295 | } 296 | } 297 | 298 | class _FButton extends State with SingleTickerProviderStateMixin { 299 | double shadowBlur; 300 | 301 | Color shadowColor; 302 | 303 | Tween shadowTween; 304 | 305 | AnimationController animationController; 306 | 307 | bool get enabled => widget.onPressed != null && !loading; 308 | 309 | bool loading = false; 310 | 311 | @override 312 | void initState() { 313 | super.initState(); 314 | updateParam(); 315 | animationController = AnimationController( 316 | duration: Duration(milliseconds: 300), 317 | vsync: this, 318 | ); 319 | shadowTween = Tween(begin: 0, end: widget.shadowBlur); 320 | animationController.addListener(() { 321 | setState(() { 322 | shadowBlur = shadowTween.evaluate(animationController); 323 | }); 324 | }); 325 | } 326 | 327 | @override 328 | void didUpdateWidget(FButton oldWidget) { 329 | super.didUpdateWidget(oldWidget); 330 | updateParam(); 331 | } 332 | 333 | @override 334 | void dispose() { 335 | animationController.dispose(); 336 | super.dispose(); 337 | } 338 | 339 | updateParam() { 340 | shadowBlur = widget.shadowBlur; 341 | shadowColor = widget.shadowColor; 342 | loading = widget.loading; 343 | } 344 | 345 | @override 346 | Widget build(BuildContext context) { 347 | double disableOpacity = 0.58; 348 | 349 | /// Handle the relationship between [image] and [loading] 350 | Widget image = loading 351 | ? widget.loadingWidget ?? 352 | SizedBox( 353 | width: widget.loadingSize, 354 | height: widget.loadingSize, 355 | child: CircularProgressIndicator( 356 | strokeWidth: widget.loadingStrokeWidth, 357 | valueColor: AlwaysStoppedAnimation(widget.loadingColor == 358 | null 359 | ? Theme.of(context).accentColor.withOpacity(disableOpacity) 360 | : widget.loadingColor.withOpacity(disableOpacity)), 361 | ), 362 | ) 363 | : widget.image; 364 | 365 | /// Corner 366 | BorderRadius borderRadius = widget.corner == null 367 | ? BorderRadius.all(Radius.circular(0)) 368 | : BorderRadius.only( 369 | topLeft: Radius.circular(widget.corner.leftTopCorner), 370 | topRight: Radius.circular(widget.corner.rightTopCorner), 371 | bottomRight: Radius.circular(widget.corner.rightBottomCorner), 372 | bottomLeft: Radius.circular(widget.corner.leftBottomCorner), 373 | ); 374 | 375 | /// side 376 | Color sideColor = widget.strokeColor ?? Colors.transparent; 377 | BorderSide borderSide = BorderSide( 378 | width: widget.strokeWidth ?? 0, 379 | color: sideColor, 380 | style: BorderStyle.solid, 381 | ); 382 | 383 | /// shape 384 | FShape shape = FShape( 385 | borderShape: widget.cornerStyle == FCornerStyle.round 386 | ? FBorderShape.RoundedRectangle 387 | : FBorderShape.BeveledRectangle, 388 | side: borderSide, 389 | borderRadius: borderRadius, 390 | ); 391 | 392 | /// Handle the relationship between loading text and regular text 393 | String loadingText = 394 | widget.loadingText == null ? widget.text : widget.loadingText; 395 | String text = loading ? loadingText : widget.text; 396 | Widget layerText = _buildTextLayer(text); 397 | Widget layerRow = _buildRowLayer(text, image, layerText); 398 | 399 | ///////////////// 400 | Widget layerContainer = FControl( 401 | lightOrientation: widget.lightOrientation, 402 | width: widget.width, 403 | height: widget.height, 404 | padding: widget.padding, 405 | gradient: widget.gradient, 406 | shape: shape, 407 | surface: widget.surfaceStyle, 408 | supportDropShadow: (shadowColor != null && shadowBlur != 0.0) || 409 | widget.isSupportNeumorphism, 410 | dropShadow: FShadow( 411 | highlightColor: widget.isSupportNeumorphism 412 | ? widget.highlightShadowColor ?? Colors.white.withOpacity(0.83) 413 | : Colors.transparent, 414 | highlightBlur: widget.isSupportNeumorphism ? _shadowBlur : 0.0, 415 | highlightDistance: shadowDistance, 416 | shadowColor: shadowColor ?? Color(0xffd1d9e6), 417 | shadowBlur: _shadowBlur, 418 | shadowDistance: shadowDistance, 419 | shadowOffset: widget.shadowOffset, 420 | ), 421 | supportInnerShadow: widget.isSupportNeumorphism, 422 | innerShadow: FShadow( 423 | highlightColor: 424 | widget.highlightShadowColor ?? Colors.white.withOpacity(0.83), 425 | highlightBlur: _shadowBlur, 426 | highlightDistance: shadowDistance, 427 | shadowColor: shadowColor ?? Color(0xffd1d9e6), 428 | shadowBlur: _shadowBlur, 429 | shadowDistance: shadowDistance, 430 | ), 431 | appearance: widget.isSupportNeumorphism 432 | ? FAppearance.Neumorphism 433 | : FAppearance.Material, 434 | onTapCallback: (_, __) { 435 | onPressed(); 436 | }, 437 | onTapDownCallback: (_, __) { 438 | widget.onPressedDown?.call(); 439 | }, 440 | onTapUpCallback: (_, __) { 441 | widget.onPressedUp?.call(); 442 | }, 443 | onTapCancelCallback: (_, __) { 444 | widget.onPressedCancel?.call(); 445 | }, 446 | maskColor: widget.activeMaskColor, 447 | colorForCallback: (sender, state) { 448 | if (state == FState.Highlighted) { 449 | return widget.isSupportNeumorphism 450 | ? widget.color 451 | : widget.highlightColor; 452 | } else if (state == FState.Disable) { 453 | return widget.disabledColor ?? 454 | (widget.color ?? FDisableColor).withOpacity(disableOpacity); 455 | } 456 | return widget.color; 457 | }, 458 | disabled: !enabled, 459 | hoverColor: widget.hoverColor, 460 | onHover: widget.onHover != null 461 | ? (v) { 462 | widget.onHover(v); 463 | } 464 | : null, 465 | child: Container( 466 | alignment: widget.alignment, 467 | child: layerRow, 468 | ), 469 | ); 470 | ///////////////// 471 | 472 | Widget result = Semantics( 473 | button: true, 474 | enabled: enabled, 475 | child: layerContainer, 476 | ); 477 | return result; 478 | } 479 | 480 | double get _shadowBlur { 481 | if ((shadowBlur == null || shadowBlur == 0.0) && 482 | widget.isSupportNeumorphism) { 483 | return 6.0; 484 | } else { 485 | return shadowBlur; 486 | } 487 | } 488 | 489 | double get shadowDistance { 490 | if (widget.isSupportNeumorphism) { 491 | return widget.shadowOffset?.dy ?? 3.0; 492 | } else { 493 | return widget.shadowOffset?.dy ?? 0.0; 494 | } 495 | } 496 | 497 | Text _buildTextLayer(String text) { 498 | return Text( 499 | text ?? "", 500 | style: enabled ? widget.style : widget.disableStyle ?? widget.style, 501 | ); 502 | } 503 | 504 | Widget _buildRowLayer(String text, Widget image, Widget layerText) { 505 | if (image == null) return layerText; 506 | var showLoading = (loading && widget.hideTextOnLoading); 507 | if (showLoading || text == null || text == "") { 508 | return image; 509 | } else { 510 | switch (widget.imageAlignment) { 511 | case ImageAlignment.left: 512 | return Row( 513 | crossAxisAlignment: CrossAxisAlignment.center, 514 | mainAxisAlignment: MainAxisAlignment.center, 515 | mainAxisSize: MainAxisSize.min, 516 | children: [ 517 | image, 518 | SizedBox(width: widget.imageMargin), 519 | layerText, 520 | ], 521 | ); 522 | case ImageAlignment.top: 523 | return Column( 524 | crossAxisAlignment: CrossAxisAlignment.center, 525 | mainAxisAlignment: MainAxisAlignment.center, 526 | mainAxisSize: MainAxisSize.min, 527 | children: [ 528 | image, 529 | SizedBox(height: widget.imageMargin), 530 | layerText, 531 | ], 532 | ); 533 | case ImageAlignment.right: 534 | return Row( 535 | crossAxisAlignment: CrossAxisAlignment.center, 536 | mainAxisAlignment: MainAxisAlignment.center, 537 | mainAxisSize: MainAxisSize.min, 538 | children: [ 539 | layerText, 540 | SizedBox(width: widget.imageMargin), 541 | image, 542 | ], 543 | ); 544 | case ImageAlignment.bottom: 545 | return Column( 546 | crossAxisAlignment: CrossAxisAlignment.center, 547 | mainAxisAlignment: MainAxisAlignment.center, 548 | mainAxisSize: MainAxisSize.min, 549 | children: [ 550 | layerText, 551 | SizedBox(height: widget.imageMargin), 552 | image, 553 | ], 554 | ); 555 | } 556 | } 557 | return layerText; 558 | } 559 | 560 | onPressed() { 561 | widget.onPressed?.call(); 562 | if (widget.clickLoading && !loading) { 563 | loading = true; 564 | setState(() {}); 565 | } else if (widget.clickEffect) { 566 | pressOutEffect(); 567 | } 568 | } 569 | 570 | pressOutEffect() { 571 | shadowBlur = (shadowBlur ?? 0) + 6; 572 | shadowColor = widget.shadowColor; 573 | if (shadowColor == null) { 574 | if (widget.isSupportNeumorphism) { 575 | shadowColor = Color(0xffd1d9e6); 576 | } else if (widget.strokeColor != null) { 577 | shadowColor = widget.strokeColor.withOpacity(0.58); 578 | } else { 579 | shadowColor = widget.color.withOpacity(0.58); 580 | } 581 | } 582 | shadowTween 583 | ..begin = shadowBlur 584 | ..end = widget.shadowBlur; 585 | animationController 586 | ..value = 0.0 587 | ..forward(); 588 | } 589 | } 590 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: fbutton 2 | description: Let developers just need to grasp only one button component. 3 | version: 2.0.1 4 | author: CoorChice 5 | homepage: https://github.com/Fliggy-Mobile/fbutton 6 | 7 | environment: 8 | sdk: ">=2.2.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | fcontrol: ^1.0.0 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | # fcontrol: 21 | # path: ../fcontrol 22 | 23 | # For information on the generic Dart part of this file, see the 24 | # following page: https://dart.dev/tools/pub/pubspec 25 | 26 | # The following section is specific to Flutter. 27 | flutter: 28 | # This section identifies this Flutter project as a plugin project. 29 | # The androidPackage and pluginClass identifiers should not ordinarily 30 | # be modified. They are used by the tooling to maintain consistency when 31 | # adding or updating assets for this project. 32 | # plugin: 33 | # androidPackage: com.taobao.fapi.fbutton 34 | # pluginClass: FbuttonPlugin 35 | 36 | # To add assets to your plugin package, add an assets section, like this: 37 | # assets: 38 | # - images/a_dot_burr.jpeg 39 | # - images/a_dot_ham.jpeg 40 | # 41 | # For details regarding assets in packages, see 42 | # https://flutter.dev/assets-and-images/#from-packages 43 | # 44 | # An image asset can refer to one or more resolution-specific "variants", see 45 | # https://flutter.dev/assets-and-images/#resolution-aware. 46 | 47 | # To add custom fonts to your plugin package, add a fonts section here, 48 | # in this "flutter" section. Each entry in this list should have a 49 | # "family" key with the font family name, and a "fonts" key with a 50 | # list giving the asset and other descriptors for the font. For 51 | # example: 52 | # fonts: 53 | # - family: Schyler 54 | # fonts: 55 | # - asset: fonts/Schyler-Regular.ttf 56 | # - asset: fonts/Schyler-Italic.ttf 57 | # style: italic 58 | # - family: Trajan Pro 59 | # fonts: 60 | # - asset: fonts/TrajanPro.ttf 61 | # - asset: fonts/TrajanPro_Bold.ttf 62 | # weight: 700 63 | # 64 | # For details regarding fonts in packages, see 65 | # https://flutter.dev/custom-fonts/#from-packages 66 | --------------------------------------------------------------------------------