├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── sample │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── linear_tab_bar_page.dart │ ├── main.dart │ ├── page_item.dart │ ├── pinned_linear_tab_bar_page.dart │ ├── round_tab_bar_page.dart │ ├── standard_tab_bar_page.dart │ └── vertical_round_tab_bar_page.dart ├── pubspec.lock ├── pubspec.yaml ├── test │ └── widget_test.dart └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json ├── lib ├── custom_tab_bar.dart ├── indicator │ ├── custom_indicator.dart │ ├── linear_indicator.dart │ ├── round_indicator.dart │ └── standard_indicator.dart ├── library.dart ├── models.dart └── transform │ ├── color_transform.dart │ ├── scale_transform.dart │ └── tab_bar_transform.dart ├── pubspec.lock ├── pubspec.yaml └── test └── flutter_custom_tab_bar_test.dart /.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 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 9b2d32b605630f28625709ebd9d78ab3016b2bf6 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "flutter_custom_tab_bar", 9 | "request": "launch", 10 | "type": "dart" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [1.2.1] - 2022.11.14 2 | 1. 修复在setState后tabBarItem位置偏移的问题 3 | ## [1.2.0] - 2022.08.29 4 | 1. 移除在3.x版本flutter中WidgetsBinding.instance不再为空导致的警告 5 | ## [1.1.9] - 2022.01.30 6 | 1. 增加direction属性(只支持RoundIndicator) 7 | ## [1.1.7] - 2022.01.10 8 | 1. 增加CustomTabBarController的currentIndex属性和isChanging方法 9 | ## [1.1.6] - 2022.01.06 10 | 1. 修复发现的问题 11 | 2. 增加LinearIndicator的width参数 12 | 3. 移除无用的参数Alignment 13 | ## [1.1.3] - 2021.12.28 14 | 1. 修复发现的问题 15 | 2. 移除 `initialIndex` 参数 16 | ## [1.0.9] - 2021.12.18 17 | 1. 修复发现的问题 18 | 19 | ## [1.0.8] - 2021.12.10 20 | [破坏性更新] 21 | 1.修改指示器控制方式 22 | 2.重构代码,移除不合理的代码设计 23 | 3.修复跨索引跳转的时候tabbar item的动画生硬的问题 24 | 25 | 26 | ## [1.0.7] - 2021.11.23 27 | 1. 修改indicatorColor属性为color 28 | 2. 修改indicatorWidth属性为width 29 | 3. 增加StandardIndicator height属性 30 | 31 | ## [1.0.6] - 2021.11.04 32 | fix some bug 33 | 34 | ## [1.0.5] - 2021.10.29 35 | fix some bug 36 | 37 | ## [1.0.4] - 2021.10.20 38 | fix some bug 39 | 40 | 41 | ## [1.0.3] - 2021.8.10 42 | Added the ability to bisecting the width of tab bar items in a non-sliding state 43 | 44 | 45 | ## [1.0.2] - 2021.7.3 46 | fix some bug 47 | 48 | ## [1.0.1] - 2021.7.3 49 | fix some bug 50 | 51 | 52 | ## [1.0.0] - 2021.4.26 53 | 54 | Support null-safety 55 | 56 | ## [0.0.12] - 2021.4.24 57 | 58 | fixed some bug 59 | 60 | 61 | ## [0.0.11] - 2021.4.24 62 | 63 | fixed some bug 64 | 65 | 66 | ## [0.0.10] - 2021.4.24 67 | 68 | fixed some bug 69 | 70 | 71 | 72 | ## [0.0.9] - 2021.4.24 73 | 74 | fixed some bug 75 | 76 | 77 | ## [0.0.8] - 2021.4.16 78 | 79 | fixed some bug 80 | 81 | 82 | ## [0.0.7] - 2021.4.12 83 | 84 | fixed some bug 85 | 86 | ## [0.0.6] - 2021.3.25 87 | 88 | fixed some bug 89 | 90 | 91 | ## [0.0.5] - 2021.3.17 92 | 93 | fixed some bug 94 | 95 | ## [0.0.4] - 2021.3.17 96 | 97 | fixed some bug 98 | 99 | 100 | ## [0.0.3] - 2021.3.17 101 | 102 | add Linear,Round Indicator 103 | 104 | ## [0.0.2] - 2021.2.26 105 | 106 | fixed some bug 107 | 108 | ## [0.0.1] - 2021.2.26 109 | 110 | init release 111 | 112 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ``` 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 DirectCode 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # custom_tab_bar 2 | [Vertical Round Indicator Example](./example/lib/vertical_round_tab_bar_page.dart) 3 | 4 | 5 | 6 | [Standard Indicator Example](./example/lib/standard_tab_bar_page.dart) 7 | ![](https://raw.githubusercontent.com/lazyee/ImageHosting/master/img/standard.gif) 8 | 9 | [Linear Indicator Example](./example/lib/linear_tab_bar_page.dart) 10 | ![](https://raw.githubusercontent.com/lazyee/ImageHosting/master/img/linear.gif) 11 | 12 | 13 | [round Indicator Example](./example/lib/round_tab_bar_page.dart) 14 | ![](https://raw.githubusercontent.com/lazyee/ImageHosting/master/img/round.gif) 15 | 16 | 17 | -------------------------------------------------------------------------------- /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 | 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 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 0b8abb4724aa590dd0f429683339b1e045a1594d 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # sample 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | linter: 12 | # The lint rules applied to this project can be customized in the 13 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 14 | # included above or to enable additional rules. A list of all available lints 15 | # and their documentation is published at 16 | # https://dart-lang.github.io/linter/lints/index.html. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.sample" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/sample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.sample 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXCopyFilesBuildPhase section */ 19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 20 | isa = PBXCopyFilesBuildPhase; 21 | buildActionMask = 2147483647; 22 | dstPath = ""; 23 | dstSubfolderSpec = 10; 24 | files = ( 25 | ); 26 | name = "Embed Frameworks"; 27 | runOnlyForDeploymentPostprocessing = 0; 28 | }; 29 | /* End PBXCopyFilesBuildPhase section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | ); 53 | runOnlyForDeploymentPostprocessing = 0; 54 | }; 55 | /* End PBXFrameworksBuildPhase section */ 56 | 57 | /* Begin PBXGroup section */ 58 | 9740EEB11CF90186004384FC /* Flutter */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 65 | ); 66 | name = Flutter; 67 | sourceTree = ""; 68 | }; 69 | 97C146E51CF9000F007C117D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 9740EEB11CF90186004384FC /* Flutter */, 73 | 97C146F01CF9000F007C117D /* Runner */, 74 | 97C146EF1CF9000F007C117D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 97C146EF1CF9000F007C117D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 97C146EE1CF9000F007C117D /* Runner.app */, 82 | ); 83 | name = Products; 84 | sourceTree = ""; 85 | }; 86 | 97C146F01CF9000F007C117D /* Runner */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 92 | 97C147021CF9000F007C117D /* Info.plist */, 93 | 97C146F11CF9000F007C117D /* Supporting Files */, 94 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 95 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 96 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 97 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 98 | ); 99 | path = Runner; 100 | sourceTree = ""; 101 | }; 102 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | ); 106 | name = "Supporting Files"; 107 | sourceTree = ""; 108 | }; 109 | /* End PBXGroup section */ 110 | 111 | /* Begin PBXNativeTarget section */ 112 | 97C146ED1CF9000F007C117D /* Runner */ = { 113 | isa = PBXNativeTarget; 114 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 115 | buildPhases = ( 116 | 9740EEB61CF901F6004384FC /* Run Script */, 117 | 97C146EA1CF9000F007C117D /* Sources */, 118 | 97C146EB1CF9000F007C117D /* Frameworks */, 119 | 97C146EC1CF9000F007C117D /* Resources */, 120 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 121 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 122 | ); 123 | buildRules = ( 124 | ); 125 | dependencies = ( 126 | ); 127 | name = Runner; 128 | productName = Runner; 129 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 130 | productType = "com.apple.product-type.application"; 131 | }; 132 | /* End PBXNativeTarget section */ 133 | 134 | /* Begin PBXProject section */ 135 | 97C146E61CF9000F007C117D /* Project object */ = { 136 | isa = PBXProject; 137 | attributes = { 138 | LastUpgradeCheck = 1300; 139 | ORGANIZATIONNAME = "The Chromium Authors"; 140 | TargetAttributes = { 141 | 97C146ED1CF9000F007C117D = { 142 | CreatedOnToolsVersion = 7.3.1; 143 | LastSwiftMigration = 1100; 144 | }; 145 | }; 146 | }; 147 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 148 | compatibilityVersion = "Xcode 3.2"; 149 | developmentRegion = en; 150 | hasScannedForEncodings = 0; 151 | knownRegions = ( 152 | en, 153 | Base, 154 | ); 155 | mainGroup = 97C146E51CF9000F007C117D; 156 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 157 | projectDirPath = ""; 158 | projectRoot = ""; 159 | targets = ( 160 | 97C146ED1CF9000F007C117D /* Runner */, 161 | ); 162 | }; 163 | /* End PBXProject section */ 164 | 165 | /* Begin PBXResourcesBuildPhase section */ 166 | 97C146EC1CF9000F007C117D /* Resources */ = { 167 | isa = PBXResourcesBuildPhase; 168 | buildActionMask = 2147483647; 169 | files = ( 170 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 171 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 172 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 173 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 174 | ); 175 | runOnlyForDeploymentPostprocessing = 0; 176 | }; 177 | /* End PBXResourcesBuildPhase section */ 178 | 179 | /* Begin PBXShellScriptBuildPhase section */ 180 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 181 | isa = PBXShellScriptBuildPhase; 182 | buildActionMask = 2147483647; 183 | files = ( 184 | ); 185 | inputPaths = ( 186 | ); 187 | name = "Thin Binary"; 188 | outputPaths = ( 189 | ); 190 | runOnlyForDeploymentPostprocessing = 0; 191 | shellPath = /bin/sh; 192 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 193 | }; 194 | 9740EEB61CF901F6004384FC /* Run Script */ = { 195 | isa = PBXShellScriptBuildPhase; 196 | buildActionMask = 2147483647; 197 | files = ( 198 | ); 199 | inputPaths = ( 200 | ); 201 | name = "Run Script"; 202 | outputPaths = ( 203 | ); 204 | runOnlyForDeploymentPostprocessing = 0; 205 | shellPath = /bin/sh; 206 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 207 | }; 208 | /* End PBXShellScriptBuildPhase section */ 209 | 210 | /* Begin PBXSourcesBuildPhase section */ 211 | 97C146EA1CF9000F007C117D /* Sources */ = { 212 | isa = PBXSourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 216 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | /* End PBXSourcesBuildPhase section */ 221 | 222 | /* Begin PBXVariantGroup section */ 223 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 224 | isa = PBXVariantGroup; 225 | children = ( 226 | 97C146FB1CF9000F007C117D /* Base */, 227 | ); 228 | name = Main.storyboard; 229 | sourceTree = ""; 230 | }; 231 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 232 | isa = PBXVariantGroup; 233 | children = ( 234 | 97C147001CF9000F007C117D /* Base */, 235 | ); 236 | name = LaunchScreen.storyboard; 237 | sourceTree = ""; 238 | }; 239 | /* End PBXVariantGroup section */ 240 | 241 | /* Begin XCBuildConfiguration section */ 242 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ALWAYS_SEARCH_USER_PATHS = NO; 246 | CLANG_ANALYZER_NONNULL = YES; 247 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 248 | CLANG_CXX_LIBRARY = "libc++"; 249 | CLANG_ENABLE_MODULES = YES; 250 | CLANG_ENABLE_OBJC_ARC = YES; 251 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 252 | CLANG_WARN_BOOL_CONVERSION = YES; 253 | CLANG_WARN_COMMA = YES; 254 | CLANG_WARN_CONSTANT_CONVERSION = YES; 255 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 256 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 257 | CLANG_WARN_EMPTY_BODY = YES; 258 | CLANG_WARN_ENUM_CONVERSION = YES; 259 | CLANG_WARN_INFINITE_RECURSION = YES; 260 | CLANG_WARN_INT_CONVERSION = YES; 261 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 262 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 263 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 264 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 265 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 266 | CLANG_WARN_STRICT_PROTOTYPES = YES; 267 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 268 | CLANG_WARN_UNREACHABLE_CODE = YES; 269 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 270 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 271 | COPY_PHASE_STRIP = NO; 272 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 273 | ENABLE_NS_ASSERTIONS = NO; 274 | ENABLE_STRICT_OBJC_MSGSEND = YES; 275 | GCC_C_LANGUAGE_STANDARD = gnu99; 276 | GCC_NO_COMMON_BLOCKS = YES; 277 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 278 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 279 | GCC_WARN_UNDECLARED_SELECTOR = YES; 280 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 281 | GCC_WARN_UNUSED_FUNCTION = YES; 282 | GCC_WARN_UNUSED_VARIABLE = YES; 283 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 284 | MTL_ENABLE_DEBUG_INFO = NO; 285 | SDKROOT = iphoneos; 286 | SUPPORTED_PLATFORMS = iphoneos; 287 | TARGETED_DEVICE_FAMILY = "1,2"; 288 | VALIDATE_PRODUCT = YES; 289 | }; 290 | name = Profile; 291 | }; 292 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 293 | isa = XCBuildConfiguration; 294 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 295 | buildSettings = { 296 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 297 | CLANG_ENABLE_MODULES = YES; 298 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 299 | ENABLE_BITCODE = NO; 300 | FRAMEWORK_SEARCH_PATHS = ( 301 | "$(inherited)", 302 | "$(PROJECT_DIR)/Flutter", 303 | ); 304 | INFOPLIST_FILE = Runner/Info.plist; 305 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 306 | LIBRARY_SEARCH_PATHS = ( 307 | "$(inherited)", 308 | "$(PROJECT_DIR)/Flutter", 309 | ); 310 | PRODUCT_BUNDLE_IDENTIFIER = com.example.sample; 311 | PRODUCT_NAME = "$(TARGET_NAME)"; 312 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 313 | SWIFT_VERSION = 5.0; 314 | VERSIONING_SYSTEM = "apple-generic"; 315 | }; 316 | name = Profile; 317 | }; 318 | 97C147031CF9000F007C117D /* Debug */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_ANALYZER_NONNULL = YES; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 324 | CLANG_CXX_LIBRARY = "libc++"; 325 | CLANG_ENABLE_MODULES = YES; 326 | CLANG_ENABLE_OBJC_ARC = YES; 327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_COMMA = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_EMPTY_BODY = YES; 334 | CLANG_WARN_ENUM_CONVERSION = YES; 335 | CLANG_WARN_INFINITE_RECURSION = YES; 336 | CLANG_WARN_INT_CONVERSION = YES; 337 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 338 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 339 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 341 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 342 | CLANG_WARN_STRICT_PROTOTYPES = YES; 343 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 344 | CLANG_WARN_UNREACHABLE_CODE = YES; 345 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 346 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 347 | COPY_PHASE_STRIP = NO; 348 | DEBUG_INFORMATION_FORMAT = dwarf; 349 | ENABLE_STRICT_OBJC_MSGSEND = YES; 350 | ENABLE_TESTABILITY = YES; 351 | GCC_C_LANGUAGE_STANDARD = gnu99; 352 | GCC_DYNAMIC_NO_PIC = NO; 353 | GCC_NO_COMMON_BLOCKS = YES; 354 | GCC_OPTIMIZATION_LEVEL = 0; 355 | GCC_PREPROCESSOR_DEFINITIONS = ( 356 | "DEBUG=1", 357 | "$(inherited)", 358 | ); 359 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 360 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 361 | GCC_WARN_UNDECLARED_SELECTOR = YES; 362 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 363 | GCC_WARN_UNUSED_FUNCTION = YES; 364 | GCC_WARN_UNUSED_VARIABLE = YES; 365 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 366 | MTL_ENABLE_DEBUG_INFO = YES; 367 | ONLY_ACTIVE_ARCH = YES; 368 | SDKROOT = iphoneos; 369 | TARGETED_DEVICE_FAMILY = "1,2"; 370 | }; 371 | name = Debug; 372 | }; 373 | 97C147041CF9000F007C117D /* Release */ = { 374 | isa = XCBuildConfiguration; 375 | buildSettings = { 376 | ALWAYS_SEARCH_USER_PATHS = NO; 377 | CLANG_ANALYZER_NONNULL = YES; 378 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 379 | CLANG_CXX_LIBRARY = "libc++"; 380 | CLANG_ENABLE_MODULES = YES; 381 | CLANG_ENABLE_OBJC_ARC = YES; 382 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 383 | CLANG_WARN_BOOL_CONVERSION = YES; 384 | CLANG_WARN_COMMA = YES; 385 | CLANG_WARN_CONSTANT_CONVERSION = YES; 386 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 387 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 388 | CLANG_WARN_EMPTY_BODY = YES; 389 | CLANG_WARN_ENUM_CONVERSION = YES; 390 | CLANG_WARN_INFINITE_RECURSION = YES; 391 | CLANG_WARN_INT_CONVERSION = YES; 392 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 393 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 394 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 395 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 396 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 397 | CLANG_WARN_STRICT_PROTOTYPES = YES; 398 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 399 | CLANG_WARN_UNREACHABLE_CODE = YES; 400 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 401 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 402 | COPY_PHASE_STRIP = NO; 403 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 404 | ENABLE_NS_ASSERTIONS = NO; 405 | ENABLE_STRICT_OBJC_MSGSEND = YES; 406 | GCC_C_LANGUAGE_STANDARD = gnu99; 407 | GCC_NO_COMMON_BLOCKS = YES; 408 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 409 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 410 | GCC_WARN_UNDECLARED_SELECTOR = YES; 411 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 412 | GCC_WARN_UNUSED_FUNCTION = YES; 413 | GCC_WARN_UNUSED_VARIABLE = YES; 414 | IPHONEOS_DEPLOYMENT_TARGET = 11.0; 415 | MTL_ENABLE_DEBUG_INFO = NO; 416 | SDKROOT = iphoneos; 417 | SUPPORTED_PLATFORMS = iphoneos; 418 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 419 | TARGETED_DEVICE_FAMILY = "1,2"; 420 | VALIDATE_PRODUCT = YES; 421 | }; 422 | name = Release; 423 | }; 424 | 97C147061CF9000F007C117D /* Debug */ = { 425 | isa = XCBuildConfiguration; 426 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 427 | buildSettings = { 428 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 429 | CLANG_ENABLE_MODULES = YES; 430 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 431 | ENABLE_BITCODE = NO; 432 | FRAMEWORK_SEARCH_PATHS = ( 433 | "$(inherited)", 434 | "$(PROJECT_DIR)/Flutter", 435 | ); 436 | INFOPLIST_FILE = Runner/Info.plist; 437 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 438 | LIBRARY_SEARCH_PATHS = ( 439 | "$(inherited)", 440 | "$(PROJECT_DIR)/Flutter", 441 | ); 442 | PRODUCT_BUNDLE_IDENTIFIER = com.example.sample; 443 | PRODUCT_NAME = "$(TARGET_NAME)"; 444 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 445 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 446 | SWIFT_VERSION = 5.0; 447 | VERSIONING_SYSTEM = "apple-generic"; 448 | }; 449 | name = Debug; 450 | }; 451 | 97C147071CF9000F007C117D /* Release */ = { 452 | isa = XCBuildConfiguration; 453 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 454 | buildSettings = { 455 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 456 | CLANG_ENABLE_MODULES = YES; 457 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 458 | ENABLE_BITCODE = NO; 459 | FRAMEWORK_SEARCH_PATHS = ( 460 | "$(inherited)", 461 | "$(PROJECT_DIR)/Flutter", 462 | ); 463 | INFOPLIST_FILE = Runner/Info.plist; 464 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 465 | LIBRARY_SEARCH_PATHS = ( 466 | "$(inherited)", 467 | "$(PROJECT_DIR)/Flutter", 468 | ); 469 | PRODUCT_BUNDLE_IDENTIFIER = com.example.sample; 470 | PRODUCT_NAME = "$(TARGET_NAME)"; 471 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 472 | SWIFT_VERSION = 5.0; 473 | VERSIONING_SYSTEM = "apple-generic"; 474 | }; 475 | name = Release; 476 | }; 477 | /* End XCBuildConfiguration section */ 478 | 479 | /* Begin XCConfigurationList section */ 480 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 481 | isa = XCConfigurationList; 482 | buildConfigurations = ( 483 | 97C147031CF9000F007C117D /* Debug */, 484 | 97C147041CF9000F007C117D /* Release */, 485 | 249021D3217E4FDB00AE95B9 /* Profile */, 486 | ); 487 | defaultConfigurationIsVisible = 0; 488 | defaultConfigurationName = Release; 489 | }; 490 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 491 | isa = XCConfigurationList; 492 | buildConfigurations = ( 493 | 97C147061CF9000F007C117D /* Debug */, 494 | 97C147071CF9000F007C117D /* Release */, 495 | 249021D4217E4FDB00AE95B9 /* Profile */, 496 | ); 497 | defaultConfigurationIsVisible = 0; 498 | defaultConfigurationName = Release; 499 | }; 500 | /* End XCConfigurationList section */ 501 | }; 502 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 503 | } 504 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | sample 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | CADisableMinimumFrameDurationOnPhone 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/linear_tab_bar_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_tab_bar/library.dart'; 3 | 4 | import 'page_item.dart'; 5 | 6 | class LinearTabBarPage extends StatefulWidget { 7 | LinearTabBarPage({Key? key}) : super(key: key); 8 | 9 | @override 10 | _LinearTabBarPageState createState() => _LinearTabBarPageState(); 11 | } 12 | 13 | class _LinearTabBarPageState extends State { 14 | final int pageCount = 20; 15 | final PageController _controller = PageController(initialPage: 10); 16 | CustomTabBarController _tabBarController = CustomTabBarController(); 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | } 22 | 23 | Widget getTabbarChild(BuildContext context, int index) { 24 | return TabBarItem( 25 | index: index, 26 | transform: ColorsTransform( 27 | highlightColor: Colors.pink, 28 | normalColor: Colors.black, 29 | builder: (context, color) { 30 | return Container( 31 | padding: EdgeInsets.all(2), 32 | alignment: Alignment.center, 33 | constraints: BoxConstraints(minWidth: 60), 34 | child: (Text( 35 | index == 5 ? 'Tab555555555555' : 'Tab$index', 36 | style: TextStyle(fontSize: 14, color: color), 37 | )), 38 | ); 39 | }), 40 | ); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Scaffold( 46 | appBar: AppBar(title: Text('Linear Indicator')), 47 | body: Column( 48 | children: [ 49 | CustomTabBar( 50 | tabBarController: _tabBarController, 51 | height: 35, 52 | itemCount: pageCount, 53 | builder: getTabbarChild, 54 | indicator: LinearIndicator(color: Colors.pink, bottom: 5), 55 | pageController: _controller, 56 | ), 57 | Expanded( 58 | child: PageView.builder( 59 | controller: _controller, 60 | itemCount: pageCount, 61 | itemBuilder: (context, index) { 62 | return PageItem(index); 63 | })) 64 | ], 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:example/pinned_linear_tab_bar_page.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | 5 | import 'linear_tab_bar_page.dart'; 6 | import 'round_tab_bar_page.dart'; 7 | import 'standard_tab_bar_page.dart'; 8 | import 'vertical_round_tab_bar_page.dart'; 9 | 10 | void main() { 11 | runApp(MyApp()); 12 | } 13 | 14 | class MyApp extends StatelessWidget { 15 | // This widget is the root of your application. 16 | @override 17 | Widget build(BuildContext context) { 18 | return MaterialApp( 19 | title: 'Flutter Demo', 20 | theme: ThemeData( 21 | primarySwatch: Colors.blue, 22 | visualDensity: VisualDensity.adaptivePlatformDensity, 23 | ), 24 | home: MyHomePage(title: 'Flutter Demo Home Page'), 25 | ); 26 | } 27 | } 28 | 29 | class MyHomePage extends StatefulWidget { 30 | MyHomePage({Key? key, this.title}) : super(key: key); 31 | final String? title; 32 | 33 | @override 34 | _MyHomePageState createState() => _MyHomePageState(); 35 | } 36 | 37 | class _MyHomePageState extends State { 38 | Widget _buildItem(BuildContext context, String title, Widget widget) { 39 | return InkWell( 40 | onTap: () { 41 | Navigator.of(context) 42 | .push(MaterialPageRoute(builder: (context) => widget)); 43 | }, 44 | child: Container( 45 | padding: EdgeInsets.all(10), 46 | child: Text(title), 47 | ), 48 | ); 49 | } 50 | 51 | @override 52 | void initState() { 53 | super.initState(); 54 | } 55 | 56 | @override 57 | Widget build(BuildContext context) { 58 | return Scaffold( 59 | appBar: AppBar( 60 | title: Text(widget.title!), 61 | ), 62 | body: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ 63 | _buildItem(context, "Standard Tab Bar", StandardTabBarPage()), 64 | _buildItem(context, "Linear Tab Bar", LinearTabBarPage()), 65 | _buildItem( 66 | context, "Pinned Linear Tab Bar", PinnedLinearTabBarPage()), 67 | _buildItem(context, "Round Tab Bar", RoundTabBarPage()), 68 | _buildItem( 69 | context, "Vertical Round Tab Bar", VerticalRoundTabBarPage()), 70 | ])); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /example/lib/page_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class PageItem extends StatefulWidget { 4 | final int index; 5 | PageItem(this.index, {Key? key}) : super(key: key); 6 | 7 | @override 8 | _PageItemState createState() => _PageItemState(); 9 | } 10 | 11 | class _PageItemState extends State 12 | with AutomaticKeepAliveClientMixin { 13 | @override 14 | Widget build(BuildContext context) { 15 | super.build(context); 16 | print('build index:${widget.index} page'); 17 | return Container( 18 | // color: Colors.pink, 19 | child: Text('index:${widget.index}'), 20 | alignment: Alignment.center, 21 | ); 22 | } 23 | 24 | @override 25 | // bool get wantKeepAlive => false; 26 | bool get wantKeepAlive => true; 27 | } 28 | -------------------------------------------------------------------------------- /example/lib/pinned_linear_tab_bar_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_tab_bar/library.dart'; 3 | 4 | import 'page_item.dart'; 5 | 6 | class PinnedLinearTabBarPage extends StatefulWidget { 7 | PinnedLinearTabBarPage({Key? key}) : super(key: key); 8 | 9 | @override 10 | _PinnedLinearTabBarPageState createState() => _PinnedLinearTabBarPageState(); 11 | } 12 | 13 | class _PinnedLinearTabBarPageState extends State { 14 | PageController pageController = PageController(); 15 | CustomTabBarController _tabBarController = CustomTabBarController(); 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | } 21 | 22 | Widget getTabbarChild(BuildContext context, int index) { 23 | return TabBarItem( 24 | index: index, 25 | transform: ScaleTransform( 26 | maxScale: 1.3, 27 | transform: ColorsTransform( 28 | normalColor: Colors.black, 29 | highlightColor: Colors.green, 30 | builder: (context, color) { 31 | return Container( 32 | padding: EdgeInsets.all(12), 33 | alignment: Alignment.center, 34 | constraints: BoxConstraints(minWidth: 70), 35 | child: (Text( 36 | index == 5 ? 'Tab555' : 'Tab$index', 37 | style: TextStyle( 38 | fontSize: 14, 39 | color: color, 40 | ), 41 | ))); 42 | }, 43 | ))); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return Scaffold( 49 | appBar: AppBar(title: Text('Pinned Linear Indicator')), 50 | body: Column( 51 | children: [ 52 | CustomTabBar( 53 | tabBarController: _tabBarController, 54 | builder: getTabbarChild, 55 | pinned: true, 56 | width: 140, 57 | // height: 50, 58 | pageController: pageController, 59 | indicator: LinearIndicator( 60 | color: Colors.blue, 61 | height: 3, 62 | bottom: 5, 63 | width: 20, 64 | radius: BorderRadius.circular(2)), 65 | itemCount: 2), 66 | Expanded( 67 | child: PageView( 68 | children: [ 69 | PageItem(0), 70 | PageItem(1), 71 | ], 72 | controller: pageController, 73 | )) 74 | ], 75 | )); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /example/lib/round_tab_bar_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_tab_bar/library.dart'; 3 | 4 | import 'page_item.dart'; 5 | 6 | class RoundTabBarPage extends StatefulWidget { 7 | RoundTabBarPage({Key? key}) : super(key: key); 8 | 9 | @override 10 | _RoundTabBarPageState createState() => _RoundTabBarPageState(); 11 | } 12 | 13 | class _RoundTabBarPageState extends State { 14 | final int pageCount = 4; 15 | late PageController _controller = PageController(initialPage: 3); 16 | CustomTabBarController _tabBarController = CustomTabBarController(); 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | } 22 | 23 | Widget getTabbarChild(BuildContext context, int index) { 24 | return TabBarItem( 25 | transform: ColorsTransform( 26 | highlightColor: Colors.white, 27 | normalColor: Colors.black, 28 | builder: (context, color) { 29 | return Container( 30 | padding: EdgeInsets.fromLTRB(10, 2, 10, 2), 31 | alignment: Alignment.center, 32 | constraints: BoxConstraints(minWidth: 60), 33 | child: (Text( 34 | index == 2 ? 'Tab522222' : 'Tab$index', 35 | style: TextStyle(fontSize: 14, color: color), 36 | )), 37 | ); 38 | }), 39 | index: index); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return Scaffold( 45 | appBar: AppBar(title: Text('Round Indicator')), 46 | body: Column( 47 | children: [ 48 | CustomTabBar( 49 | tabBarController: _tabBarController, 50 | height: 35, 51 | itemCount: pageCount, 52 | builder: getTabbarChild, 53 | indicator: RoundIndicator( 54 | color: Colors.red, 55 | top: 2.5, 56 | bottom: 2.5, 57 | left: 2.5, 58 | right: 2.5, 59 | radius: BorderRadius.circular(15), 60 | ), 61 | pageController: _controller, 62 | ), 63 | Expanded( 64 | child: PageView.builder( 65 | controller: _controller, 66 | itemCount: pageCount, 67 | itemBuilder: (context, index) { 68 | return PageItem(index); 69 | })), 70 | TextButton( 71 | onPressed: () { 72 | _tabBarController.animateToIndex(3); 73 | }, 74 | child: Text('gogogo')) 75 | ], 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/lib/standard_tab_bar_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_tab_bar/library.dart'; 3 | 4 | import 'page_item.dart'; 5 | 6 | class StandardTabBarPage extends StatefulWidget { 7 | StandardTabBarPage({Key? key}) : super(key: key); 8 | 9 | @override 10 | _StandardTabBarPageState createState() => _StandardTabBarPageState(); 11 | } 12 | 13 | class _StandardTabBarPageState extends State { 14 | final int pageCount = 20; 15 | final PageController _controller = PageController(); 16 | 17 | CustomTabBarController _tabBarController = CustomTabBarController(); 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | } 23 | 24 | Widget getTabbarChild(BuildContext context, int index) { 25 | return TabBarItem( 26 | index: index, 27 | transform: ScaleTransform( 28 | maxScale: 1.3, 29 | transform: ColorsTransform( 30 | normalColor: Colors.black, 31 | highlightColor: Colors.green, 32 | builder: (context, color) { 33 | return Container( 34 | padding: EdgeInsets.all(2), 35 | alignment: Alignment.center, 36 | constraints: BoxConstraints(minWidth: 70), 37 | child: (Text( 38 | index == 2 ? 'Tab2222' : 'Tab$index', 39 | style: TextStyle( 40 | fontSize: 14, 41 | color: color, 42 | ), 43 | ))); 44 | }, 45 | ))); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Scaffold( 51 | appBar: AppBar(title: Text('Standard Indicator')), 52 | body: Column( 53 | children: [ 54 | CustomTabBar( 55 | tabBarController: _tabBarController, 56 | height: 35, 57 | itemCount: pageCount, 58 | builder: getTabbarChild, 59 | indicator: StandardIndicator( 60 | width: 20, 61 | height: 3, 62 | color: Colors.green, 63 | ), 64 | pageController: _controller, 65 | ), 66 | Expanded( 67 | child: PageView.builder( 68 | controller: _controller, 69 | itemCount: pageCount, 70 | itemBuilder: (context, index) { 71 | return PageItem(index); 72 | })) 73 | ], 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /example/lib/vertical_round_tab_bar_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_tab_bar/library.dart'; 3 | 4 | import 'page_item.dart'; 5 | 6 | class VerticalRoundTabBarPage extends StatefulWidget { 7 | VerticalRoundTabBarPage({Key? key}) : super(key: key); 8 | 9 | @override 10 | _VerticalRoundTabBarPageState createState() => 11 | _VerticalRoundTabBarPageState(); 12 | } 13 | 14 | class _VerticalRoundTabBarPageState extends State { 15 | final int pageCount = 30; 16 | late PageController _controller = PageController(initialPage: 0); 17 | CustomTabBarController _tabBarController = CustomTabBarController(); 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | } 23 | 24 | Widget getTabbarChild(BuildContext context, int index) { 25 | return TabBarItem( 26 | transform: ColorsTransform( 27 | highlightColor: Colors.white, 28 | normalColor: Colors.black, 29 | builder: (context, color) { 30 | return Container( 31 | padding: EdgeInsets.fromLTRB(10, 2, 10, 2), 32 | alignment: Alignment.center, 33 | constraints: BoxConstraints(minHeight: 35), 34 | child: (Text( 35 | index == 2 ? 'Tab22222222222222222' : 'Tab$index', 36 | style: TextStyle(fontSize: 14, color: color), 37 | )), 38 | ); 39 | }), 40 | index: index); 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Scaffold( 46 | appBar: AppBar(title: Text('Vertical Round Indicator')), 47 | body: Row( 48 | children: [ 49 | CustomTabBar( 50 | tabBarController: _tabBarController, 51 | width: 80, 52 | direction: Axis.vertical, 53 | itemCount: pageCount, 54 | builder: getTabbarChild, 55 | indicator: RoundIndicator( 56 | color: Colors.red, 57 | top: 2.5, 58 | bottom: 2.5, 59 | left: 2.5, 60 | right: 2.5, 61 | radius: BorderRadius.circular(5), 62 | ), 63 | pageController: _controller, 64 | ), 65 | Expanded( 66 | child: PageView.builder( 67 | scrollDirection: Axis.vertical, 68 | controller: _controller, 69 | itemCount: pageCount, 70 | itemBuilder: (context, index) { 71 | return PageItem(index); 72 | })), 73 | ], 74 | ), 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.9.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.1" 25 | clock: 26 | dependency: transitive 27 | description: 28 | name: clock 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.1" 32 | collection: 33 | dependency: transitive 34 | description: 35 | name: collection 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.16.0" 39 | cupertino_icons: 40 | dependency: "direct main" 41 | description: 42 | name: cupertino_icons 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.0.4" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.3.1" 53 | flutter: 54 | dependency: "direct main" 55 | description: flutter 56 | source: sdk 57 | version: "0.0.0" 58 | flutter_custom_tab_bar: 59 | dependency: "direct main" 60 | description: 61 | path: ".." 62 | relative: true 63 | source: path 64 | version: "1.2.1" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | matcher: 71 | dependency: transitive 72 | description: 73 | name: matcher 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "0.12.12" 77 | material_color_utilities: 78 | dependency: transitive 79 | description: 80 | name: material_color_utilities 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "0.1.5" 84 | meta: 85 | dependency: transitive 86 | description: 87 | name: meta 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.8.0" 91 | path: 92 | dependency: transitive 93 | description: 94 | name: path 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "1.8.2" 98 | sky_engine: 99 | dependency: transitive 100 | description: flutter 101 | source: sdk 102 | version: "0.0.99" 103 | source_span: 104 | dependency: transitive 105 | description: 106 | name: source_span 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.9.0" 110 | stack_trace: 111 | dependency: transitive 112 | description: 113 | name: stack_trace 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.10.0" 117 | stream_channel: 118 | dependency: transitive 119 | description: 120 | name: stream_channel 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "2.1.0" 124 | string_scanner: 125 | dependency: transitive 126 | description: 127 | name: string_scanner 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.1.1" 131 | term_glyph: 132 | dependency: transitive 133 | description: 134 | name: term_glyph 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "1.2.1" 138 | test_api: 139 | dependency: transitive 140 | description: 141 | name: test_api 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "0.4.12" 145 | vector_math: 146 | dependency: transitive 147 | description: 148 | name: vector_math 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "2.1.2" 152 | sdks: 153 | dart: ">=2.17.0-0 <3.0.0" 154 | flutter: ">=1.17.0" 155 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.12.0 <3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^1.0.2 26 | flutter_custom_tab_bar: 27 | path: ../ 28 | 29 | dev_dependencies: 30 | flutter_test: 31 | sdk: flutter 32 | 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 | # - images/a_dot_burr.jpeg 48 | # - images/a_dot_ham.jpeg 49 | 50 | # An image asset can refer to one or more resolution-specific "variants", see 51 | # https://flutter.dev/assets-and-images/#resolution-aware. 52 | 53 | # For details regarding adding assets from package dependencies, see 54 | # https://flutter.dev/assets-and-images/#from-packages 55 | 56 | # To add custom fonts to your application, add a fonts section here, 57 | # in this "flutter" section. Each entry in this list should have a 58 | # "family" key with the font family name, and a "fonts" key with a 59 | # list giving the asset and other descriptors for the font. For 60 | # example: 61 | # fonts: 62 | # - family: Schyler 63 | # fonts: 64 | # - asset: fonts/Schyler-Regular.ttf 65 | # - asset: fonts/Schyler-Italic.ttf 66 | # style: italic 67 | # - family: Trajan Pro 68 | # fonts: 69 | # - asset: fonts/TrajanPro.ttf 70 | # - asset: fonts/TrajanPro_Bold.ttf 71 | # weight: 700 72 | # 73 | # For details regarding fonts from package dependencies, 74 | # see https://flutter.dev/custom-fonts/#from-packages 75 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import '../lib/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lazyee/flutter_custom_tab_bar/1c43ab673586a9efb1eb729f47d8121e2cc043de/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | example 33 | 34 | 35 | 36 | 39 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /lib/custom_tab_bar.dart: -------------------------------------------------------------------------------- 1 | library flutter_custom_tab_bar; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/rendering.dart'; 5 | import 'package:flutter_custom_tab_bar/library.dart'; 6 | 7 | final Duration kCustomerTabBarAnimDuration = Duration(milliseconds: 300); 8 | 9 | typedef IndexedTabBarItemBuilder = Widget Function( 10 | BuildContext context, int index); 11 | 12 | class CustomTabBarContext extends InheritedWidget { 13 | final ValueNotifier progressNotifier = 14 | ValueNotifier(ScrollProgressInfo()); 15 | 16 | CustomTabBarContext({required Widget child, Key? key}) 17 | : super(child: child, key: key); 18 | 19 | @override 20 | bool updateShouldNotify(covariant InheritedWidget oldWidget) { 21 | return true; 22 | } 23 | 24 | static CustomTabBarContext? of(BuildContext context) { 25 | return context.dependOnInheritedWidgetOfExactType( 26 | aspect: CustomTabBarContext); 27 | } 28 | } 29 | 30 | class CustomTabBar extends StatelessWidget { 31 | final Axis direction; 32 | final IndexedTabBarItemBuilder builder; 33 | final int itemCount; 34 | final PageController pageController; 35 | final CustomIndicator? indicator; 36 | final ValueChanged? onTapItem; 37 | final double? height; 38 | final double? width; 39 | final bool pinned; 40 | final bool controlJump; 41 | final CustomTabBarController? tabBarController; 42 | const CustomTabBar( 43 | {required this.builder, 44 | required this.itemCount, 45 | required this.pageController, 46 | this.height, 47 | this.direction = Axis.horizontal, 48 | this.onTapItem, 49 | this.indicator, 50 | this.tabBarController, 51 | this.width, 52 | this.pinned = false, 53 | this.controlJump = true, 54 | Key? key}) 55 | : assert( 56 | direction == Axis.horizontal || 57 | (direction == Axis.vertical && indicator is RoundIndicator), 58 | "vertical direction only support RoundIndicator"), 59 | assert( 60 | direction == Axis.horizontal || 61 | (direction == Axis.vertical && width != null), 62 | "vertical direction must set width property"), 63 | assert(pinned == true || 64 | (pinned == false && 65 | (direction == Axis.vertical || height != null))), 66 | super(key: key); 67 | 68 | @override 69 | Widget build(BuildContext context) { 70 | return CustomTabBarContext( 71 | child: _CustomTabBar( 72 | direction: direction, 73 | onTapItem: onTapItem, 74 | controlJump: controlJump, 75 | indicator: indicator, 76 | tabBarController: tabBarController, 77 | width: width, 78 | height: height, 79 | pinned: pinned, 80 | builder: builder, 81 | itemCount: itemCount, 82 | pageController: pageController)); 83 | } 84 | } 85 | 86 | class _CustomTabBar extends StatefulWidget { 87 | final IndexedTabBarItemBuilder builder; 88 | final int itemCount; 89 | final PageController pageController; 90 | final CustomIndicator? indicator; 91 | final ValueChanged? onTapItem; 92 | final double? height; 93 | final double? width; 94 | final bool pinned; 95 | final bool controlJump; 96 | final CustomTabBarController? tabBarController; 97 | final Axis direction; 98 | 99 | const _CustomTabBar( 100 | {required this.builder, 101 | required this.itemCount, 102 | required this.pageController, 103 | this.direction = Axis.horizontal, 104 | this.height, 105 | this.onTapItem, 106 | this.tabBarController, 107 | this.controlJump = true, 108 | this.indicator, 109 | this.width, 110 | this.pinned = false, 111 | Key? key}) 112 | : super(key: key); 113 | 114 | @override 115 | _CustomTabBarState createState() => _CustomTabBarState(); 116 | } 117 | 118 | class _CustomTabBarState extends State<_CustomTabBar> 119 | with TickerProviderStateMixin { 120 | late List sizeList = 121 | List.generate(widget.itemCount, (index) => Size(0, 0)); 122 | ScrollController? _scrollController; 123 | late CustomTabBarController _tabBarController = 124 | widget.tabBarController ?? CustomTabBarController(); 125 | late int _currentIndex = widget.pageController.initialPage; 126 | ValueNotifier positionNotifier = 127 | ValueNotifier(IndicatorPosition(0, 0, 0, 0)); 128 | ValueNotifier? get progressNotifier => 129 | CustomTabBarContext.of(context)?.progressNotifier; 130 | // late ValueNotifier? progressNotifier = 131 | // CustomTabBarContext.of(context)?.progressNotifier; 132 | double get getCurrentPage => widget.pageController.page ?? 0; 133 | 134 | double indicatorLeft = 0; 135 | double indicatorRight = 0; 136 | double? indicatorTop; 137 | double indicatorBottom = 0; 138 | 139 | void _init() { 140 | for (int i = sizeList.length; i < widget.itemCount; i++) { 141 | sizeList.add(Size(0, 0)); 142 | } 143 | 144 | _tabBarController.setOrientation(widget.direction); 145 | _tabBarController.setAnimateToIndexCallback(_animateToIndex); 146 | widget.indicator?.controller = _tabBarController; 147 | } 148 | 149 | @override 150 | void didUpdateWidget(covariant _CustomTabBar oldWidget) { 151 | super.didUpdateWidget(oldWidget); 152 | _init(); 153 | progressNotifier?.value = ScrollProgressInfo(currentIndex: _currentIndex); 154 | } 155 | 156 | @override 157 | void initState() { 158 | super.initState(); 159 | 160 | _init(); 161 | 162 | positionNotifier.addListener(() { 163 | setState(() { 164 | indicatorLeft = 165 | positionNotifier.value.left + (widget.indicator?.left ?? 0); 166 | indicatorRight = 167 | positionNotifier.value.right + (widget.indicator?.right ?? 0); 168 | 169 | indicatorBottom = 170 | positionNotifier.value.bottom + (widget.indicator?.bottom ?? 0); 171 | 172 | if (widget.direction == Axis.vertical || 173 | widget.indicator?.top != null) { 174 | indicatorTop = 175 | positionNotifier.value.top + (widget.indicator?.top ?? 0); 176 | } 177 | }); 178 | }); 179 | 180 | Future.delayed(Duration.zero, () { 181 | progressNotifier?.value = ScrollProgressInfo(currentIndex: _currentIndex); 182 | }); 183 | 184 | if (!widget.pinned) { 185 | _scrollController = ScrollController(); 186 | } 187 | 188 | widget.pageController.addListener(() { 189 | if (_tabBarController.isJumpToTarget) return; 190 | if (_currentIndex == getCurrentPage) return; 191 | _currentIndex = getCurrentPage.toInt(); 192 | 193 | _tabBarController.scrollByPageView(_viewportSize / 2, sizeList, 194 | _scrollController, widget.pageController); 195 | 196 | ScrollProgressInfo? scrollProgressInfo = 197 | _tabBarController.calculateScrollProgressByPageView( 198 | _currentIndex, widget.pageController); 199 | if (scrollProgressInfo != null) { 200 | progressNotifier?.value = scrollProgressInfo; 201 | } 202 | 203 | widget.indicator?.updateScrollIndicator(getCurrentPage, sizeList, 204 | kCustomerTabBarAnimDuration, positionNotifier); 205 | }); 206 | } 207 | 208 | Size _viewportSize = Size(-1, -1); 209 | 210 | @override 211 | void dispose() { 212 | super.dispose(); 213 | progressAnimationController?.stop(canceled: true); 214 | } 215 | 216 | @override 217 | Widget build(BuildContext context) { 218 | late Widget child; 219 | if (widget.pinned && widget.direction == Axis.horizontal) { 220 | //使用外部传入的宽度 221 | assert(widget.width != null, 'width must set value on pinned is true'); 222 | if (_viewportSize.width != widget.width) { 223 | _viewportSize = Size(widget.width!, _viewportSize.height); 224 | } 225 | 226 | child = _buildTabBarItemList(); 227 | } else { 228 | child = Scrollable( 229 | controller: _scrollController, 230 | viewportBuilder: _buildViewport, 231 | axisDirection: widget.direction == Axis.horizontal 232 | ? AxisDirection.right 233 | : AxisDirection.down, 234 | physics: widget.pinned 235 | ? NeverScrollableScrollPhysics() 236 | : BouncingScrollPhysics(), 237 | ); 238 | } 239 | return MeasureSizeBox( 240 | child: Container( 241 | width: widget.width, 242 | height: widget.direction == Axis.horizontal ? widget.height : null, 243 | child: child), 244 | onSizeCallback: (size) { 245 | if (_viewportSize != size) { 246 | _viewportSize = Size.copy(size); 247 | } 248 | }, 249 | ); 250 | } 251 | 252 | Widget _buildViewport(BuildContext context, ViewportOffset offset) { 253 | return Viewport( 254 | offset: offset, 255 | axisDirection: widget.direction == Axis.horizontal 256 | ? AxisDirection.right 257 | : AxisDirection.down, 258 | slivers: [_buildSlivers()], 259 | ); 260 | } 261 | 262 | ///点击tabbar Item 263 | void _onTapItem(int index) { 264 | if (_currentIndex == index) return; 265 | widget.onTapItem?.call(index); 266 | _animateToIndex(index); 267 | } 268 | 269 | void _animateToIndex(int index) { 270 | if (_currentIndex == index) return; 271 | _tabBarController.setCurrentIndex(index); 272 | _tabBarController.startJump(); 273 | if (widget.controlJump) { 274 | widget.pageController.animateToPage(index, 275 | duration: kCustomerTabBarAnimDuration, curve: Curves.easeIn); 276 | } 277 | updateProgressByAnimation(_currentIndex, index); 278 | _tabBarController.scrollTargetToCenter( 279 | _viewportSize / 2, index, sizeList, _scrollController, 280 | duration: kCustomerTabBarAnimDuration); 281 | 282 | widget.indicator?.indicatorScrollToIndex( 283 | index, sizeList, kCustomerTabBarAnimDuration, this, positionNotifier); 284 | 285 | _currentIndex = index; 286 | } 287 | 288 | AnimationController? progressAnimationController; 289 | Animation? progressAnimation; 290 | 291 | ///通过动画更新进度 292 | void updateProgressByAnimation(int currentIndex, int targetIndex) { 293 | progressAnimationController = 294 | AnimationController(vsync: this, duration: kCustomerTabBarAnimDuration); 295 | Animation animation = Tween(begin: 0.0, end: 1.0) 296 | .animate(progressAnimationController!); 297 | 298 | animation.addListener(() { 299 | if (!mounted) return null; 300 | 301 | progressNotifier?.value = ScrollProgressInfo( 302 | progress: animation.value, 303 | currentIndex: currentIndex, 304 | targetIndex: targetIndex); 305 | }); 306 | animation.addStatusListener((status) { 307 | if (status == AnimationStatus.completed) { 308 | _tabBarController.endJump(); 309 | } 310 | }); 311 | progressAnimationController?.forward(); 312 | } 313 | 314 | ///是否已经测量TabBarItem的size 315 | bool isMeasureTabBarItemSize() { 316 | for (int i = 0; i < sizeList.length; i++) { 317 | if (sizeList[i].width == 0) { 318 | return false; 319 | } 320 | } 321 | 322 | return true; 323 | } 324 | 325 | //构建指示器 326 | Widget _buildIndicator() { 327 | if (!isMeasureTabBarItemSize()) return SizedBox(); 328 | return Positioned( 329 | key: widget.key, 330 | left: indicatorLeft, 331 | right: indicatorRight, 332 | top: indicatorTop, 333 | bottom: indicatorBottom, 334 | child: Container( 335 | width: widget.indicator?.width, 336 | height: widget.indicator?.height, 337 | decoration: BoxDecoration( 338 | color: widget.indicator?.color, 339 | borderRadius: widget.indicator?.radius, 340 | ), 341 | ), 342 | ); 343 | } 344 | 345 | Widget _buildTabBarItemList() { 346 | return Stack(children: [ 347 | if (widget.indicator != null) _buildIndicator(), 348 | TabBarItemList( 349 | direction: widget.direction, 350 | viewPortWidth: widget.pinned 351 | ? (_viewportSize.width == -1 ? null : _viewportSize.width) 352 | : null, 353 | physics: widget.pinned 354 | ? NeverScrollableScrollPhysics() 355 | : BouncingScrollPhysics(), 356 | builder: widget.builder, 357 | onTapItem: _onTapItem, 358 | itemCount: widget.itemCount, 359 | sizeList: sizeList, 360 | onMeasureCompleted: () { 361 | var widgetsBindingInstance = WidgetsBinding.instance; 362 | //这里是兼容flutter2.0和3.0之间的差异 363 | //WidgetsBinding.instance 在2.x的版本是可空的,但是在3.x版本是不可空的 364 | //原有的WidgetsBinding.instance?在3.x版本抛警告,所以在这里做下兼容 365 | if (widgetsBindingInstance != null) { 366 | widgetsBindingInstance.addPostFrameCallback((d) { 367 | setState(() { 368 | widget.indicator?.updateScrollIndicator(getCurrentPage, 369 | sizeList, kCustomerTabBarAnimDuration, positionNotifier); 370 | 371 | _tabBarController.scrollTargetToCenter( 372 | _viewportSize / 2, 373 | widget.pageController.initialPage, 374 | sizeList, 375 | _scrollController); 376 | }); 377 | }); 378 | } 379 | }, 380 | ) 381 | ]); 382 | } 383 | 384 | Widget _buildSlivers() { 385 | return SliverList( 386 | delegate: SliverChildListDelegate([_buildTabBarItemList()])); 387 | } 388 | } 389 | 390 | class TabBarItemList extends StatefulWidget { 391 | final Axis direction; 392 | final double? viewPortWidth; 393 | final int itemCount; 394 | final IndexedWidgetBuilder builder; 395 | final List sizeList; 396 | final ValueChanged onTapItem; 397 | final ScrollPhysics physics; 398 | final VoidCallback onMeasureCompleted; 399 | 400 | TabBarItemList( 401 | {required this.viewPortWidth, 402 | required this.itemCount, 403 | required this.builder, 404 | required this.sizeList, 405 | required this.onTapItem, 406 | required this.physics, 407 | required this.onMeasureCompleted, 408 | required this.direction, 409 | key}) 410 | : super(key: key); 411 | 412 | @override 413 | TabBarItemListState createState() => TabBarItemListState(); 414 | } 415 | 416 | class TabBarItemListState extends State { 417 | bool isMeasureCompletedCallback = false; 418 | 419 | Widget _createItem(int index, Widget child) { 420 | return GestureDetector( 421 | behavior: HitTestBehavior.opaque, 422 | onTap: () => widget.onTapItem(index), 423 | child: child, 424 | ); 425 | } 426 | 427 | bool isAllItemMeasureComplete() { 428 | for (Size size in widget.sizeList) { 429 | if (size.isEmpty) { 430 | return false; 431 | } 432 | } 433 | return true; 434 | } 435 | 436 | @override 437 | void didUpdateWidget(covariant TabBarItemList oldWidget) { 438 | super.didUpdateWidget(oldWidget); 439 | if (widget.itemCount != oldWidget.itemCount) { 440 | isMeasureCompletedCallback = false; 441 | setState(() {}); 442 | } 443 | } 444 | 445 | @override 446 | Widget build(BuildContext context) { 447 | List widgetList = []; 448 | 449 | ///如果不能滑动并且是水平方向就平分父级组件宽度 450 | if (widget.physics is NeverScrollableScrollPhysics && 451 | widget.direction == Axis.horizontal) { 452 | double? itemWidth = (widget.viewPortWidth ?? 0) / widget.itemCount; 453 | for (var i = 0; i < widget.itemCount; i++) { 454 | widgetList.add(_createItem( 455 | i, 456 | Container( 457 | width: itemWidth, 458 | child: widget.builder(context, i), 459 | ))); 460 | widget.sizeList[i] = Size(itemWidth, 0); 461 | } 462 | if (!isMeasureCompletedCallback) { 463 | widget.onMeasureCompleted(); 464 | isMeasureCompletedCallback = true; 465 | } 466 | } else { 467 | for (var i = 0; i < widget.itemCount; i++) { 468 | widgetList.add(_createItem( 469 | i, 470 | MeasureSizeBox( 471 | child: Container( 472 | key: ValueKey(i), child: widget.builder(context, i)), 473 | onSizeCallback: (size) { 474 | widget.sizeList[i] = size; 475 | if (isAllItemMeasureComplete() && !isMeasureCompletedCallback) { 476 | widget.onMeasureCompleted(); 477 | isMeasureCompletedCallback = true; 478 | } 479 | }, 480 | ))); 481 | } 482 | } 483 | 484 | if (widget.direction == Axis.horizontal) { 485 | return Row(children: widgetList); 486 | } 487 | 488 | return Container( 489 | width: widget.viewPortWidth, child: Column(children: widgetList)); 490 | } 491 | } 492 | 493 | class MeasureSizeBox extends SingleChildRenderObjectWidget { 494 | final Widget child; 495 | final ValueChanged onSizeCallback; 496 | 497 | MeasureSizeBox({ 498 | required this.child, 499 | required this.onSizeCallback, 500 | }) : super(child: child); 501 | 502 | @override 503 | RenderObject createRenderObject(BuildContext context) { 504 | return _RenderConstrainedBox(onSizeCallback: this.onSizeCallback); 505 | } 506 | } 507 | 508 | class _RenderConstrainedBox extends RenderConstrainedBox { 509 | final ValueChanged onSizeCallback; 510 | 511 | _RenderConstrainedBox({required this.onSizeCallback}) 512 | : super(additionalConstraints: BoxConstraints()); 513 | 514 | @override 515 | void layout(Constraints constraints, {bool parentUsesSize = false}) { 516 | super.layout(constraints, parentUsesSize: parentUsesSize); 517 | if (size.isEmpty) return; 518 | 519 | onSizeCallback(Size.copy(size)); 520 | } 521 | } 522 | 523 | class TabBarItem extends StatefulWidget { 524 | final Widget? child; 525 | final int index; 526 | final TabBarTransform? transform; 527 | TabBarItem({ 528 | Key? key, 529 | this.child, 530 | required this.index, 531 | this.transform, 532 | }) : super(key: key); 533 | 534 | @override 535 | _TabBarItemState createState() => _TabBarItemState(); 536 | } 537 | 538 | class _TabBarItemState extends State { 539 | ValueNotifier? get progressNotifier => 540 | CustomTabBarContext.of(context)?.progressNotifier; 541 | ScrollProgressInfo? info; 542 | @override 543 | void initState() { 544 | super.initState(); 545 | 546 | Future.delayed(Duration.zero, () { 547 | // setState(() { 548 | // info = progressNotifier!.value; 549 | // }); 550 | progressNotifier?.addListener(() { 551 | setState(() { 552 | info = progressNotifier!.value; 553 | }); 554 | }); 555 | assert(progressNotifier != null); 556 | }); 557 | } 558 | 559 | @override 560 | Widget build(BuildContext context) { 561 | info = progressNotifier?.value; 562 | if (info == null) return SizedBox(); 563 | if (widget.transform != null) { 564 | return widget.transform!.build(context, widget.index, info!); 565 | } 566 | 567 | return Container( 568 | child: widget.child, 569 | ); 570 | } 571 | } 572 | -------------------------------------------------------------------------------- /lib/indicator/custom_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../library.dart'; 4 | 5 | class CustomTabBarController { 6 | double? _lastPage = 0; 7 | bool _isJumpToTarget = false; 8 | ValueChanged? _animateToIndexCallback; 9 | int _currentIndex = 0; 10 | double _progress = 0; 11 | 12 | Axis _direction = Axis.horizontal; 13 | 14 | List _listeners = []; 15 | 16 | int get currentIndex => _currentIndex; 17 | 18 | void setCurrentIndex(int index) { 19 | _currentIndex = index; 20 | } 21 | 22 | Axis get direction => _direction; 23 | 24 | void setOrientation(Axis orientation) { 25 | this._direction = orientation; 26 | } 27 | 28 | void addListener(VoidCallback? callback) { 29 | _listeners.add(callback); 30 | } 31 | 32 | void removeAt(int index) { 33 | if (index >= 0 && index < _listeners.length) { 34 | _listeners.removeAt(index); 35 | } 36 | } 37 | 38 | bool isChanging() { 39 | if (isJumpToTarget) return true; 40 | if (!isJumpToTarget) { 41 | return double.parse(_progress.toStringAsFixed(3)) > 0.001; 42 | } 43 | 44 | return false; 45 | } 46 | 47 | void forEachListenerCallback() { 48 | _listeners.forEach((listener) { 49 | listener?.call(); 50 | }); 51 | } 52 | 53 | void startJump() { 54 | _isJumpToTarget = true; 55 | } 56 | 57 | void endJump() { 58 | _isJumpToTarget = false; 59 | } 60 | 61 | bool get isJumpToTarget => _isJumpToTarget; 62 | 63 | void setAnimateToIndexCallback(ValueChanged callback) { 64 | _animateToIndexCallback = callback; 65 | } 66 | 67 | void animateToIndex(int targetIndex) { 68 | _animateToIndexCallback?.call(targetIndex); 69 | } 70 | 71 | ScrollItemInfo calculateScrollTabbarItemInfo( 72 | double? page, List sizeList) { 73 | _progress = page! % 1.0; 74 | 75 | ///确定当前索引值位置 76 | if (page > _lastPage!) { 77 | if (page.toInt() > _lastPage!.toInt()) { 78 | _currentIndex = page.toInt(); 79 | } else { 80 | _currentIndex = _lastPage!.toInt(); 81 | _progress = _progress == 0 ? 1 : _progress; 82 | } 83 | } else { 84 | _currentIndex = page.toInt(); 85 | } 86 | 87 | _lastPage = page; 88 | 89 | //获取下一个Item的Size 90 | Size nextIndexItemSize = Size(-1, -1); 91 | if (_currentIndex < sizeList.length - 1) { 92 | nextIndexItemSize = sizeList[_currentIndex + 1]; 93 | } 94 | 95 | return ScrollItemInfo.obtain( 96 | _currentIndex, 97 | getTargetItemScrollEndOffset(sizeList, _currentIndex), 98 | sizeList[_currentIndex], 99 | nextIndexItemSize, 100 | _progress, 101 | getTabBarSize(sizeList), 102 | sizeList.length); 103 | } 104 | 105 | //根据pageController来计算进度 106 | ScrollProgressInfo? calculateScrollProgressByPageView( 107 | int currentIndex, PageController pageController) { 108 | if (pageController.page == currentIndex) return null; 109 | 110 | int targetIndex = 0; 111 | if ((pageController.page ?? 0) > currentIndex) { 112 | targetIndex = pageController.page!.ceil(); 113 | } else { 114 | targetIndex = pageController.page!.floor(); 115 | } 116 | 117 | _progress = pageController.page! % 1.0; 118 | if (targetIndex < currentIndex) { 119 | _progress = 1 - _progress; 120 | } 121 | _progress = _progress == 0 ? 1 : _progress; 122 | return ScrollProgressInfo( 123 | progress: _progress, 124 | targetIndex: targetIndex, 125 | currentIndex: currentIndex); 126 | } 127 | 128 | ///根据pageController来设置偏移量 129 | void scrollByPageView(Size tabCenterSize, List? sizeList, 130 | ScrollController? scrollController, PageController pageController) { 131 | if (scrollController == null) return; 132 | var index = pageController.page!.ceil(); 133 | var preIndex = pageController.page!.floor(); 134 | var offsetPercent = pageController.page! % 1; 135 | var currentIndexHalfSize = sizeList![index] / 2; 136 | var preIndexHalfSize = sizeList[preIndex] / 2; 137 | 138 | var totalSize = Size(currentIndexHalfSize.width + preIndexHalfSize.width, 139 | currentIndexHalfSize.height + preIndexHalfSize.height); 140 | 141 | var startOffset = getTargetItemScrollStartOffset(sizeList, preIndex); 142 | var endSize = sizeList[preIndex] / 2 + startOffset; 143 | 144 | var contentInsertSize = getTabBarSize(sizeList); 145 | 146 | bool isVisible = 147 | isItemVisible(scrollController, index, sizeList, tabCenterSize * 2); 148 | 149 | var offset = 0.0; 150 | if (direction == Axis.horizontal) { 151 | if (isVisible) { 152 | if (endSize.width + totalSize.width > tabCenterSize.width) { 153 | if (endSize.width > tabCenterSize.width) { 154 | offset = endSize.width - 155 | tabCenterSize.width + 156 | offsetPercent * totalSize.width; 157 | } else { 158 | offset = offsetPercent * 159 | (totalSize.width + endSize.width - tabCenterSize.width); 160 | } 161 | if (contentInsertSize.width - offset - tabCenterSize.width > 162 | tabCenterSize.width) { 163 | scrollController.jumpTo(offset); 164 | } 165 | } 166 | } else { 167 | offset = startOffset.dx - tabCenterSize.width; 168 | scrollController.jumpTo(offset < 0 ? 0 : offset); 169 | } 170 | } else { 171 | //竖直方向 172 | if (isVisible) { 173 | if (endSize.height + totalSize.height > tabCenterSize.height) { 174 | if (endSize.height > tabCenterSize.height) { 175 | offset = endSize.height - 176 | tabCenterSize.height + 177 | offsetPercent * totalSize.height; 178 | } else { 179 | offset = offsetPercent * 180 | (totalSize.height + endSize.height - tabCenterSize.height); 181 | } 182 | 183 | if (contentInsertSize.height - offset - tabCenterSize.height > 184 | tabCenterSize.height) { 185 | scrollController.jumpTo(offset); 186 | } 187 | } 188 | } else { 189 | offset = startOffset.dy - tabCenterSize.height; 190 | scrollController.jumpTo(offset < 0 ? 0 : offset); 191 | } 192 | } 193 | } 194 | 195 | ///判断item是否显示在可见区域 196 | bool isItemVisible(ScrollController scrollController, index, 197 | List? sizeList, Size tabBarSize) { 198 | var startOffset = getTargetItemScrollStartOffset(sizeList, index); 199 | 200 | if (direction == Axis.horizontal) { 201 | return scrollController.position.pixels < startOffset.dx && 202 | startOffset.dx < scrollController.position.pixels + tabBarSize.width; 203 | } 204 | return scrollController.position.pixels < startOffset.dy && 205 | startOffset.dy < scrollController.position.pixels + tabBarSize.height; 206 | } 207 | 208 | int lastIndex = 0; 209 | 210 | ///滚动目标索引的项到中间位置 211 | void scrollTargetToCenter(Size tabCenterSize, int targetIndex, 212 | List? sizeList, ScrollController? scrollController, 213 | {Duration? duration}) { 214 | if (targetIndex == lastIndex) return; 215 | var targetItemScrollOffset = 216 | getTargetItemScrollEndOffset(sizeList, targetIndex); 217 | var tabBarSize = getTabBarSize(sizeList); 218 | 219 | double animateToOffset = 0; 220 | 221 | if (direction == Axis.horizontal) { 222 | animateToOffset = targetItemScrollOffset.dx - 223 | sizeList![targetIndex].width / 2 - 224 | tabCenterSize.width; 225 | } else { 226 | animateToOffset = targetItemScrollOffset.dy - 227 | sizeList![targetIndex].height / 2 - 228 | tabCenterSize.height; 229 | } 230 | 231 | if (animateToOffset <= 0) { 232 | animateToOffset = 0; 233 | } else { 234 | if (direction == Axis.horizontal) { 235 | if (animateToOffset + tabCenterSize.width > 236 | tabBarSize.width - tabCenterSize.width) { 237 | if (tabBarSize.width > tabCenterSize.width * 2) { 238 | animateToOffset = tabBarSize.width - tabCenterSize.width * 2; 239 | } else { 240 | animateToOffset = 0; 241 | } 242 | } 243 | } else { 244 | if (animateToOffset + tabCenterSize.height > 245 | tabBarSize.height - tabCenterSize.height) { 246 | if (tabBarSize.height > tabCenterSize.height * 2) { 247 | animateToOffset = tabBarSize.height - tabCenterSize.height * 2; 248 | } else { 249 | animateToOffset = 0; 250 | } 251 | } 252 | } 253 | } 254 | 255 | lastIndex = targetIndex; 256 | 257 | if (duration == null) { 258 | scrollController?.jumpTo(animateToOffset); 259 | } else { 260 | scrollController?.animateTo(animateToOffset, 261 | duration: duration, curve: Curves.easeIn); 262 | } 263 | } 264 | 265 | Offset getTargetItemScrollEndOffset(List? sizeList, int index) { 266 | double width = 0; 267 | double height = 0; 268 | for (int i = 0; i <= index; i++) { 269 | width += sizeList![i].width; 270 | height += sizeList[i].height; 271 | } 272 | return Offset(width, height); 273 | } 274 | 275 | Offset getTargetItemScrollStartOffset(List? sizeList, int index) { 276 | double width = 0; 277 | double height = 0; 278 | 279 | for (int i = 0; i < index; i++) { 280 | width += sizeList![i].width; 281 | height += sizeList[i].height; 282 | } 283 | 284 | return Offset(width, height); 285 | } 286 | 287 | Size getTabBarSize(List? sizeList) { 288 | double width = 0; 289 | double height = 0; 290 | sizeList?.forEach((item) { 291 | width += item.width; 292 | height += item.height; 293 | }); 294 | 295 | return Size(width, height); 296 | } 297 | 298 | double getTabIndicatorCenterX(double width) { 299 | return width / 2; 300 | } 301 | } 302 | 303 | abstract class CustomIndicator { 304 | final Color color; 305 | final double left; 306 | final double right; 307 | final double? top; 308 | final double bottom; 309 | final double height; 310 | final double? width; 311 | final BorderRadius? radius; 312 | late CustomTabBarController controller; 313 | 314 | CustomIndicator( 315 | {this.bottom = 0, 316 | this.top, 317 | this.right = 0, 318 | this.left = 0, 319 | this.width, 320 | required this.color, 321 | required this.height, 322 | this.radius}); 323 | 324 | void updateScrollIndicator(double? page, List? sizeList, 325 | Duration duration, ValueNotifier notifier); 326 | 327 | void indicatorScrollToIndex( 328 | int index, 329 | List? sizeList, 330 | Duration duration, 331 | TickerProvider vsync, 332 | ValueNotifier notifier); 333 | 334 | void dispose(); 335 | } 336 | -------------------------------------------------------------------------------- /lib/indicator/linear_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_tab_bar/library.dart'; 3 | 4 | class LinearIndicator extends CustomIndicator { 5 | final Color color; 6 | final double bottom; 7 | final BorderRadius? radius; 8 | final double height; 9 | final double? width; 10 | 11 | LinearIndicator( 12 | {required this.color, 13 | required this.bottom, 14 | this.height = 3, 15 | this.radius, 16 | this.width}) 17 | : super( 18 | bottom: bottom, 19 | color: color, 20 | height: height, 21 | radius: radius, 22 | width: width); 23 | 24 | double getTabIndicatorCenterX(double width) { 25 | return width / 2; 26 | } 27 | 28 | @override 29 | void updateScrollIndicator(double? page, List? sizeList, 30 | Duration duration, ValueNotifier notifier) { 31 | ScrollItemInfo info = 32 | controller.calculateScrollTabbarItemInfo(page, sizeList!); 33 | if (info.nextItemSize.width == -1 && 34 | info.nextItemSize.height == -1 && 35 | !info.isLast) return; 36 | 37 | double left = 0; 38 | double right = 0; 39 | double top = 0; 40 | double bottom = 0; 41 | if (this.width == null) { 42 | left = info.currentItemScrollEndOffset.dx - 43 | info.currentItemSize.width + 44 | info.currentItemSize.width * info.progress; 45 | right = info.tabBarSize.width - 46 | info.currentItemScrollEndOffset.dx - 47 | info.nextItemSize.width * info.progress; 48 | } else { 49 | left = info.currentItemScrollEndOffset.dx - 50 | (info.currentItemSize.width + this.width!) / 2 + 51 | (info.currentItemSize.width + info.nextItemSize.width) / 52 | 2 * 53 | info.progress; 54 | right = info.tabBarSize.width - left - this.width!; 55 | } 56 | 57 | notifier.value = IndicatorPosition(left, right, top, bottom); 58 | controller.forEachListenerCallback(); 59 | } 60 | 61 | AnimationController? _animationController; 62 | late Animation _animation; 63 | 64 | @override 65 | void dispose() { 66 | _animationController?.stop(canceled: true); 67 | } 68 | 69 | @override 70 | void indicatorScrollToIndex( 71 | int index, 72 | List? sizeList, 73 | Duration duration, 74 | TickerProvider vsync, 75 | ValueNotifier notifier) { 76 | double left = notifier.value.left; 77 | double right = notifier.value.right; 78 | double width = 79 | this.width ?? controller.getTabBarSize(sizeList).width - right - left; 80 | 81 | double targetLeft = 82 | controller.getTargetItemScrollStartOffset(sizeList, index).dx; 83 | 84 | if (this.width != null) { 85 | targetLeft = targetLeft + 86 | (sizeList?[index].width ?? 0 - this.width!) / 2 - 87 | this.width! / 2; 88 | } 89 | if (targetLeft == left) return; 90 | 91 | _animationController = 92 | AnimationController(duration: duration, vsync: vsync); 93 | _animation = 94 | Tween(begin: left, end: targetLeft).animate(_animationController!); 95 | 96 | _animation.addListener(() { 97 | double rate = 0; 98 | double targetRight = 0; 99 | if (left > targetLeft) { 100 | rate = 1 - (targetLeft - _animation.value) / (targetLeft - left); 101 | } else { 102 | rate = (_animation.value - left) / (targetLeft - left); 103 | } 104 | 105 | if (this.width == null) { 106 | targetRight = controller.getTabBarSize(sizeList).width - 107 | _animation.value - 108 | width - 109 | (sizeList![index].width - width) * rate; 110 | } else { 111 | targetRight = 112 | controller.getTabBarSize(sizeList).width - _animation.value - width; 113 | } 114 | 115 | notifier.value = IndicatorPosition(_animation.value, targetRight, 0, 0); 116 | controller.forEachListenerCallback(); 117 | }); 118 | _animationController!.forward(); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/indicator/round_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../models.dart'; 4 | import 'custom_indicator.dart'; 5 | 6 | class RoundIndicator extends CustomIndicator { 7 | final Color color; 8 | final BorderRadius? radius; 9 | final double height; 10 | final double? top; 11 | final double bottom; 12 | final double left; 13 | final double right; 14 | RoundIndicator( 15 | {required this.color, 16 | this.top = 0, 17 | this.bottom = 0, 18 | this.left = 0, 19 | this.right = 0, 20 | this.height = 3, 21 | this.radius}) 22 | : super( 23 | bottom: bottom, 24 | color: color, 25 | height: height, 26 | radius: radius, 27 | ); 28 | double getTabIndicatorCenterX(double width) { 29 | return width / 2; 30 | } 31 | 32 | @override 33 | void updateScrollIndicator(double? page, List? sizeList, 34 | Duration duration, ValueNotifier notifier) { 35 | ScrollItemInfo info = 36 | controller.calculateScrollTabbarItemInfo(page, sizeList!); 37 | if (info.nextItemSize.width == -1 && 38 | info.nextItemSize.width == -1 && 39 | !info.isLast) return; 40 | 41 | double left = 0; 42 | double right = 0; 43 | double top = 0; 44 | double bottom = 0; 45 | 46 | if (controller.direction == Axis.horizontal) { 47 | left = info.currentItemScrollEndOffset.dx - 48 | info.currentItemSize.width + 49 | info.currentItemSize.width * info.progress; 50 | right = info.tabBarSize.width - 51 | info.currentItemScrollEndOffset.dx - 52 | info.nextItemSize.width * info.progress; 53 | } else { 54 | top = info.currentItemScrollEndOffset.dy - 55 | info.currentItemSize.height + 56 | info.currentItemSize.height * info.progress; 57 | bottom = info.tabBarSize.height - 58 | info.currentItemScrollEndOffset.dy - 59 | info.nextItemSize.height * info.progress; 60 | } 61 | 62 | notifier.value = IndicatorPosition(left, right, top, bottom); 63 | controller.forEachListenerCallback(); 64 | } 65 | 66 | AnimationController? _animationController; 67 | late Animation _animation; 68 | 69 | @override 70 | void dispose() { 71 | _animationController?.stop(canceled: true); 72 | } 73 | 74 | @override 75 | void indicatorScrollToIndex( 76 | int index, 77 | List? sizeList, 78 | Duration duration, 79 | TickerProvider vsync, 80 | ValueNotifier notifier) { 81 | double left = notifier.value.left; 82 | double right = notifier.value.right; 83 | double top = notifier.value.top; 84 | double bottom = notifier.value.bottom; 85 | 86 | if (controller.direction == Axis.horizontal) { 87 | double width = controller.getTabBarSize(sizeList).width - right - left; 88 | double targetLeft = 89 | controller.getTargetItemScrollStartOffset(sizeList, index).dx; 90 | if (targetLeft == left) return; 91 | 92 | _animationController = 93 | AnimationController(duration: duration, vsync: vsync); 94 | 95 | _animation = 96 | Tween(begin: left, end: targetLeft).animate(_animationController!); 97 | 98 | _animation.addListener(() { 99 | double? rate = 0; 100 | double targetRight = 0; 101 | if (left > targetLeft) { 102 | rate = 1 - (targetLeft - _animation.value) / (targetLeft - left); 103 | targetRight = controller.getTabBarSize(sizeList).width - 104 | _animation.value - 105 | width - 106 | (sizeList![index].width - width) * rate; 107 | } else { 108 | rate = (_animation.value - left) / (targetLeft - left); 109 | targetRight = controller.getTabBarSize(sizeList).width - 110 | _animation.value - 111 | width - 112 | (sizeList![index].width - width) * rate!; 113 | } 114 | 115 | notifier.value = IndicatorPosition(_animation.value, targetRight, 0, 0); 116 | controller.forEachListenerCallback(); 117 | }); 118 | } else { 119 | double height = controller.getTabBarSize(sizeList).height - bottom - top; 120 | double targetTop = 121 | controller.getTargetItemScrollStartOffset(sizeList, index).dy; 122 | if (targetTop == top) return; 123 | 124 | _animationController = 125 | AnimationController(duration: duration, vsync: vsync); 126 | 127 | _animation = 128 | Tween(begin: top, end: targetTop).animate(_animationController!); 129 | 130 | _animation.addListener(() { 131 | double? rate = 0; 132 | double targetBottom = 0; 133 | if (top > targetTop) { 134 | rate = 1 - (targetTop - _animation.value) / (targetTop - top); 135 | targetBottom = controller.getTabBarSize(sizeList).height - 136 | _animation.value - 137 | height - 138 | (sizeList![index].height - height) * rate; 139 | } else { 140 | rate = (_animation.value - top) / (targetTop - top); 141 | targetBottom = controller.getTabBarSize(sizeList).height - 142 | _animation.value - 143 | height - 144 | (sizeList![index].height - height) * rate!; 145 | } 146 | 147 | notifier.value = 148 | IndicatorPosition(0, 0, _animation.value, targetBottom); 149 | controller.forEachListenerCallback(); 150 | }); 151 | } 152 | 153 | _animationController!.forward(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/indicator/standard_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../models.dart'; 4 | import 'custom_indicator.dart'; 5 | 6 | class StandardIndicator extends CustomIndicator { 7 | final Color color; 8 | final double bottom; 9 | final BorderRadius? radius; 10 | final double width; 11 | final double height; 12 | 13 | StandardIndicator( 14 | {required this.width, 15 | required this.color, 16 | this.bottom = 0, 17 | this.height = 3, 18 | this.radius}) 19 | : super(bottom: bottom, color: color, height: height, radius: radius); 20 | 21 | @override 22 | void dispose() { 23 | _animationController?.stop(canceled: true); 24 | } 25 | 26 | AnimationController? _animationController; 27 | late Animation _animation; 28 | 29 | @override 30 | void updateScrollIndicator(double? page, List? sizeList, 31 | Duration duration, ValueNotifier notifier) { 32 | ScrollItemInfo info = 33 | controller.calculateScrollTabbarItemInfo(page, sizeList!); 34 | if (info.nextItemSize.width == -1 && 35 | info.nextItemSize.height == -1 && 36 | !info.isLast) return; 37 | 38 | double left = 0; 39 | double right = 0; 40 | double top = 0; 41 | double bottom = 0; 42 | 43 | if (info.progress <= 0.5) { 44 | left = info.currentItemScrollEndOffset.dx - 45 | (info.currentItemSize.width + width) * 0.5; 46 | right = info.tabBarSize.width - 47 | info.currentItemScrollEndOffset.dx + 48 | info.currentItemSize.width * (0.5 - info.progress) - 49 | width * 0.5 - 50 | info.nextItemSize.width * info.progress; 51 | } else { 52 | left = info.currentItemScrollEndOffset.dx - 53 | width * 0.5 - 54 | info.nextItemSize.width * (0.5 - info.progress) - 55 | info.currentItemSize.width * (1 - info.progress); 56 | 57 | right = info.tabBarSize.width - 58 | info.currentItemScrollEndOffset.dx - 59 | (info.nextItemSize.width + width) * 0.5; 60 | } 61 | notifier.value = IndicatorPosition(left, right, top, bottom); 62 | controller.forEachListenerCallback(); 63 | } 64 | 65 | @override 66 | void indicatorScrollToIndex( 67 | int index, 68 | List? sizeList, 69 | Duration duration, 70 | TickerProvider vsync, 71 | ValueNotifier notifier) { 72 | double left = notifier.value.left; 73 | 74 | double targetLeft = 75 | controller.getTargetItemScrollEndOffset(sizeList, index).dx - 76 | (sizeList![index].width + width) / 2; 77 | 78 | _animationController = 79 | AnimationController(duration: duration, vsync: vsync); 80 | 81 | _animation = 82 | Tween(begin: left, end: targetLeft).animate(_animationController!); 83 | _animation.addListener(() { 84 | double right = 85 | controller.getTabBarSize(sizeList).width - _animation.value - width; 86 | 87 | notifier.value = IndicatorPosition(_animation.value, right, 0, 0); 88 | controller.forEachListenerCallback(); 89 | }); 90 | 91 | _animationController!.forward(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /lib/library.dart: -------------------------------------------------------------------------------- 1 | export './indicator/custom_indicator.dart'; 2 | export './indicator/linear_indicator.dart'; 3 | export './indicator/round_indicator.dart'; 4 | export './indicator/standard_indicator.dart'; 5 | export 'custom_tab_bar.dart'; 6 | export 'models.dart'; 7 | export 'transform/color_transform.dart'; 8 | export 'transform/scale_transform.dart'; 9 | export 'transform/tab_bar_transform.dart'; 10 | -------------------------------------------------------------------------------- /lib/models.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui'; 2 | 3 | class ScrollItemInfo { 4 | final double progress; 5 | final Size nextItemSize; 6 | final int currentIndex; 7 | final Size currentItemSize; 8 | final Offset currentItemScrollEndOffset; 9 | final Size tabBarSize; 10 | final int tabsLength; 11 | 12 | bool get isLast => tabsLength - 1 == currentIndex; 13 | 14 | const ScrollItemInfo.obtain( 15 | this.currentIndex, 16 | this.currentItemScrollEndOffset, 17 | this.currentItemSize, 18 | this.nextItemSize, 19 | this.progress, 20 | this.tabBarSize, 21 | this.tabsLength); 22 | } 23 | 24 | class IndicatorPosition { 25 | final double left; 26 | final double right; 27 | final double top; 28 | final double bottom; 29 | const IndicatorPosition(this.left, this.right, this.top, this.bottom); 30 | } 31 | 32 | class ScrollProgressInfo { 33 | final int targetIndex; 34 | final int currentIndex; 35 | final double progress; 36 | 37 | const ScrollProgressInfo({ 38 | this.targetIndex = -1, 39 | this.currentIndex = 0, 40 | this.progress = 0, 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /lib/transform/color_transform.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../models.dart'; 4 | import 'tab_bar_transform.dart'; 5 | 6 | class ColorsTransform extends TabBarTransform { 7 | Color highlightColor; 8 | Color normalColor; 9 | Color? transformColor; 10 | 11 | ColorsTransform({ 12 | TabBarTransform? transform, 13 | TransformBuilder? builder, 14 | required this.highlightColor, 15 | required this.normalColor, 16 | }) : super(transform: transform, builder: builder); 17 | 18 | @override 19 | Widget build(BuildContext context, int index, ScrollProgressInfo info) { 20 | calculate(index, info); 21 | if (builder != null) { 22 | return builder!(context, transformColor); 23 | } 24 | return transform!.build(context, index, info); 25 | } 26 | 27 | @override 28 | void calculate(int index, ScrollProgressInfo info) { 29 | double changeValue = info.progress; 30 | int alphaValueOffset = highlightColor.alpha - normalColor.alpha; 31 | int blueValueOffset = highlightColor.blue - normalColor.blue; 32 | int greenValueOffset = highlightColor.green - normalColor.green; 33 | int redValueOffset = highlightColor.red - normalColor.red; 34 | 35 | if (info.currentIndex == index) { 36 | transformColor = highlightColor 37 | .withAlpha( 38 | highlightColor.alpha - (alphaValueOffset * changeValue).toInt()) 39 | .withBlue( 40 | highlightColor.blue - (blueValueOffset * changeValue).toInt()) 41 | .withGreen( 42 | highlightColor.green - (greenValueOffset * changeValue).toInt()) 43 | .withRed(highlightColor.red - (redValueOffset * changeValue).toInt()); 44 | } else if (info.targetIndex == index) { 45 | transformColor = normalColor 46 | .withAlpha( 47 | normalColor.alpha + (alphaValueOffset * changeValue).toInt()) 48 | .withBlue(normalColor.blue + (blueValueOffset * changeValue).toInt()) 49 | .withGreen( 50 | normalColor.green + (greenValueOffset * changeValue).toInt()) 51 | .withRed(normalColor.red + (redValueOffset * changeValue).toInt()); 52 | } else { 53 | transformColor = normalColor; 54 | return; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/transform/scale_transform.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../models.dart'; 4 | import 'tab_bar_transform.dart'; 5 | 6 | class ScaleTransform extends TabBarTransform { 7 | double maxScale; 8 | ScaleTransform({ 9 | TabBarTransform? transform, 10 | TransformBuilder? builder, 11 | this.maxScale = 1.2, 12 | }) : assert(maxScale >= 1), 13 | super(transform: transform, builder: builder); 14 | 15 | double scale = 0; 16 | 17 | @override 18 | void calculate(int index, ScrollProgressInfo info) { 19 | if (info.currentIndex == index) { 20 | scale = 1.0 - info.progress; 21 | 22 | return; 23 | } 24 | if (info.targetIndex == index) { 25 | scale = info.progress; 26 | return; 27 | } 28 | scale = 0; 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context, int index, ScrollProgressInfo info) { 33 | calculate(index, info); 34 | 35 | return Transform.scale( 36 | scale: 1 + ((maxScale - 1) * scale), 37 | child: builder == null 38 | ? transform!.build(context, index, info) 39 | : builder!(context, scale), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/transform/tab_bar_transform.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_custom_tab_bar/library.dart'; 3 | 4 | typedef TransformBuilder = Widget Function( 5 | BuildContext context, dynamic transform); 6 | 7 | abstract class TabBarTransform { 8 | TabBarTransform? transform; 9 | TransformBuilder? builder; 10 | TabBarTransform({ 11 | this.transform, 12 | this.builder, 13 | }); 14 | 15 | void calculate(int index, ScrollProgressInfo info); 16 | 17 | Widget build(BuildContext context, int index, ScrollProgressInfo info); 18 | } 19 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.9.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.2.1" 25 | clock: 26 | dependency: transitive 27 | description: 28 | name: clock 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.1" 32 | collection: 33 | dependency: transitive 34 | description: 35 | name: collection 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.16.0" 39 | fake_async: 40 | dependency: transitive 41 | description: 42 | name: fake_async 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.3.1" 46 | flutter: 47 | dependency: "direct main" 48 | description: flutter 49 | source: sdk 50 | version: "0.0.0" 51 | flutter_test: 52 | dependency: "direct dev" 53 | description: flutter 54 | source: sdk 55 | version: "0.0.0" 56 | matcher: 57 | dependency: transitive 58 | description: 59 | name: matcher 60 | url: "https://pub.dartlang.org" 61 | source: hosted 62 | version: "0.12.12" 63 | material_color_utilities: 64 | dependency: transitive 65 | description: 66 | name: material_color_utilities 67 | url: "https://pub.dartlang.org" 68 | source: hosted 69 | version: "0.1.5" 70 | meta: 71 | dependency: transitive 72 | description: 73 | name: meta 74 | url: "https://pub.dartlang.org" 75 | source: hosted 76 | version: "1.8.0" 77 | path: 78 | dependency: transitive 79 | description: 80 | name: path 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "1.8.2" 84 | sky_engine: 85 | dependency: transitive 86 | description: flutter 87 | source: sdk 88 | version: "0.0.99" 89 | source_span: 90 | dependency: transitive 91 | description: 92 | name: source_span 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "1.9.0" 96 | stack_trace: 97 | dependency: transitive 98 | description: 99 | name: stack_trace 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "1.10.0" 103 | stream_channel: 104 | dependency: transitive 105 | description: 106 | name: stream_channel 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "2.1.0" 110 | string_scanner: 111 | dependency: transitive 112 | description: 113 | name: string_scanner 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.1.1" 117 | term_glyph: 118 | dependency: transitive 119 | description: 120 | name: term_glyph 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "1.2.1" 124 | test_api: 125 | dependency: transitive 126 | description: 127 | name: test_api 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "0.4.12" 131 | vector_math: 132 | dependency: transitive 133 | description: 134 | name: vector_math 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "2.1.2" 138 | sdks: 139 | dart: ">=2.17.0-0 <3.0.0" 140 | flutter: ">=1.17.0" 141 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_custom_tab_bar 2 | description: custom tab bar. 3 | version: 1.2.1 4 | homepage: https://github.com/lazyee/flutter_custom_tab_bar 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | 18 | # For information on the generic Dart part of this file, see the 19 | # following page: https://dart.dev/tools/pub/pubspec 20 | 21 | # The following section is specific to Flutter. 22 | flutter: 23 | 24 | # To add assets to your package, add an assets section, like this: 25 | # assets: 26 | # - images/a_dot_burr.jpeg 27 | # - images/a_dot_ham.jpeg 28 | # 29 | # For details regarding assets in packages, see 30 | # https://flutter.dev/assets-and-images/#from-packages 31 | # 32 | # An image asset can refer to one or more resolution-specific "variants", see 33 | # https://flutter.dev/assets-and-images/#resolution-aware. 34 | 35 | # To add custom fonts to your package, add a fonts section here, 36 | # in this "flutter" section. Each entry in this list should have a 37 | # "family" key with the font family name, and a "fonts" key with a 38 | # list giving the asset and other descriptors for the font. For 39 | # example: 40 | # fonts: 41 | # - family: Schyler 42 | # fonts: 43 | # - asset: fonts/Schyler-Regular.ttf 44 | # - asset: fonts/Schyler-Italic.ttf 45 | # style: italic 46 | # - family: Trajan Pro 47 | # fonts: 48 | # - asset: fonts/TrajanPro.ttf 49 | # - asset: fonts/TrajanPro_Bold.ttf 50 | # weight: 700 51 | # 52 | # For details regarding fonts in packages, see 53 | # https://flutter.dev/custom-fonts/#from-packages 54 | -------------------------------------------------------------------------------- /test/flutter_custom_tab_bar_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | void main() { 4 | test('adds one to input values', () {}); 5 | } 6 | --------------------------------------------------------------------------------