├── CHANGELOG.md ├── LICENSE ├── README.md ├── create_demo.sh ├── example ├── .flutter-plugins-dependencies ├── .metadata ├── Magpie.gif ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── magpie_fly_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ ├── launch_background.xml │ │ │ │ └── magpie_fly.png │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── logo.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ └── images │ │ ├── 2.0x │ │ ├── tab_nomal1@2x.png │ │ ├── tab_nomal2@2x.png │ │ ├── tab_nomal3@2x.png │ │ ├── tab_nomal4@2x.png │ │ ├── tab_selected1@2x.png │ │ ├── tab_selected2@2x.png │ │ ├── tab_selected3@2x.png │ │ └── tab_selected4@2x.png │ │ ├── 3.0x │ │ ├── tab_nomal1@3x.png │ │ ├── tab_nomal2@3x.png │ │ ├── tab_nomal3@3x.png │ │ ├── tab_nomal4@3x.png │ │ ├── tab_selected1@3x.png │ │ ├── tab_selected2@3x.png │ │ ├── tab_selected3@3x.png │ │ └── tab_selected4@3x.png │ │ ├── tab_nomal1.png │ │ ├── tab_nomal2.png │ │ ├── tab_nomal3.png │ │ ├── tab_nomal4.png │ │ ├── tab_selected1.png │ │ ├── tab_selected2.png │ │ ├── tab_selected3.png │ │ └── tab_selected4.png ├── create_demo │ ├── createDemo.dart │ └── fileUtil.dart ├── doc │ ├── contribution.md │ ├── how_to_add_component.md │ ├── magpie_ui.md │ └── resource │ │ ├── create_demo1.jpg │ │ ├── create_demo2.jpg │ │ └── create_demo3.jpg ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ └── 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 │ │ ├── Contents.json │ │ └── LaunchImage.launchimage │ │ │ ├── Contents.json │ │ │ ├── 启动页1125X2436.png │ │ │ ├── 启动页1242X2208.png │ │ │ ├── 启动页1242X2688.png │ │ │ ├── 启动页320X480.png │ │ │ ├── 启动页640X1136-1.png │ │ │ ├── 启动页640X1136.png │ │ │ ├── 启动页640X960-1.png │ │ │ ├── 启动页640X960.png │ │ │ ├── 启动页750X1334.png │ │ │ └── 启动页828X1792.png │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── app.dart │ ├── comon_widget │ │ ├── code_component.dart │ │ ├── effect_placeholder.dart │ │ ├── fullscreen_code.dart │ │ ├── fullscreen_effect.dart │ │ ├── markdown_page.dart │ │ └── widget_demo.dart │ ├── demo │ │ ├── ListDatas.dart │ │ ├── banner_widget │ │ │ ├── .page.json │ │ │ ├── banner_widget.md │ │ │ └── banner_widget_demo.dart │ │ ├── draggable_btn │ │ │ ├── .page.json │ │ │ ├── drag_btn_demo.dart │ │ │ └── dragable_btn.md │ │ ├── horizonal_scroll │ │ │ ├── .page.json │ │ │ ├── HorizontalScrollCustom.dart │ │ │ ├── HorizontalScrollDefault.dart │ │ │ ├── HorizontalScrollText.dart │ │ │ ├── component │ │ │ │ └── card.dart │ │ │ └── horizonal_scroll.md │ │ ├── pinned_sliver │ │ │ ├── .page.json │ │ │ ├── component │ │ │ │ └── simple_rect.dart │ │ │ ├── pinned_sliver.md │ │ │ └── pinned_sliver_demo.dart │ │ ├── popup_window │ │ │ ├── .page.json │ │ │ ├── popup_window.md │ │ │ └── popup_window_demo.dart │ │ ├── search_input │ │ │ ├── .page.json │ │ │ ├── search_input.md │ │ │ └── search_input_demo.dart │ │ ├── test_widget │ │ │ ├── .page.json │ │ │ ├── test_widget.md │ │ │ └── test_widget_demo.dart │ │ └── video │ │ │ ├── .page.json │ │ │ ├── component │ │ │ ├── text_component.dart │ │ │ ├── video_component.dart │ │ │ └── video_view.dart │ │ │ ├── video.md │ │ │ └── video_feed_demo.dart │ ├── list.dart │ ├── main.dart │ ├── markdown_doc.dart │ ├── news.dart │ └── util │ │ ├── config.dart │ │ ├── parse_markdown.dart │ │ └── syntax_highlighter.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── lib ├── magpie_fly.dart └── src │ ├── banner │ └── banner_round.dart │ ├── drag │ └── draggable_btn.dart │ ├── feed │ ├── meta_consumer.dart │ ├── scroll_detect_listener.dart │ ├── video_meta.dart │ └── video_play_model.dart │ ├── horizontal_scroll │ ├── footer_default_painter.dart │ ├── footer_view.dart │ └── horizontal_scroll.dart │ ├── popup_window │ └── popup_window.dart │ ├── search_input │ └── search_input.dart │ └── sliver │ ├── pinned_appbar.dart │ └── safearea_header.dart └── pubspec.yaml /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2005-present, 58.com. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, 4 | are permitted provided that the following conditions are met: 5 | 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above 9 | copyright notice, this list of conditions and the following 10 | disclaimer in the documentation and/or other materials provided 11 | with the distribution. 12 | * Neither the name of Google Inc. nor the names of its 13 | contributors may be used to endorse or promote products derived 14 | from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 | # magpie_fly 4 | 5 | Magpie-fly 是58集体出品组件库,封装了多种常用组件,以满足开发者需求。(Magpie-fly is a component library produced by 58 Group, which encapsulates a variety of common components to meet the needs of developers) 6 | 7 | Mapie包含了一系列的开源项目,访问对应仓库了解更多。 8 | 9 | > Magpie 10 | 11 | 一个Flutter开发的工具流,实现独立Flutter模块的创建,开发,编译,打包,上传流程。[https://github.com/wuba/https://github.com/wuba/magpie](https://github.com/wuba/https://github.com/wuba/magpie) 12 | 13 | > Magpie Native&Dart SDK 14 | 15 | 与Workflow配套,用于接入App,Flutter的SDK。[https://github.com/wuba/https://github.com/wuba/magpie_sdk](https://github.com/wuba/https://github.com/wuba/magpie_sdk) 16 | 17 | > Magpie Log 18 | 19 | 适用于Flutter平台下的圈选埋点库。[https://github.com/wuba/magpie_log](https://github.com/wuba/magpie_log) 20 | 21 | ## Pub使用 22 | 23 | 1. Depend on it 24 | Add this to your package's pubspec.yaml 25 | ``` 26 | dependencies: 27 | magpie_fly: ^0.0.1 28 | ``` 29 | 30 | 2. Install it 31 | You can install packages from the command line: 32 | ``` 33 | $ flutter pub get 34 | ``` 35 | 36 | 3. Import it 37 | Now in your Dart code, you can use: 38 | ``` 39 | import 'package:magpie_fly/magpie_fly.dart'; 40 | ``` 41 | 42 | ## 组件目录 43 | lib/src/ 44 | ``` 45 | lib 46 | ├── magpie_fly.dart 47 | └── src 48 | ├── banner 49 | ├── drag 50 | ├── feed 51 | ├── horizontal_scroll 52 | ├── popup_window 53 | ├── search_input 54 | ├── sliver 55 | ``` 56 | 57 | ## 组件清单 58 | 59 | | 组件 | 说明 | 60 | | -------------------- | --------------------------------------------- | 61 | | 视频滚动检测播放控件 | 用于视频列表的滚动检测和播放控制 | 62 | | 安全区吸顶组件 | Sliver状态栏安全区吸顶 | 63 | | 全屏拖动按钮 | 可用于右下角的广告位 | 64 | | 尾部弹性ScrollView | 一个支持滑动回弹加载更多的组件 | 65 | | popup_window | 一个支持任意方向上的popwindow | 66 | | 搜索组件 | 提供常用带阴影和不带阴影的搜索组件 | 67 | 68 | 69 | 70 | ## 致谢 71 | 在开发过程中,我们使用到了一些第三方依赖库,在此特别感谢Flutter&Dart社区的开发者们。[provider](https://pub.dev/packages/provider)、[cupertino_icons](https://pub.dev/packages/cupertino_icons)、[video_player](https://pub.dev/packages/video_player)、[flutter_markdown](https://pub.dev/packages/flutter_markdown)、[android_intent](https://pub.dev/packages/android_intent)、[flutter_webview_plugin](https://pub.dev/packages/flutter_webview_plugin)、[fluttertoast](https://pub.dev/packages/fluttertoast) 72 | -------------------------------------------------------------------------------- /create_demo.sh: -------------------------------------------------------------------------------- 1 | 2 | SHELL_FOLDER=$(cd "$(dirname "$0")";pwd) 3 | cd $SHELL_FOLDER/example 4 | 5 | dart create_demo/createDemo.dart 6 | -------------------------------------------------------------------------------- /example/.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"android_intent","dependencies":[]},{"name":"flutter_webview_plugin","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"video_player","dependencies":["video_player_web"]},{"name":"video_player_web","dependencies":[]}]} -------------------------------------------------------------------------------- /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: 27321ebbad34b0a3fafe99fac037102196d655ff 8 | channel: v1.12.13-hotfixes 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/Magpie.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/Magpie.gif -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | 2 | [toc] 3 | 4 | # Magpie-fly 5 | Magpie-fly 是58集体出品组件库,封装了多种常用组件,以满足开发者需求。 6 | 7 | ## 功能 8 | 1. 58公众号信息 9 | * 显示了58公众号信息,方便58获取最新资讯 10 | 2. 组件列表和详情 11 | * 详情页通过加载markdown的方式进行显示 12 | 1. [使用文档](doc/magpie_ui.md)及[贡献流程文档](doc/contribution.md) 13 | * 介绍了组件库的使用方式和接入组件的具体流程 14 | 4. create_demo脚本 15 | * 自动生成demo.dart,.page.json,demo.md,让开发者能快速的接入组件 16 | 17 | ## 基础环境 18 | 19 | 开始使用前,请确保基础环境配置正常
20 | 如有问题,请参考https://flutterchina.club/setup-macos/ 21 | 22 | * `flutter --version` 23 | ``` 24 | Flutter 1.12.13+hotfix.6 • channel beta • https://github.com/flutter/flutter.git 25 | Framework • revision 18cd7a3601 (13 天前) • 2019-12-11 06:35:39 -0800 26 | Engine • revision 2994f7e1e6 27 | Tools • Dart 2.7.0 28 | ``` 29 | 30 | * `dart --version` 31 | ``` 32 | Dart VM version: 2.7.0 (Mon Dec 2 20:10:59 2019 +0100) on "macos_x64" 33 | ``` 34 | 35 | * `pub --version` 36 | ``` 37 | Pub 2.7.0 38 | ``` 39 | 40 | * `flutter doctor` 41 | ``` 42 | 检查有没有缺失项 43 | ``` 44 | 45 | ## 配置编辑器(二选一) 46 | [安装Android Studio](https://flutterchina.club/get-started/editor/) 47 | [安装Visual Studio](https://flutterchina.club/get-started/editor/#vscode) 48 | 49 | ## 运行 50 | 用编辑器打开下载好的Magpie-fly工程,运行 51 | ``` 52 | magpie_fly/example/lib/main.dart 53 | ``` 54 | 55 | ## 整体设计 56 | 1. 目录结构 57 | 58 | * 各个组件的代码在项目根目录/lib下,每个文件夹对应一个组件。 59 | ``` 60 | lib 61 | ├── magpie_ui.dart 62 | └── src 63 | | ├── drag 64 | | │ └── draggable_btn.dart 65 | | ├── feed 66 | | │ ├── meta_consumer.dart 67 | | │ ├── scroll_detect_listener.dart 68 | | │ ├── video_meta.dart 69 | | │ └── video_play_model.dart 70 | ``` 71 | 72 | * 组件的演示demo以及文档都在`/example/lib/demo`目录下,每个文件夹对应一个组件,里面有`xxx_xxx_demo.dart`、对应的文档`xxx.md`以及描述组件的信息文件.page.json,执行create_demo.sh脚本之后,会自动生成默认生成这三个文件,一般不需要修改。 73 | ``` 74 | demo 75 | │ ├── draggable_btn 76 | │ │ ├── drag_btn_demo.dart 77 | │ │ ├── .page.json 78 | │ │ └── dragable_btn.md 79 | │ ├── horizonal_scroll 80 | │ │ ├── HorizontalScrollCustom.dart 81 | │ │ ├── HorizontalScrollDefault.dart 82 | │ │ ├── HorizontalScrollText.dart 83 | │ │ ├── component 84 | │ │ │ └── card.dart 85 | │ │ └── horizonal_scroll.md 86 | │ │ └── .page.json 87 | ``` 88 | 89 | 2 组件详情页采用markdown+组件+代码的组合方式,介绍组件的详细信息和组件效果,在查看文档的同时还能操作具体组件。 90 | ![](http://wos.58cdn.com.cn/IjGfEdCbIlr/ishare/2921ed14-4356-4651-9466-74d8bb855ee3效果.jpg) 91 | 92 | ## 组件接入 93 | 请参考[how_to_add_component.md](doc/how_to_add_component.md) 94 | 95 | ## 整体效果 96 | ![](Magpie.gif) 97 | 98 | ## Magpie-fly优势 99 | - 整合了开发过程中常用的组件 100 | - 采用Markdown+组件+代码的方式介绍具体组件的使用和效果 101 | - 可添加常用widget的使用方式,让想学习flutter 102 | 快速上手 -------------------------------------------------------------------------------- /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.magpie_fly_example" 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 | implementation 'androidx.legacy:legacy-support-core-ui:1.0.0' 69 | implementation 'androidx.lifecycle:lifecycle-viewmodel:2.1.0' 70 | implementation 'androidx.fragment:fragment:1.1.0' 71 | } 72 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 13 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/magpie_fly_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.magpie_fly_example 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/magpie_fly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/android/app/src/main/res/drawable/magpie_fly.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/android/app/src/main/res/mipmap-xxhdpi/logo.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/assets/images/2.0x/tab_nomal1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/2.0x/tab_nomal1@2x.png -------------------------------------------------------------------------------- /example/assets/images/2.0x/tab_nomal2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/2.0x/tab_nomal2@2x.png -------------------------------------------------------------------------------- /example/assets/images/2.0x/tab_nomal3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/2.0x/tab_nomal3@2x.png -------------------------------------------------------------------------------- /example/assets/images/2.0x/tab_nomal4@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/2.0x/tab_nomal4@2x.png -------------------------------------------------------------------------------- /example/assets/images/2.0x/tab_selected1@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/2.0x/tab_selected1@2x.png -------------------------------------------------------------------------------- /example/assets/images/2.0x/tab_selected2@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/2.0x/tab_selected2@2x.png -------------------------------------------------------------------------------- /example/assets/images/2.0x/tab_selected3@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/2.0x/tab_selected3@2x.png -------------------------------------------------------------------------------- /example/assets/images/2.0x/tab_selected4@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/2.0x/tab_selected4@2x.png -------------------------------------------------------------------------------- /example/assets/images/3.0x/tab_nomal1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/3.0x/tab_nomal1@3x.png -------------------------------------------------------------------------------- /example/assets/images/3.0x/tab_nomal2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/3.0x/tab_nomal2@3x.png -------------------------------------------------------------------------------- /example/assets/images/3.0x/tab_nomal3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/3.0x/tab_nomal3@3x.png -------------------------------------------------------------------------------- /example/assets/images/3.0x/tab_nomal4@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/3.0x/tab_nomal4@3x.png -------------------------------------------------------------------------------- /example/assets/images/3.0x/tab_selected1@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/3.0x/tab_selected1@3x.png -------------------------------------------------------------------------------- /example/assets/images/3.0x/tab_selected2@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/3.0x/tab_selected2@3x.png -------------------------------------------------------------------------------- /example/assets/images/3.0x/tab_selected3@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/3.0x/tab_selected3@3x.png -------------------------------------------------------------------------------- /example/assets/images/3.0x/tab_selected4@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/3.0x/tab_selected4@3x.png -------------------------------------------------------------------------------- /example/assets/images/tab_nomal1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/tab_nomal1.png -------------------------------------------------------------------------------- /example/assets/images/tab_nomal2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/tab_nomal2.png -------------------------------------------------------------------------------- /example/assets/images/tab_nomal3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/tab_nomal3.png -------------------------------------------------------------------------------- /example/assets/images/tab_nomal4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/tab_nomal4.png -------------------------------------------------------------------------------- /example/assets/images/tab_selected1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/tab_selected1.png -------------------------------------------------------------------------------- /example/assets/images/tab_selected2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/tab_selected2.png -------------------------------------------------------------------------------- /example/assets/images/tab_selected3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/tab_selected3.png -------------------------------------------------------------------------------- /example/assets/images/tab_selected4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/assets/images/tab_selected4.png -------------------------------------------------------------------------------- /example/create_demo/createDemo.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | // import 'package:args/args.dart'; 3 | import 'fileUtil.dart'; 4 | 5 | 6 | Future main(List arguments) async{ 7 | 8 | // print('请输入Demo名称(限制英文,且数字不在第一位,如:test_widget):'); 9 | print('input your Demo name(eg:test_widget):'); 10 | String name = stdin.readLineSync() ?? 'test_widget'; 11 | print(''); 12 | 13 | print('input you component title:'); 14 | String title = stdin.readLineSync() ?? ''; 15 | print(''); 16 | 17 | 18 | print('input you component description:'); 19 | String desc = stdin.readLineSync() ?? ''; 20 | print(''); 21 | 22 | print('您的demo在example/lib/demo/$name下,组件名称:$title,组件描述:$desc'); 23 | 24 | var demoName = '$name'; 25 | var className = underScore2CamelCase(name); 26 | var demoPath = 'lib/demo/$demoName'; 27 | var stateName = '_${className}State'; 28 | await createFile('lib/demo'); 29 | await createFile(demoPath); 30 | 31 | //创建.dart文件 32 | await writeContent2Path(demoPath, '${demoName}_demo.dart', 33 | """ 34 | import 'package:flutter/material.dart'; 35 | 36 | class $className extends StatefulWidget { 37 | @override 38 | State createState() => $stateName(); 39 | } 40 | 41 | class $stateName extends State<$className> { 42 | @override 43 | Widget build(BuildContext context) { 44 | return Container( 45 | child: Text("this is flutter go init demo"), 46 | ); 47 | } 48 | } 49 | """); 50 | 51 | //创建rm文件 52 | var rmName = '$demoName.md'; 53 | await writeContent2Path(demoPath, rmName, 54 | """ 55 | 56 | 57 | ## 简介 58 | * 名称:你的组件名称 59 | * 概述:你的组件概述 60 | * 作者:你的邮箱 61 | 62 | ## 导入(安装) 63 | import 'package:magpie_ui/magpie_ui.dart'; 64 | 65 | ## 概述 66 | XXXXXXXXXXX 67 | 68 | ## 使用 69 | 70 | 77 | 效果: 78 | {{"demo": "lib/demo/$demoName/${demoName}_demo.dart"}} 79 | 80 | ## 参数配置 81 | 82 | | 参数 | 描述 | 83 | | --- | --- | 84 | 85 | """ 86 | ); 87 | 88 | 89 | //写json 90 | await writeContent2Path(demoPath, '.page.json', 91 | """ 92 | { 93 | "name": "$name", 94 | "title": "$title", 95 | "desc": "$desc", 96 | "markdown": "$demoPath/$rmName" 97 | } 98 | """ 99 | ); 100 | 101 | //写列表数据 102 | buildList().then((list){ 103 | writeContent2Path('lib/demo', 'ListDatas.dart', 104 | """ 105 | //列表页数据 106 | 107 | List> DemolistDatas = $list; 108 | """ 109 | ); 110 | }); 111 | 112 | } 113 | 114 | Future buildList() async { 115 | var demoPath = 'lib/demo/'; 116 | var directory = Directory(demoPath); 117 | List files = directory.listSync(); 118 | 119 | List list = []; 120 | for (var file in files) { 121 | var isdir = FileSystemEntity.isDirectorySync(file.path); 122 | //文件夹 123 | if(isdir){ 124 | var subdirectory = Directory(file.path); 125 | List subfiles = subdirectory.listSync(); 126 | for (var subFile in subfiles) { 127 | //取出md文件 128 | if(FileSystemEntity.isFileSync(subFile.path) && subFile.path.endsWith('.page.json')){ 129 | var file = File(subFile.path); 130 | try { 131 | String jsonstring = file.readAsStringSync(); 132 | list.add(jsonstring); 133 | } catch (e) { 134 | print(e.toString()); 135 | } 136 | break; 137 | } 138 | } 139 | } 140 | } 141 | return list; 142 | } 143 | 144 | String underScore2CamelCase(String key) { 145 | RegExp regex = new RegExp(r'[_]+(.)'); 146 | String s = key.replaceAllMapped(regex, (m) { 147 | return m.group(1).toUpperCase(); 148 | }); 149 | return '${s[0].toUpperCase()}${s.substring(1)}'; 150 | } 151 | 152 | 153 | -------------------------------------------------------------------------------- /example/create_demo/fileUtil.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | Future createFile(String path) async { 4 | final tempDic = new Directory(path); 5 | var exits = await tempDic.exists(); 6 | if (exits) { 7 | return Future(() => false); 8 | } 9 | tempDic.createSync(recursive: true); 10 | return Future(() => true); 11 | } 12 | 13 | // 该文件调用的时候. 必须存在父级文件夹 14 | Future writeContent2Path(String path,String fileName, String content) async { 15 | var file = File('$path/$fileName'); 16 | var sink = file.openWrite(); 17 | sink.write(content); 18 | await sink.flush(); 19 | await sink.close(); 20 | return Future.value(); 21 | } -------------------------------------------------------------------------------- /example/doc/contribution.md: -------------------------------------------------------------------------------- 1 | [toc] 2 | 3 | ## 贡献流程 4 | * fork Magpie Fly仓库
5 | https://github.com/wuba/magpie_ui.git **地址待定** 6 | 7 | ``` 8 | git clone https://github.com/{your organization}/magpie_ui.git 9 | ``` 10 | 11 | * 添加远程地址 12 | ``` 13 | cd magpie_ui 14 | git remote add upstream https://github.com/wuba/magpie_ui.git 15 | ``` 16 | 17 | * 创建开发分支 18 | ``` 19 | git checkout -b {your branch name} 20 | ``` 21 | * 如何创建组件模板,请参考[使用文档](doc/magpie_ui.md) 22 | 23 | * 提交代码 24 | ``` 25 | git commit -m "{your commit msg}" 26 | ``` 27 | 28 | * rebase master 29 | ``` 30 | git checkout master//切到master分支 31 | git pull upstream master//拉取上游master最新代码 32 | git checkout {your branch name}//切回开发分支 33 | git rebase master//rebase master 34 | git push origin {your branch name}//提交代码到远程开发分支 35 | ``` 36 | 37 | * 申请Pull Request 38 | 39 | 在你的github仓库,切到{your branch name},点击`New Pull Request`,填写msg提交即可 40 | -------------------------------------------------------------------------------- /example/doc/how_to_add_component.md: -------------------------------------------------------------------------------- 1 | 2 | [toc] 3 | 4 | # 怎么样快速的接入组件 5 | 6 | ## 安装 7 | Mac下安装dart: 8 | ``` 9 | brew tap dart-lang/dart 10 | brew install dart 11 | ``` 12 | 13 | ## 进入magpie_ui工程目录,运行脚本: 14 | ``` 15 | sh create_demo.sh 16 | ``` 17 | 18 | ------- 19 | 根据提示依次输入Demo名称、组件名称和组件描述,回车运行脚本。 20 | 运行脚本主要做两件事: 21 | 1. 在example/lib/demo中生成对应组件的文件夹。如图: 22 | ![](resource/create_demo1.jpg) 23 | 24 | 文件夹test_widget中自动生成如图所示的三个文件: 25 | `.page.json格式:` 26 | 27 | ``` 28 | { 29 | "name": "demo名称", 30 | "title": "组件名称", 31 | "desc": "组件描述", 32 | "markdown": "lib/demo/test_widget/test_widget.md" 33 | } 34 | ``` 35 | markdown是字段,是用来记录对应的组件的md文件的位置,作用是给点击list之后跳到相应的详情页。 36 | 37 | `.md文件` 38 | 对应的组件的markdown,用于组件详情页的展示, 39 | 可以在markdown中插入想要展示的demo样式。格式如下: 40 | 41 | ``` 42 | {{"demo": "lib/demo/horizonal_scroll/HorizontalScrollDefault.dart", "code": true, "jump":true}} 43 | ``` 44 | 参数详解: 45 | * demo:需要引入的样式的路径 46 | * code:在样式下面是否显示代码 47 | * jump:该样式是否需要跳转界面显示(如果效果是全屏的效果,最好跳到下一页显示) 48 | 49 | `.dart文件` 50 | markdown中需要引入的样式的实现文件 51 | 52 | 2. 自动在ListDatas中加入需要在list中显示的组件。 53 | 54 | ![](resource/create_demo2.jpg) 55 | 56 | ## 配置pubspec.yaml 57 | 因为需要读取.md和.dart文件,所以需要配置assets,如: 58 | ``` 59 | - lib/demo/test_wdiget/ 60 | ``` 61 | 62 | ## 配置demo 63 | 需要在util/config.dart中配置自己的demo数据: 64 | * key: markdown中配置的'demo'的value 65 | * value: 返回对应的demo的实例对象,用于在详情页中展现 66 | 67 | 如markdown中配置了 68 | `{{"demo": "lib/demo/test_widget/test_widget", "code": true, "jump":true}}` 69 | 70 | 则需要在config.dart中配置: 71 | `'lib/demo/test_widget/test_widget': () => TestWidget(),` 72 | 73 | 至此就完成了一个简单的接入工作,运行工程,会在列表页看见加入的demo。 74 | 75 | ## 加入自己的组件进行显示 76 | 将自己的组件放入外层的lib/src文件夹中,然后在自己的demo中引用自己的组件就可以啦。 77 | ![](resource/create_demo3.jpg) -------------------------------------------------------------------------------- /example/doc/magpie_ui.md: -------------------------------------------------------------------------------- 1 | 2 | [toc] 3 | 4 | # Magpie Fly使用文档 5 | 6 | ## 简介 7 | Magpie Fly 是58集体出品组件库,封装了多种常用组件,以满足开发者需求。
8 | 9 | 包含内容: 10 | 11 | * 视频Feed流滚动探测播放插件 12 | 13 | * 混合嵌套列列表中的吸顶安全区解决⽅方案 14 | 15 | * 一个支持多种形式的高性能轮播组件 16 | 17 | * 一个带阻尼回弹效果的横向加载更多组件 18 | 19 | * 一个任意位置,箭头跟踪View的弹层效果组件 20 | 21 | * 可拖动的按钮(可用于悬浮广告位) 22 | 23 | * 带阴影描边的搜索输入框 24 | 25 | 26 | ## 基础环境 27 | 28 | 开始使用前,请确保基础环境配置正常
29 | 如有问题,请参考https://flutterchina.club/setup-macos/ 30 | 31 | * `flutter --version` 32 | ``` 33 | Flutter 1.12.13+hotfix.6 • channel beta • https://github.com/flutter/flutter.git 34 | Framework • revision 18cd7a3601 (13 天前) • 2019-12-11 06:35:39 -0800 35 | Engine • revision 2994f7e1e6 36 | Tools • Dart 2.7.0 37 | ``` 38 | 39 | * `dart --version` 40 | ``` 41 | Dart VM version: 2.7.0 (Mon Dec 2 20:10:59 2019 +0100) on "macos_x64" 42 | ``` 43 | 44 | * `pub --version` 45 | ``` 46 | Pub 2.7.0 47 | ``` 48 | 49 | * `flutter doctor` 50 | ``` 51 | 检查有没有缺失项 52 | ``` 53 | 54 | ## 使用方式 55 | 56 | * 添加依赖 57 | ``` 58 | dependencies 59 | magpie_fly: ^0.0.1 60 | ``` 61 | * 下载依赖 62 | ``` 63 | flutter pub get 64 | ``` 65 | * 导入 66 | ``` 67 | import 'package:magpie_fly/magpie_fly.dart'; 68 | ``` 69 | 70 | 71 | ## 如何快速创建组件模块? 72 | 参考: [快速接入组件](doc/how_to_add_component.md) 73 | 74 | 75 | -------------------------------------------------------------------------------- /example/doc/resource/create_demo1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/doc/resource/create_demo1.jpg -------------------------------------------------------------------------------- /example/doc/resource/create_demo2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/doc/resource/create_demo2.jpg -------------------------------------------------------------------------------- /example/doc/resource/create_demo3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/doc/resource/create_demo3.jpg -------------------------------------------------------------------------------- /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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/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/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "extent" : "full-screen", 5 | "idiom" : "iphone", 6 | "subtype" : "2688h", 7 | "filename" : "启动页1242X2688.png", 8 | "minimum-system-version" : "12.0", 9 | "orientation" : "portrait", 10 | "scale" : "3x" 11 | }, 12 | { 13 | "extent" : "full-screen", 14 | "idiom" : "iphone", 15 | "subtype" : "1792h", 16 | "filename" : "启动页828X1792.png", 17 | "minimum-system-version" : "12.0", 18 | "orientation" : "portrait", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "extent" : "full-screen", 23 | "idiom" : "iphone", 24 | "subtype" : "2436h", 25 | "filename" : "启动页1125X2436.png", 26 | "minimum-system-version" : "11.0", 27 | "orientation" : "portrait", 28 | "scale" : "3x" 29 | }, 30 | { 31 | "extent" : "full-screen", 32 | "idiom" : "iphone", 33 | "subtype" : "736h", 34 | "filename" : "启动页1242X2208.png", 35 | "minimum-system-version" : "8.0", 36 | "orientation" : "portrait", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "extent" : "full-screen", 41 | "idiom" : "iphone", 42 | "subtype" : "667h", 43 | "filename" : "启动页750X1334.png", 44 | "minimum-system-version" : "8.0", 45 | "orientation" : "portrait", 46 | "scale" : "2x" 47 | }, 48 | { 49 | "orientation" : "portrait", 50 | "idiom" : "iphone", 51 | "filename" : "启动页640X960.png", 52 | "extent" : "full-screen", 53 | "minimum-system-version" : "7.0", 54 | "scale" : "2x" 55 | }, 56 | { 57 | "extent" : "full-screen", 58 | "idiom" : "iphone", 59 | "subtype" : "retina4", 60 | "filename" : "启动页640X1136.png", 61 | "minimum-system-version" : "7.0", 62 | "orientation" : "portrait", 63 | "scale" : "2x" 64 | }, 65 | { 66 | "orientation" : "portrait", 67 | "idiom" : "iphone", 68 | "filename" : "启动页320X480.png", 69 | "extent" : "full-screen", 70 | "scale" : "1x" 71 | }, 72 | { 73 | "orientation" : "portrait", 74 | "idiom" : "iphone", 75 | "filename" : "启动页640X960-1.png", 76 | "extent" : "full-screen", 77 | "scale" : "2x" 78 | }, 79 | { 80 | "orientation" : "portrait", 81 | "idiom" : "iphone", 82 | "filename" : "启动页640X1136-1.png", 83 | "extent" : "full-screen", 84 | "subtype" : "retina4", 85 | "scale" : "2x" 86 | } 87 | ], 88 | "info" : { 89 | "version" : 1, 90 | "author" : "xcode" 91 | } 92 | } -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页1125X2436.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页1125X2436.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页1242X2208.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页1242X2208.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页1242X2688.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页1242X2688.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页320X480.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页320X480.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页640X1136-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页640X1136-1.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页640X1136.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页640X1136.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页640X960-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页640X960-1.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页640X960.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页640X960.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页750X1334.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页750X1334.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页828X1792.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wuba/magpie_fly/8bfc005b1c74056108e8ec6c1c78f67ceb71bdb8/example/ios/Runner/Assets.xcassets/LaunchImage.launchimage/启动页828X1792.png -------------------------------------------------------------------------------- /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 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /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 | Magpie-Fly 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UIMainStoryboardFile 31 | Main 32 | UISupportedInterfaceOrientations 33 | 34 | UIInterfaceOrientationPortrait 35 | 36 | UISupportedInterfaceOrientations~ipad 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationPortraitUpsideDown 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | UIViewControllerBasedStatusBarAppearance 44 | 45 | io.flutter.embedded_views_preview 46 | YES 47 | 48 | 49 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /example/lib/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 2 | 3 | import 'markdown_doc.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'news.dart'; 6 | import 'list.dart'; 7 | 8 | class AppPage extends StatefulWidget { 9 | @override 10 | State createState() => AppPageState(); 11 | } 12 | 13 | class AppPageState extends State { 14 | //数据 15 | static List tabDatas = [ 16 | {'title': '58公众号', 'icon':Image.asset('assets/images/tab_nomal1.png'), 'selectIcon':Image.asset('assets/images/tab_selected1.png')}, 17 | {'title': 'Widget', 'icon':Image.asset('assets/images/tab_nomal2.png'), 'selectIcon':Image.asset('assets/images/tab_selected2.png')}, 18 | {'title': '使用文档', 'icon':Image.asset('assets/images/tab_nomal3.png'), 'selectIcon':Image.asset('assets/images/tab_selected3.png')}, 19 | {'title': '贡献流程', 'icon':Image.asset('assets/images/tab_nomal4.png'), 'selectIcon':Image.asset('assets/images/tab_selected4.png')}, 20 | ]; 21 | 22 | List _pages = List(); 23 | List _tabs = []; 24 | var _currentIndex = 0; 25 | var _currentTitle = tabDatas[0]['title']; 26 | final flutterWebviewPlugin = new FlutterWebviewPlugin(); 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | 32 | //页面列表 33 | _pages..add(NewsPage(_currentTitle))..add(ListPage())..add(MarkDownDoc(mdPath: 'doc/magpie_ui.md',))..add(MarkDownDoc(mdPath: 'doc/contribution.md',)); 34 | 35 | //tab列表 36 | tabDatas.forEach((map) { 37 | _tabs.add(BottomNavigationBarItem( 38 | title: Text(map['title'], style: TextStyle(fontSize: 10),), 39 | icon:map['icon'], 40 | activeIcon: map['selectIcon'] 41 | )); 42 | }); 43 | } 44 | 45 | @override 46 | void dispose() { 47 | flutterWebviewPlugin.dispose(); 48 | super.dispose(); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return Scaffold( 54 | appBar: _currentIndex==0?null:AppBar(title: Text(_currentTitle)), 55 | body: IndexedStack( 56 | index: _currentIndex, 57 | children: _pages, 58 | ), 59 | bottomNavigationBar: BottomNavigationBar( 60 | items: _tabs, 61 | currentIndex: _currentIndex, 62 | onTap: tabClick, 63 | type: BottomNavigationBarType.fixed, 64 | selectedFontSize:16, 65 | unselectedFontSize: 14, 66 | ), 67 | ); 68 | } 69 | 70 | void tabClick(int index) { 71 | setState(() { 72 | _currentIndex = index; 73 | _currentTitle = tabDatas[index]['title']; 74 | if(_currentIndex != 0){ 75 | flutterWebviewPlugin.hide(); 76 | }else{ 77 | flutterWebviewPlugin.show(); 78 | } 79 | }); 80 | } 81 | } -------------------------------------------------------------------------------- /example/lib/comon_widget/code_component.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import '../util/syntax_highlighter.dart'; 4 | 5 | //代码和组件的组合控件 6 | 7 | class CodeComponent extends StatefulWidget { 8 | final Widget component; 9 | final String codePath; 10 | 11 | CodeComponent({this.component, this.codePath}); 12 | 13 | @override 14 | State createState() { 15 | return _CodeComponentState(); 16 | } 17 | } 18 | 19 | class _CodeComponentState extends State { 20 | bool _codeIsOpen = false; 21 | String _code = ''; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | if (widget.codePath != null && widget.codePath.length > 0) { 27 | readCodeFile().then((code) { 28 | setState(() { 29 | _code = code ?? ''; 30 | }); 31 | }); 32 | } 33 | } 34 | 35 | Future readCodeFile() async { 36 | try { 37 | var a = await rootBundle.loadString(widget.codePath); 38 | return a; 39 | } catch (e) { 40 | print(e.toString()); 41 | } 42 | return ''; 43 | } 44 | 45 | @override 46 | Widget build(BuildContext context) { 47 | return Column( 48 | children: [ 49 | widget.component==null?Text('') : widget.component, 50 | _code.length == 0 ? Text('') : _handleButttons(), 51 | _code.length == 0 52 | ? Text('') 53 | : Container( 54 | margin: EdgeInsets.symmetric( 55 | vertical: _codeIsOpen == false ? 0 : 10), 56 | padding: EdgeInsets.all(_codeIsOpen == false ? 0 : 10), 57 | child: _codeIsOpen == false ? Text('') : _codeWidget(), 58 | decoration: BoxDecoration( 59 | borderRadius: BorderRadius.all(Radius.circular(5)), 60 | color: Color.fromRGBO(29, 30, 25, 1), 61 | ), 62 | ) 63 | ], 64 | ); 65 | } 66 | 67 | Widget _handleButttons() { 68 | return Row( 69 | children: [ 70 | Expanded( 71 | child: Text(''), 72 | ), 73 | IconButton( 74 | icon: Icon(Icons.code), 75 | onPressed: () { 76 | setState(() { 77 | _codeIsOpen = !_codeIsOpen; 78 | }); 79 | }, 80 | ), 81 | IconButton( 82 | icon: Icon(Icons.content_copy), 83 | onPressed: () { 84 | Clipboard.setData(new ClipboardData(text: _code)); 85 | Scaffold.of(context).showSnackBar(SnackBar( 86 | content: Text('已复制到剪切板'), 87 | duration: Duration(milliseconds: 2000), 88 | backgroundColor: Colors.grey, 89 | )); 90 | }, 91 | ) 92 | ], 93 | ); 94 | } 95 | 96 | Widget _codeWidget() { 97 | final SyntaxHighlighterStyle style = 98 | SyntaxHighlighterStyle.magpieDarkThemeStyle(); 99 | Widget _codeWidget; 100 | try { 101 | DartSyntaxHighlighter(style).format(_code); 102 | _codeWidget = RichText( 103 | text: TextSpan( 104 | style: const TextStyle(fontFamily: 'monospace', fontSize: 16.0), 105 | children: [DartSyntaxHighlighter(style).format(_code)], 106 | ), 107 | ); 108 | } catch (err) { 109 | _codeWidget = Text(_code); 110 | } 111 | 112 | return Scrollbar( 113 | child: SingleChildScrollView( 114 | scrollDirection: Axis.horizontal, child: _codeWidget), 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /example/lib/comon_widget/effect_placeholder.dart: -------------------------------------------------------------------------------- 1 | import 'fullscreen_effect.dart'; 2 | import '../util/parse_markdown.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class EffectPlaceHolder extends StatelessWidget { 6 | final Model model; 7 | 8 | const EffectPlaceHolder({Key key, this.model}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return InkWell( 13 | onTap: () => Navigator.push( 14 | context, MaterialPageRoute(builder: (context) => FullScreenEffect(model: model,))), 15 | child: Container( 16 | alignment: Alignment.center, 17 | height: 44, 18 | margin: const EdgeInsets.only(bottom: 20, left: 60, right: 60), 19 | decoration: BoxDecoration( 20 | borderRadius: BorderRadius.circular(22), 21 | border: Border.all(width:1, color:Theme.of(context).primaryColor) 22 | ), 23 | child: Text( 24 | "点击查看效果&代码", 25 | style: TextStyle(color: Theme.of(context).primaryColor, fontSize: 14.0), 26 | ), 27 | ), 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /example/lib/comon_widget/fullscreen_code.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import '../util/syntax_highlighter.dart'; 4 | 5 | class FullScreenCode extends StatelessWidget { 6 | final String code; 7 | 8 | const FullScreenCode({Key key, this.code}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | final key = new GlobalKey(); 13 | final SyntaxHighlighterStyle style = 14 | Theme.of(context).brightness == Brightness.dark 15 | ? SyntaxHighlighterStyle.darkThemeStyle() 16 | : SyntaxHighlighterStyle.lightThemeStyle(); 17 | Widget _codeWidget; 18 | try { 19 | DartSyntaxHighlighter(style).format(code); 20 | _codeWidget = RichText( 21 | text: TextSpan( 22 | style: const TextStyle(fontFamily: 'monospace', fontSize: 12.0), 23 | children: [DartSyntaxHighlighter(style).format(code)], 24 | ), 25 | ); 26 | } catch (err) { 27 | _codeWidget = Text(code); 28 | } 29 | 30 | return Scaffold( 31 | key: key, 32 | appBar: AppBar( 33 | title: Text("Example Code"), 34 | actions: [ 35 | IconButton( 36 | tooltip: "copy", 37 | onPressed: () { 38 | Clipboard.setData(new ClipboardData(text: code)); 39 | key.currentState.showSnackBar( 40 | new SnackBar(content: new Text("已复制到粘贴板"),)); 41 | }, 42 | icon: Text("复制"), 43 | ) 44 | ], 45 | ), 46 | body: SingleChildScrollView( 47 | child: Padding( 48 | padding: const EdgeInsets.all(16.0), 49 | child: _codeWidget, 50 | ), 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /example/lib/comon_widget/fullscreen_effect.dart: -------------------------------------------------------------------------------- 1 | import 'fullscreen_code.dart'; 2 | import '../util/config.dart'; 3 | import '../util/parse_markdown.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | 7 | class FullScreenEffect extends StatelessWidget { 8 | final Model model; 9 | 10 | const FullScreenEffect({Key key, this.model}) : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | List actions = [ 15 | IconButton( 16 | tooltip: "source", 17 | onPressed: () { 18 | rootBundle.loadString(model.demo).then((value) { 19 | Navigator.push(context, MaterialPageRoute( 20 | builder: (context) => FullScreenCode(code: value))); 21 | }); 22 | }, 23 | icon: Icon(Icons.code), 24 | ) 25 | ]; 26 | 27 | if (model.needAppbar) { 28 | return Scaffold( 29 | appBar: AppBar( 30 | title: Text(model.className), 31 | actions: actions, 32 | ), 33 | body: Container( 34 | alignment: Alignment.center, 35 | child: markdownPathToWidget[model.demo](), 36 | ), 37 | ); 38 | } 39 | return markdownPathToWidget[model.demo](); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/lib/comon_widget/markdown_page.dart: -------------------------------------------------------------------------------- 1 | import 'effect_placeholder.dart'; 2 | import '../util/config.dart'; 3 | import '../util/parse_markdown.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'code_component.dart'; 6 | import 'widget_demo.dart'; 7 | 8 | class MarkdownPage extends StatefulWidget { 9 | MarkdownPage({@required this.filePath}); 10 | 11 | final String filePath; 12 | 13 | @override 14 | State createState() { 15 | return _MarkdownPageState(); 16 | } 17 | } 18 | 19 | class _MarkdownPageState extends State { 20 | List _contentList = []; 21 | bool _loadMarkdownError = false; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | parseMarkdown(widget.filePath).then((List list) { 27 | if (list == null) { 28 | _loadMarkdownError = true; 29 | } else { 30 | _loadMarkdownError = false; 31 | for (var item in list) { 32 | if (item is String) { 33 | _contentList.add(item); 34 | } else if (item is Model && markdownPathToWidget[item.demo] != null) { 35 | if(!item.isJump){ 36 | Widget wid = parseContentWidget(item); 37 | _contentList.add(wid); 38 | }else{ 39 | _contentList.add(EffectPlaceHolder(model: item)); 40 | } 41 | } 42 | } 43 | } 44 | setState(() {}); 45 | }); 46 | } 47 | 48 | Widget parseContentWidget(Model model) { 49 | try { 50 | Widget w = markdownPathToWidget[model.demo](); 51 | return CodeComponent(component: w, codePath: (model.code!=null&&model.code==true)?model.demo:null); 52 | } catch (e) { 53 | print('parseContentWidget : ${e.toString()}'); 54 | } 55 | return Text(''); 56 | } 57 | 58 | @override 59 | Widget build(BuildContext context) { 60 | return WidgetDemo( 61 | contentList: _loadMarkdownError?[Text('请在pubspec.yaml中配置assets \n${widget.filePath}')]:_contentList, 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /example/lib/comon_widget/widget_demo.dart: -------------------------------------------------------------------------------- 1 | /// @author Nealyang 2 | /// 新widget详情页模板 3 | 4 | import 'dart:core'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_markdown/flutter_markdown.dart'; 7 | 8 | class WidgetDemo extends StatefulWidget { 9 | final List contentList; 10 | final String docUrl; 11 | final String codeUrl; 12 | final Widget bottomNaviBar; 13 | 14 | WidgetDemo( 15 | {Key key, 16 | @required this.contentList, 17 | this.codeUrl, 18 | this.docUrl, 19 | this.bottomNaviBar}) 20 | : super(key: key); 21 | 22 | _WidgetDemoState createState() => _WidgetDemoState(); 23 | } 24 | 25 | class _WidgetDemoState extends State { 26 | final GlobalKey _scaffoldKey = GlobalKey(); 27 | 28 | List _buildContent() { 29 | List _list = [ 30 | SizedBox( 31 | height: 10.0, 32 | ), 33 | ]; 34 | widget.contentList.forEach((item) { 35 | if (item.runtimeType == String) { 36 | _list.add(Container(alignment: Alignment.centerLeft, child: MarkdownBody(data: item))); 37 | _list.add( 38 | SizedBox( 39 | height: 20.0, 40 | ), 41 | ); 42 | } else { 43 | _list.add(item); 44 | } 45 | }); 46 | return _list; 47 | } 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | } 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Container( 57 | padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), 58 | child: ListView( 59 | shrinkWrap: true, 60 | padding: const EdgeInsets.all(0.0), 61 | children: [ 62 | Column( 63 | children: _buildContent(), 64 | ), 65 | ], 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /example/lib/demo/ListDatas.dart: -------------------------------------------------------------------------------- 1 | //列表页数据 2 | 3 | List> DemolistDatas = [ { 4 | "name": "video", 5 | "title": "视频滚动检测播放控件", 6 | "desc": "", 7 | "markdown": "lib/demo/video/video.md" 8 | } 9 | , { 10 | "name": "pinned_sliver", 11 | "title": "安全区吸顶组件", 12 | "desc": "安全区吸顶组件", 13 | "markdown": "lib/demo/pinned_sliver/pinned_sliver.md" 14 | } 15 | , { 16 | "name": "draggable_btn", 17 | "title": "全屏拖动按钮", 18 | "desc": "可用于右下角的广告位", 19 | "markdown": "lib/demo/draggable_btn/dragable_btn.md" 20 | }, { 21 | "name": "test_widget", 22 | "title": "测试组件", 23 | "desc": "这是一个测试组件", 24 | "markdown": "lib/demo/test_widget/test_widget.md" 25 | } 26 | , { 27 | "name": "horizonal_scroll", 28 | "title": "尾部弹性ScrollView", 29 | "desc": "一个支持滑动回弹加载更多的组件", 30 | "markdown": "lib/demo/horizonal_scroll/horizonal_scroll.md" 31 | } 32 | , { 33 | "name": "banner_widget", 34 | "title": "轮播组件", 35 | "desc": "一个定时自定义的轮播组件", 36 | "markdown": "lib/demo/banner_widget/banner_widget.md" 37 | } 38 | , { 39 | "name": "search_input", 40 | "title": "搜索组件", 41 | "desc": "", 42 | "markdown": "lib/demo/search_input/search_input.md" 43 | } 44 | , { 45 | "name": "popup_window", 46 | "title": "popup_window", 47 | "desc": "", 48 | "markdown": "lib/demo/popup_window/popup_window.md" 49 | } 50 | ]; 51 | -------------------------------------------------------------------------------- /example/lib/demo/banner_widget/.page.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "banner_widget", 3 | "title": "轮播组件", 4 | "desc": "一个定时自定义的轮播组件", 5 | "markdown": "lib/demo/banner_widget/banner_widget.md" 6 | } 7 | -------------------------------------------------------------------------------- /example/lib/demo/banner_widget/banner_widget.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 简介 4 | * 名称:轮播组件 5 | * 概述:一个定时自定义的轮播组件 6 | * 作者:linyueyang 7 | 8 | ## 导入(安装) 9 | import 'package:magpie_fly/magpie_fly.dart'; 10 | 11 | ## 概述 12 | 构成简单分三个部分 13 | 1.PageView 14 | 2.Timer定时器实现轮播 15 | 3.指示器 16 | 17 | 页面主体基于PageView,对外暴露单页View自定义;Timer定时器实现轮播, 18 | PageController 控制页面自定播放,监听页面变换,同步指示器。 19 | 20 | ## 使用 21 | 22 | 效果: 23 | {{"demo": "lib/demo/banner_widget/banner_widget_demo.dart", "code": true}} 24 | 25 | ## 参数配置 26 | 27 | | 参数 | 描述 | 28 | | --- | --- | 29 | | buildShowView | 每页Widget | 30 | | data | List数据集 | 31 | | height | 组件高度 | 32 | | width | 组件宽度 | 33 | | delayTime | 延迟轮播时间 | 34 | | scrollTime | 单次轮播时间 | 35 | | onBannerClickListener | 单次轮播时间 | 36 | | indicatorSelectColor | 指示器选中颜色 | 37 | | indicatorRadius | 指示器大小 | 38 | | indicatorRight | 指示器位置距右侧 | 39 | | indicatorBottom | 指示器位置距底部 | -------------------------------------------------------------------------------- /example/lib/demo/banner_widget/banner_widget_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magpie_fly/magpie_fly.dart'; 3 | 4 | class BannerWidgetDemo extends StatefulWidget { 5 | @override 6 | State createState() => BannerCardState(); 7 | } 8 | 9 | class BannerCardState extends State { 10 | @override 11 | Widget build(BuildContext context) { 12 | List dataList = [ 13 | { 14 | "image": 15 | "https:\/\/pic1.ajkimg.com\/display\/anjuke\/0aa79ba5fc359b552c2467b8d71d0972\/1035x429c.jpg?t=5", 16 | "jump_action": 17 | "openanjuke:\/\/jump\/core\/common?params=%7B%22url%22%3A%22https%3A%5C%2F%5C%2Fm.anjuke.com%5C%2Factivity%5C%2Fhuati%5C%2Fvoting%3Fis_title_transparent%3D1%26from%3Dreyi%22%2C%22title%22%3A%22%22%2C%22device_id%22%3A%22%22%2C%22list_name%22%3A%22%22%2C%22ajk_type%22%3A%220%22%2C%22pagetype%22%3A%22common%22%2C%22categoryid%22%3A%22%22%2C%22loadingtype%22%3A%220%22%2C%22backtoroot%22%3A%22false%22%2C%22domainTips%22%3A%22%22%2C%22backprotocal%22%3A%22%22%2C%22webType%22%3A%22%22%2C%22save_step%22%3Afalse%2C%22pullRefresh%22%3Afalse%2C%22settings%22%3A%7B%22hide_title_panel%22%3Afalse%2C%22contain_status_bar%22%3Atrue%2C%22status_bar_mode%22%3A%22%22%2C%22status_bar_color%22%3A%22%22%7D%7D&isFinish=false&needLogin=false&isBackToMain=false&isSlideinBottom=false&isNoAnimated=false" 18 | }, 19 | { 20 | "image": 21 | "https:\/\/pic1.ajkimg.com\/display\/anjuke\/7c7caca19bf78e8f486cfe768944c62a\/1035x429c.jpg?t=5", 22 | "jump_action": 23 | "openanjuke:\/\/jump\/core\/common?params=%7B%22url%22%3A%22https%3A%5C%2F%5C%2Fm.anjuke.com%5C%2Fkol%5C%2Fpolylist%5C%2F%3Fid%3D323%22%2C%22title%22%3A%22%22%2C%22device_id%22%3A%22%22%2C%22list_name%22%3A%22%22%2C%22ajk_type%22%3A%220%22%2C%22pagetype%22%3A%22common%22%2C%22categoryid%22%3A%22%22%2C%22loadingtype%22%3A%220%22%2C%22backtoroot%22%3A%22false%22%2C%22domainTips%22%3A%22%22%2C%22backprotocal%22%3A%22%22%2C%22webType%22%3A%22%22%2C%22save_step%22%3Afalse%2C%22pullRefresh%22%3Afalse%2C%22settings%22%3A%7B%22hide_title_panel%22%3Afalse%2C%22contain_status_bar%22%3Atrue%2C%22status_bar_mode%22%3A%22%22%2C%22status_bar_color%22%3A%22%22%7D%7D&isFinish=false&needLogin=false&isBackToMain=false&isSlideinBottom=false&isNoAnimated=false" 24 | } 25 | ]; 26 | 27 | return BannerWidget( 28 | height: 60, 29 | width: MediaQuery.of(context).size.width, 30 | data: dataList, 31 | buildShowView: (index, data) { 32 | //print(data); 33 | return Image.network( 34 | data["image"], 35 | height: 100.0, 36 | //设置图片的高 37 | width: 100.0, 38 | //设置图片的宽 39 | fit: BoxFit.cover, 40 | //BoxFit.fill 全图显示,显示可能拉伸或者充满 BoxFit.contain 全图显示 显示原比例,不需充满 BoxFit.cover 显示可能拉伸可能剪裁充满 BoxFit.fitWidth显示可能拉伸可能剪裁,宽度充满 BoxFit.fitHeight显示可能拉伸可能充满,高度充满 BoxFit.scaleDown 效果和contain差不多,但是此属性不允许显示超过源图片大小,可小不可大 41 | color: Colors.black, 42 | colorBlendMode: BlendMode.dstOver, 43 | //color colorBlendMode具体效果后面再研究,我也没太弄懂,应该是一个类似混合模式的东东 44 | alignment: Alignment.center, //可以控制实际图片在容器内的位置 45 | ); 46 | }, 47 | onBannerClickListener: (index, data) { 48 | print(index); 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /example/lib/demo/draggable_btn/.page.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "draggable_btn", 3 | "title": "全屏拖动按钮", 4 | "desc": "可用于右下角的广告位", 5 | "markdown": "lib/demo/draggable_btn/dragable_btn.md" 6 | } -------------------------------------------------------------------------------- /example/lib/demo/draggable_btn/drag_btn_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magpie_fly/magpie_fly.dart'; 3 | 4 | class DragBtnDemo extends StatefulWidget { 5 | @override 6 | _DragBtnDemoState createState() => _DragBtnDemoState(); 7 | } 8 | 9 | class _DragBtnDemoState extends State { 10 | @override 11 | Widget build(BuildContext context) { 12 | return Container( 13 | alignment: Alignment.center, 14 | color: Colors.black12, 15 | child: DraggableBtn( 16 | child: Container( 17 | width: 50, 18 | height: 50, 19 | decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.red), 20 | alignment: Alignment.center, 21 | child: Icon(Icons.favorite, color: Colors.yellow,), 22 | ), 23 | initOffset: Offset(MediaQuery 24 | .of(context) 25 | .size 26 | .width - 100, 27 | MediaQuery 28 | .of(context) 29 | .size 30 | .height - 300), 31 | fullScreenOffset: Offset(0, 100), 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /example/lib/demo/draggable_btn/dragable_btn.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | * 名称:DraggableBtn 3 | * 概述:跟随手指滚动的按钮(适用于悬浮广告按钮) 4 | * 作者:李义新 5 | 6 | ## 导入(安装) 7 | import 'package:magpie_fly/magpie_fly.dart'; 8 | 9 | ## 概述 10 | * 跟随手指滚动的按钮(适用于悬浮广告按钮) 11 | 12 | 13 | ## 使用 14 | 15 | {{"demo": "lib/demo/draggable_btn/drag_btn_demo.dart", "code": true, "jump": true}} 16 | 17 | 18 | 19 | ## 属性 20 | 21 | | 参数 | 描述 | 22 | | --- | --- | 23 | | child | child | 24 | | initOffset | 初始展示的位置 | 25 | | fullScreenOffset | 可拖动范围相对于全屏的偏移量 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/lib/demo/horizonal_scroll/.page.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "horizonal_scroll", 3 | "title": "尾部弹性ScrollView", 4 | "desc": "一个支持滑动回弹加载更多的组件", 5 | "markdown": "lib/demo/horizonal_scroll/horizonal_scroll.md" 6 | } 7 | -------------------------------------------------------------------------------- /example/lib/demo/horizonal_scroll/HorizontalScrollCustom.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magpie_fly/magpie_fly.dart'; 3 | import 'package:magpie_fly_example/demo/horizonal_scroll/component/card.dart'; 4 | // import 'component/card.dart'; 5 | 6 | class HorizontalScrollCustom extends StatelessWidget { 7 | @override 8 | Widget build(BuildContext context) { 9 | return Container( 10 | height: 150, 11 | child: HorizontalScrollView( 12 | itemCount: 10, 13 | footerWidth: 70, 14 | onFooterLoadingCallBack: () { 15 | print('开始加载'); 16 | }, 17 | footerView: Container( 18 | color: Color.fromRGBO(237, 239, 241, 1), 19 | width: 70, 20 | child: Center( 21 | child: Text('自定义footerview', style: TextStyle(color: Color.fromRGBO(141, 145, 147, 1))), 22 | ), 23 | ), 24 | footerShowStyle: FooterViewShowStyle.dismiss, 25 | itembuilder: (context, index) { 26 | return CardWidget.card(Color.fromRGBO(240, 242, 255, 1)); 27 | }, 28 | ), 29 | ); 30 | } 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /example/lib/demo/horizonal_scroll/HorizontalScrollDefault.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magpie_fly/magpie_fly.dart'; 3 | import 'package:magpie_fly_example/demo/horizonal_scroll/component/card.dart'; 4 | 5 | // import 'component/card.dart'; 6 | 7 | class HorizontalScrollDefault extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return Container( 11 | height: 160, 12 | child: HorizontalScrollView( 13 | itemCount: 10, 14 | footerWidth: 70, 15 | height: 160, 16 | footerView: FooterDefaultView(height: 160), 17 | onFooterLoadingCallBack: () { 18 | print('开始加载回调'); 19 | }, 20 | itembuilder: (context, index) { 21 | return CardWidget.card(Color.fromRGBO(254, 241, 238, 1)); 22 | }, 23 | ), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/lib/demo/horizonal_scroll/HorizontalScrollText.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magpie_fly/magpie_fly.dart'; 3 | import 'package:magpie_fly_example/demo/horizonal_scroll/component/card.dart'; 4 | 5 | // import 'component/card.dart'; 6 | 7 | 8 | class HorizontalScrollText extends StatelessWidget{ 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | height: 150, 13 | child: HorizontalScrollView( 14 | itemCount: 10, 15 | footerWidth: 70, 16 | onFooterLoadingCallBack: (){ 17 | print('开始加载'); 18 | }, 19 | footerView: FooterTextView(height: 150), 20 | footerShowStyle: FooterViewShowStyle.dismiss, 21 | footViewFlowScrollAlways: true, 22 | itembuilder: (context, index){ 23 | return CardWidget.card(Color.fromRGBO(233, 248, 244, 1)); 24 | }, 25 | ), 26 | ); 27 | } 28 | } -------------------------------------------------------------------------------- /example/lib/demo/horizonal_scroll/component/card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CardWidget { 4 | static Widget card(Color cardBGColor) { 5 | var viewCount = '13450'; 6 | return Container( 7 | width: 270, 8 | margin: EdgeInsets.only(right: 10), 9 | decoration: BoxDecoration( 10 | borderRadius: BorderRadius.all(Radius.circular(5)), 11 | color: cardBGColor), 12 | child: Padding( 13 | padding: const EdgeInsets.fromLTRB(0, 15, 15, 0), 14 | child: Column( 15 | crossAxisAlignment: CrossAxisAlignment.start, 16 | children: [ 17 | Row( 18 | mainAxisAlignment: MainAxisAlignment.start, 19 | crossAxisAlignment: CrossAxisAlignment.end, 20 | children: [ 21 | Container( 22 | width: 15, 23 | height: 15, 24 | decoration: BoxDecoration( 25 | borderRadius: BorderRadius.all(Radius.circular(12.5)), 26 | color: Colors.black), 27 | alignment: Alignment.center, 28 | child: Text('#', 29 | style: TextStyle( 30 | fontSize: 13, 31 | color: Colors.white, 32 | fontWeight: FontWeight.bold), 33 | textAlign: TextAlign.center), 34 | ), 35 | Text(' 活动', style: TextStyle(fontSize: 13)), 36 | Expanded(child: Text('')), 37 | Text('$viewCount人参与该话题', 38 | style: TextStyle(fontSize: 12, color: Colors.black54)), 39 | ], 40 | ), 41 | Container( 42 | margin: const EdgeInsets.only(top: 10, left: 15), 43 | child: Text( 44 | '话题活动', 45 | style: TextStyle( 46 | fontSize: 16, 47 | fontWeight: FontWeight.bold, 48 | color: Colors.black, 49 | ), 50 | maxLines: 2, 51 | overflow: TextOverflow.ellipsis, 52 | ), 53 | ), 54 | Container( 55 | margin: const EdgeInsets.only(top: 10, left: 15, bottom: 10), 56 | child: Text( 57 | '11月18日-12月22日,参与限时投票,票数多方瓜分逾10万元奖池。', 58 | style: TextStyle(fontSize: 11, color: Colors.black54), 59 | ), 60 | ) 61 | ], 62 | ), 63 | ), 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /example/lib/demo/horizonal_scroll/horizonal_scroll.md: -------------------------------------------------------------------------------- 1 | 2 | # HorizontalScrollView 3 | 4 | ## 简介 5 | * 名称:HorizontalScrollView 6 | * 概述:带阻尼回弹效果的横向加载更多组件,可用默认样式,也可自定义样式 7 | * 作者:吴丹 8 | 9 | ## 导入(安装) 10 | import 'package:magpie_fly/magpie_fly.dart'; 11 | 12 | ## 概述 13 | * 支持滑动到最右侧之后显示画圆弧加载效果 14 | * 支持滑动到最右侧之后显示text类型的加载中效果 15 | * 支持自定义最右侧加载更多视图效果 16 | * 使用简单,易于上手 17 | * 纯flutter实现,不容易带来兼容问题 18 | 19 | ## 使用 20 | ### 使用默认底部样式 21 | {{"demo": "lib/demo/horizonal_scroll/HorizontalScrollDefault.dart", "code": true}} 22 | 23 | ### 使用带文字底部样式 24 | {{"demo": "lib/demo/horizonal_scroll/HorizontalScrollText.dart", "code": true}} 25 | 26 | ### 使用自定义底部样式 27 | {{"demo": "lib/demo/horizonal_scroll/HorizontalScrollCustom.dart", "code": true}} 28 | 29 | ## 参数配置 30 | 31 | | 参数 | 描述 | 32 | | --- | --- | 33 | | itemCount | scrollview上的item个数 | 34 | | footerWidth | 底部footer的宽度 | 35 | | onFooterLoadingCallBack | 加载更多回调 | 36 | | footerView | 底部footerview,已实现FooterDefaultView和FooterTextView | 37 | | footerShowStyle | 有三个值: dismiss:回弹之后footer全部不显示;showHalf:回弹之后footer显示一半;showWhole:回弹之后footer全部显示 | 38 | | footViewFlowScrollAlways | 滑动到最后,footerview是否跟着scrollview滑动 | 39 | | itembuilder | scollview上的item实现 | -------------------------------------------------------------------------------- /example/lib/demo/pinned_sliver/.page.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pinned_sliver", 3 | "title": "安全区吸顶组件", 4 | "desc": "安全区吸顶组件", 5 | "markdown": "lib/demo/pinned_sliver/pinned_sliver.md" 6 | } 7 | -------------------------------------------------------------------------------- /example/lib/demo/pinned_sliver/component/simple_rect.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | 5 | class CustomRect extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return CustomPaint( 9 | painter: Sky(), 10 | child: Center( 11 | child: Text( 12 | 'Once upon a time...', 13 | style: const TextStyle( 14 | fontSize: 40, 15 | fontWeight: FontWeight.w900, 16 | color: Colors.white, 17 | ), 18 | ), 19 | ), 20 | ); 21 | } 22 | } 23 | 24 | class Sky extends CustomPainter { 25 | @override 26 | void paint(Canvas canvas, Size size) { 27 | var rect = Offset.zero & size; 28 | var gradient = RadialGradient( 29 | center: const Alignment(0.7,-0.6), 30 | radius: 0.2, 31 | colors: [const Color(0xFFFFFF00), const Color(0xFF0099FF)], 32 | stops: [0.4, 1], 33 | ); 34 | canvas.drawRect( 35 | rect, 36 | Paint()..shader = gradient.createShader(rect), 37 | ); 38 | } 39 | 40 | @override 41 | SemanticsBuilderCallback get semanticsBuilder { 42 | return (Size size) { 43 | // Annotate a rectangle containing the picture of the sun 44 | // with the label "Sun". When text to speech feature is enabled on the 45 | // device, a user will be able to locate the sun on this picture by 46 | // touch. 47 | var rect = Offset.zero & size; 48 | var width = size.shortestSide * 0.4; 49 | rect = const Alignment(0.8, -0.9).inscribe(Size(width, width), rect); 50 | return [ 51 | CustomPainterSemantics( 52 | rect: rect, 53 | properties: SemanticsProperties( 54 | label: 'Sun', 55 | textDirection: TextDirection.ltr, 56 | ), 57 | ), 58 | ]; 59 | }; 60 | } 61 | 62 | // Since this Sky painter has no fields, it always paints 63 | // the same thing and semantics information is the same. 64 | // Therefore we return false here. If we had fields (set 65 | // from the constructor) then we would return true if any 66 | // of them differed from the same fields on the oldDelegate. 67 | @override 68 | bool shouldRepaint(Sky oldDelegate) => false; 69 | 70 | @override 71 | bool shouldRebuildSemantics(Sky oldDelegate) => false; 72 | } 73 | -------------------------------------------------------------------------------- /example/lib/demo/pinned_sliver/pinned_sliver.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 简介 4 | * 名称:NestedScrollViewHeaderSliversBuilder 5 | * 概述:安全区吸顶组件 6 | * 作者:吴朝彬 7 | 8 | ## 导入(安装) 9 | 10 | > import 'package:magpie_fly/magpie_fly.dart'; 11 | 12 | ## 概述 13 | 混合吸顶处理可以结合`SliverAppbar`,吸顶效果根据头部的交互差异有不同效果。NestedScrollViewHeaderSliversBuilder提供了一种简单的吸顶效果。 14 | 15 | ## 使用 16 | pinnedBuilder可以配合NestedScrollView的headerSliverBuilder使用。 17 | 18 | 效果: 19 | {{"demo": "lib/demo/pinned_sliver/pinned_sliver_demo.dart", "jump":true, "appbar": false}} 20 | 21 | 22 | ## 参数配置 23 | 24 | | 参数 | 描述 | 25 | | --- | --- | 26 | | placeHolderSize | 状态栏渐变的最大高度 | 27 | | color | 状态栏吸顶颜色,滑动过程中会绑定透明度渐变 | 28 | | height | 整个header的固定高度 | 29 | | headerBuilder | 作为header展示的内容 | 30 | | builder |返回header之外的其他headerSliverBuilder | 31 | 32 | -------------------------------------------------------------------------------- /example/lib/demo/pinned_sliver/pinned_sliver_demo.dart: -------------------------------------------------------------------------------- 1 | // import '../../comon_widget/fullscreen_code.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:magpie_fly/magpie_fly.dart'; 6 | import 'package:magpie_fly_example/comon_widget/fullscreen_code.dart'; 7 | 8 | import 'component/simple_rect.dart'; 9 | 10 | class PinnedSliverDemo extends StatefulWidget { 11 | @override 12 | State createState() { 13 | return _State(); 14 | } 15 | } 16 | 17 | class _State extends State { 18 | final _tabs = ['Hot', 'Latest', 'Friends']; 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | var pinnedColor = Colors.blue[800]; 23 | return Scaffold( 24 | body: DefaultTabController( 25 | length: _tabs.length, 26 | child: NestedScrollView( 27 | headerSliverBuilder: pinnedBuilder( 28 | placeHolderSize: 100, 29 | color: pinnedColor, 30 | height: 200, 31 | headerBuilder: (context) { 32 | return Container( 33 | height: 200, 34 | alignment: Alignment.bottomCenter, 35 | width: MediaQuery.of(context).size.width, 36 | child: CustomRect(), 37 | ); 38 | }, 39 | builder: (BuildContext context, bool innerBoxIsScrolled) { 40 | return [ 41 | PinnedAppBar( 42 | color: pinnedColor, 43 | child: TabBar( 44 | labelColor: Colors.white, 45 | indicatorColor: Colors.white, 46 | indicatorWeight: 4, 47 | tabs: _tabs 48 | .map((String name) => Tab(text: name)) 49 | .toList()), 50 | ) 51 | ]; 52 | }), 53 | body: TabBarView( 54 | children: _tabs 55 | .map((name) => ListView.separated( 56 | itemBuilder: (_, j) => Text("$name message #$j"), 57 | separatorBuilder: (_, index) => Divider(), 58 | itemCount: 30)) 59 | .toList(), 60 | ), 61 | ), 62 | ), 63 | floatingActionButton: FloatingActionButton( 64 | onPressed: () { 65 | rootBundle 66 | .loadString('lib/demo/pinned_sliver/pinned_sliver_demo.dart') 67 | .then((value) { 68 | Navigator.push( 69 | context, 70 | MaterialPageRoute( 71 | builder: (context) => FullScreenCode(code: value))); 72 | }); 73 | }, 74 | tooltip: "source", 75 | child: Icon(Icons.code), 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/lib/demo/popup_window/.page.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "popup_window", 3 | "title": "popup_window", 4 | "desc": "", 5 | "markdown": "lib/demo/popup_window/popup_window.md" 6 | } 7 | -------------------------------------------------------------------------------- /example/lib/demo/popup_window/popup_window.md: -------------------------------------------------------------------------------- 1 | 2 | [toc] 3 | 4 | ## 简介 5 | * 名称:PopupWindow 6 | * 概述:flutter 实现类似 android popupwindow 悬浮窗效果 7 | * 作者:张凯晓 8 | 9 | ## 导入(安装) 10 | import 'package:magpie_fly/magpie_fly.dart'; 11 | 12 | ## 概述 13 | flutter 实现类似 android popupwindow 悬浮窗效果 14 | 15 | ## 使用 16 | 17 | 效果: 18 | {{"demo": "lib/demo/popup_window/popup_window_demo.dart","jump":true}} 19 | 20 | 21 | ## 参数配置 22 | 23 | | 参数 | 描述 | 24 | | --- | --- | 25 | | context | BuildContext | 26 | | popKey | 宿主Widget key | 27 | | popDirection | 悬浮窗显示位置,默认下方 | 28 | | popWidget | 悬浮窗内容Widget | 29 | | offset | 悬浮窗偏移量 | -------------------------------------------------------------------------------- /example/lib/demo/popup_window/popup_window_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fluttertoast/fluttertoast.dart'; 3 | import 'package:magpie_fly/magpie_fly.dart'; 4 | 5 | class PopupWindowTest extends StatelessWidget { 6 | GlobalKey gk1 = new GlobalKey(); 7 | GlobalKey gk2 = new GlobalKey(); 8 | GlobalKey gk3 = new GlobalKey(); 9 | 10 | List cityData = [ 11 | "北京", 12 | "上海", 13 | "广州", 14 | "深圳", 15 | "天津", 16 | "成都", 17 | "苏州", 18 | "重庆", 19 | "武汉", 20 | "长沙", 21 | "青岛" 22 | ]; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | var content = Column( 27 | children: [ 28 | Container( 29 | child: SizedBox( 30 | key: gk1, 31 | height: 40, 32 | child: FlatButton( 33 | onPressed: () { 34 | PopupWindow.showPopWindow( 35 | context, gk1, PopDirection.left, FlutterLogo(), 5); 36 | }, 37 | child: Text( 38 | "左侧图片", 39 | style: TextStyle(color: Colors.black54), 40 | )), 41 | ), 42 | width: 200, 43 | height: 50, 44 | decoration: BoxDecoration(color: Colors.deepOrange), 45 | margin: EdgeInsets.fromLTRB(0, 20, 0, 0), 46 | ), 47 | Container( 48 | child: SizedBox( 49 | key: gk2, 50 | height: 40, 51 | child: FlatButton( 52 | onPressed: () { 53 | PopupWindow.showPopWindow( 54 | context, 55 | gk2, 56 | PopDirection.bottom, 57 | Container( 58 | width: 100.0, 59 | height: 200.0, 60 | padding: EdgeInsets.all(2), 61 | decoration: BoxDecoration( 62 | color: Colors.white, 63 | borderRadius: BorderRadius.all(Radius.circular(1))), 64 | child: Scaffold( 65 | body: Container( 66 | child: ListView.builder( 67 | itemCount: cityData.length, 68 | padding: EdgeInsets.symmetric(vertical: 8.0), 69 | itemBuilder: 70 | (BuildContext context, int position) { 71 | String str = cityData[position]; 72 | return FlatButton( 73 | child: Text(str, 74 | style: TextStyle( 75 | color: Colors.black54, fontSize: 20)), 76 | onPressed: () { 77 | Navigator.pop(context); 78 | 79 | Fluttertoast.showToast( 80 | msg: str + "onClick", 81 | toastLength: Toast.LENGTH_SHORT, 82 | gravity: ToastGravity.BOTTOM, 83 | timeInSecForIos: 1, 84 | backgroundColor: Colors.white70, 85 | textColor: Colors.black54, 86 | fontSize: 12.0); 87 | }, 88 | ); 89 | }, 90 | ), 91 | ), 92 | ), 93 | ), 94 | 0); 95 | }, 96 | child: Text( 97 | "下方城市列表", 98 | style: TextStyle(color: Colors.black54), 99 | )), 100 | ), 101 | width: 200, 102 | height: 50, 103 | decoration: BoxDecoration(color: Colors.deepOrange), 104 | margin: EdgeInsets.fromLTRB(0, 20, 0, 0), 105 | ), 106 | ], 107 | ); 108 | return Column( 109 | children: [content], 110 | ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /example/lib/demo/search_input/.page.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "search_input", 3 | "title": "搜索组件", 4 | "desc": "", 5 | "markdown": "lib/demo/search_input/search_input.md" 6 | } 7 | -------------------------------------------------------------------------------- /example/lib/demo/search_input/search_input.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | * 名称:SearchInput 3 | * 概述:支持阴影效果的输入框 4 | * 作者:李义新 5 | 6 | ## 导入(安装) 7 | import 'package:magpie_fly/magpie_fly.dart'; 8 | 9 | ## 概述 10 | * 本组件可以设置输入框描边阴影效果,也可以纯色描边,解决TextFiled不支持border渐变描边。 11 | 12 | 13 | ## 使用 14 | 15 | * 默认带阴影效果(isBorderShadow=false可以关闭阴影) 16 | 17 | {{"demo": "lib/demo/search_input/search_input_demo.dart"}} 18 | 19 | 20 | ## 属性 21 | 22 | | 参数 | 描述 | 23 | | --- | --- | 24 | | isBorderShadow | 是否描边带阴影 | 25 | | prefixIconColor | 放大镜icon颜色 | 26 | | borderColor | 描边颜色 | 27 | | fillColor | 填充颜色 | 28 | | hintText | 提示文案 | 29 | | hintTextColor | 提示文字颜色 | 30 | | blurRadius | 描边宽度 | 31 | | circleRadius | 圆角弧度 | 32 | -------------------------------------------------------------------------------- /example/lib/demo/search_input/search_input_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:magpie_fly/magpie_fly.dart'; 3 | 4 | class SearchInputDemo extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | return Column( 8 | children: [ 9 | Container( 10 | margin: EdgeInsets.all(15), 11 | child: SearchInput( 12 | hintTextColor: Colors.grey, 13 | isBorderShadow: true, 14 | blurRadius: 5.0, 15 | circleRadius: 5.0, 16 | prefixIconColor: Colors.grey, 17 | hintText: "带阴影的TextFiled", 18 | borderColor: Colors.grey, 19 | onChanged: (text){ 20 | print(text); 21 | }, 22 | )), 23 | Container( 24 | margin: EdgeInsets.all(15), 25 | child: SearchInput( 26 | circleRadius: 5.0, 27 | isBorderShadow: false, 28 | hintText: "不带阴影的TextFiled", 29 | borderColor: Colors.blue, 30 | )) 31 | ], 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /example/lib/demo/test_widget/.page.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test_widget", 3 | "title": "测试组件", 4 | "desc": "这是一个测试组件", 5 | "markdown": "lib/demo/test_widget/test_widget.md" 6 | } 7 | -------------------------------------------------------------------------------- /example/lib/demo/test_widget/test_widget.md: -------------------------------------------------------------------------------- 1 | 2 | ## 简介 3 | * 名称:你的组件名称 4 | * 概述:你的组件概述 5 | * 作者:你的邮箱 6 | * 常用指数:♥♥♥ 7 | 8 | ## 导入(安装) 9 | import 'package:magpie_ui/magpie_ui.dart'; 10 | 11 | ## 概述 12 | XXXXXXXXXXX 13 | 14 | ## 使用 15 | 16 | 17 | 效果: 18 | {{"demo": "lib/demo/test_widget/test_widget.dart"}} 19 | 20 | ## 参数配置 21 | 22 | | 参数 | 描述 | 23 | | --- | --- | 24 | 25 | -------------------------------------------------------------------------------- /example/lib/demo/test_widget/test_widget_demo.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class test_widget extends StatefulWidget { 4 | @override 5 | State createState() => _test_widgetState(); 6 | } 7 | 8 | class _test_widgetState extends State { 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | child: Text("this is flutter go init demo"), 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/lib/demo/video/.page.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "video", 3 | "title": "视频滚动检测播放控件", 4 | "desc": "", 5 | "markdown": "lib/demo/video/video.md" 6 | } 7 | -------------------------------------------------------------------------------- /example/lib/demo/video/component/text_component.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class TextComponent extends StatelessWidget { 5 | final String data; 6 | 7 | TextComponent(this.data); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Container( 12 | alignment: Alignment.center, 13 | height: 100, 14 | child: Text(data), 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /example/lib/demo/video/component/video_component.dart: -------------------------------------------------------------------------------- 1 | import 'video_view.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:magpie_fly/magpie_fly.dart'; 5 | 6 | class VideoComponent extends StatelessWidget { 7 | final int index; 8 | final dynamic data; 9 | 10 | VideoComponent(this.index, this.data); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return MetaConsumer( 15 | index: index, 16 | data: data, 17 | builder: (BuildContext context, VideoPlayModel model, Widget child) { 18 | var play = model.playIndex == index; 19 | return Container( 20 | width: MediaQuery.of(context).size.width, 21 | alignment: Alignment.center, 22 | padding: EdgeInsets.zero, 23 | child: VideoView(data, play), 24 | color: play ? Colors.redAccent : Colors.grey[100] ?? Colors.grey, 25 | ); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/lib/demo/video/component/video_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:video_player/video_player.dart'; 4 | 5 | class VideoView extends StatefulWidget { 6 | final url; 7 | final play; 8 | 9 | VideoView(this.url, this.play); 10 | 11 | @override 12 | State createState() { 13 | return _VideoState(); 14 | } 15 | } 16 | 17 | class _VideoState extends State { 18 | VideoPlayerController _controller; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | if (_controller != null) { 24 | _controller.dispose(); 25 | } 26 | _controller = VideoPlayerController.network(widget.url) 27 | ..setVolume(0) 28 | ..setLooping(true) 29 | ..initialize().then((_) { 30 | if (!mounted) { 31 | return; 32 | } 33 | setState(() {}); 34 | }); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | widget.play ? _play() : _pause(); 40 | var initialized = _controller.value.initialized; 41 | var ratio = initialized ? _controller.value.aspectRatio : 16 / 9; 42 | 43 | var children = [ 44 | AspectRatio(aspectRatio: ratio, child: VideoPlayer(_controller)), 45 | ]; 46 | if (!widget.play) { 47 | children.add(AspectRatio( 48 | aspectRatio: ratio, 49 | child: Container( 50 | color: initialized ? Colors.transparent : Colors.grey[400], 51 | child: Icon( 52 | Icons.play_circle_outline, 53 | color: Colors.white, 54 | size: 60, 55 | ), 56 | ), 57 | )); 58 | } else if (!initialized) { 59 | children.add(AspectRatio( 60 | aspectRatio: ratio, 61 | child: Center(child: const CircularProgressIndicator()), 62 | )); 63 | } else { 64 | children.add(Align( 65 | alignment: Alignment.bottomCenter, 66 | child: SizedBox( 67 | height: 8, 68 | child: VideoProgressIndicator( 69 | _controller, 70 | allowScrubbing: true, 71 | colors: VideoProgressColors( 72 | playedColor: Colors.green[600], 73 | bufferedColor: Colors.green[100], 74 | backgroundColor: Colors.grey, 75 | ), 76 | ), 77 | ), 78 | )); 79 | } 80 | return Stack( 81 | children: children, 82 | alignment: Alignment.bottomCenter, 83 | ); 84 | } 85 | 86 | void _play() { 87 | if (!_controller.value.initialized) { 88 | return; 89 | } 90 | if (!_controller.value.isPlaying) { 91 | _controller.play(); 92 | } 93 | } 94 | 95 | void _pause() { 96 | if (!_controller.value.initialized) { 97 | return; 98 | } 99 | if (_controller.value.isPlaying) { 100 | _controller.pause(); 101 | } 102 | } 103 | 104 | @override 105 | void dispose() { 106 | super.dispose(); 107 | _controller.dispose(); 108 | debugPrint('dispose ${widget.url}'); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /example/lib/demo/video/video.md: -------------------------------------------------------------------------------- 1 | ## 简介 2 | * 名称:ScrollDetectListener 3 | * 概述:视频滚动检测播放控件 4 | * 作者:吴朝彬 5 | 6 | ## 导入(安装) 7 | 8 | > import 'package:magpie_fly/magpie_fly.dart'; 9 | 10 | ## 概述 11 | * 视频列表Feed流中对视频的播放控制 12 | * 插件本身没有内嵌播放器,Demo使用了video_player插件,接入时可以选择性替换为自身的视频播放器。 13 | 14 | ## 使用 15 | 16 | * 使用ScrollDetectListener包裹ListView,ListView为业务相关的视频流帖子。 17 | * 视频帖子使用MetaConsumer包裹; 18 | 19 | 效果: 20 | {{"demo": "lib/demo/video/video_feed_demo.dart", "jump":true}} 21 | 22 | ## 参数配置 23 | 24 | | 参数 | 描述 | 25 | | --- | --- | 26 | | detect | 初次展示列表时是否主动触发播放检测 | 27 | | offset | 检测起始位置,如果非全屏,写列表上方的高度| 28 | |percentIn|帖子被认定处于屏幕内的最小百分比,低于改值时帖子只有部分在屏幕内,不会触发播放| 29 | | onVisible | 检测回调,参数为检测到的屏幕中视频帖子数组| 30 | | child | 展示列表| 31 | 32 | -------------------------------------------------------------------------------- /example/lib/demo/video/video_feed_demo.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:magpie_fly/magpie_fly.dart'; 6 | import 'component/video_component.dart'; 7 | import 'component/text_component.dart'; 8 | 9 | class VideoFeedDemo extends StatefulWidget { 10 | @override 11 | State createState() { 12 | return _State(); 13 | } 14 | } 15 | 16 | class _State extends State { 17 | List dataList = [ 18 | 'http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4', 19 | 'http://vfx.mtime.cn/Video/2019/03/21/mp4/190321153853126488.mp4', 20 | 'http://vfx.mtime.cn/Video/2019/03/19/mp4/190319222227698228.mp4', 21 | 'http://vfx.mtime.cn/Video/2019/03/19/mp4/190319212559089721.mp4', 22 | 'http://vfx.mtime.cn/Video/2019/03/18/mp4/190318231014076505.mp4', 23 | 'http://vfx.mtime.cn/Video/2019/03/18/mp4/190318214226685784.mp4', 24 | 'http://vfx.mtime.cn/Video/2019/03/19/mp4/190319104618910544.mp4', 25 | 'http://vfx.mtime.cn/Video/2019/03/19/mp4/190319125415785691.mp4', 26 | 'http://vfx.mtime.cn/Video/2019/03/17/mp4/190317150237409904.mp4', 27 | 'http://vfx.mtime.cn/Video/2019/03/14/mp4/190314223540373995.mp4', 28 | 'http://vfx.mtime.cn/Video/2019/03/14/mp4/190314102306987969.mp4', 29 | 'http://vfx.mtime.cn/Video/2019/03/13/mp4/190313094901111138.mp4', 30 | 'http://vfx.mtime.cn/Video/2019/03/12/mp4/190312143927981075.mp4', 31 | 'http://vfx.mtime.cn/Video/2019/03/12/mp4/190312083533415853.mp4', 32 | 'http://vfx.mtime.cn/Video/2019/03/09/mp4/190309153658147087.mp4', 33 | '庆祝澳门回归祖国20周年特别报道', 34 | '这次升旗,意义非凡!', 35 | '情系濠江 习近平和澳门的故事', 36 | '坚定维护党中央权威和集中统一领导', 37 | '倾听中国声音 共建美好世界 庆祝澳门回归20周年', 38 | '我国钢铁消费增长何以超预期', 39 | '奋力完成好明年经济工作重点任务', 40 | '消费品质量安全形势总体平稳 以“民生之暖”保稳定促发展', 41 | '多项经济指标超预期 外媒:中国经济企稳信号增强', 42 | '教育部解读两个《意见》——切实减轻中小学教师负担', 43 | '2020年全国硕士研究生招生考试21日开考 341万人报名', 44 | '教育部公布“双高计划”学校名单 197所高职院校入选', 45 | '山东舰交接入列仪式的举办地点怎么安排?海军回应', 46 | '四川资中5.2级地震已致9人受伤 其中重伤4人', 47 | '最高人民法院:极严重的性侵儿童罪犯判处死刑', 48 | '香港大学新学期将于1月如期开学,2020内地招生政策不变', 49 | '台“大选”电视辩论会将登场 韩国瑜蔡英文宋楚瑜正面交锋', 50 | '台媒:2020年台湾选举“不分区立委”政党票号次出炉', 51 | '美国朝鲜问题特使本周将访华 外交部:欢迎', 52 | ]..shuffle(Random.secure()); 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return Scaffold( 57 | body: ScrollDetectListener( 58 | detect: true, 59 | percentIn: 1 /*(0-1]*/, 60 | offset: 60 /*Appbar height*/, 61 | onVisible: (metas, model) { 62 | var videoItem = model.videoData; 63 | if (metas != null && metas.length > 0) { 64 | var meta = metas[0] as VideoMeta; 65 | var targetData = dataList[meta.index]; 66 | if (videoItem == null || 67 | (videoItem != null && videoItem != targetData)) { 68 | model.updateVideo(meta.index, targetData); 69 | } 70 | } else if (videoItem != null) { 71 | model.updateVideo(-1, null); 72 | } 73 | }, 74 | child: ListView.separated( 75 | padding: EdgeInsets.zero, 76 | itemBuilder: (_, i) => dataList[i].startsWith('http') 77 | ? VideoComponent(i, dataList[i]) 78 | : TextComponent(dataList[i]), 79 | separatorBuilder: (_, i) => Divider( 80 | height: 1, 81 | ), 82 | itemCount: dataList.length, 83 | ), 84 | ), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /example/lib/list.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:android_intent/android_intent.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'comon_widget/markdown_page.dart'; 6 | import 'demo/ListDatas.dart'; 7 | 8 | class ListPage extends StatelessWidget { 9 | void _cellOnTap(BuildContext context, Map map) { 10 | var markdown = map['markdown']; 11 | var title = map['title']; 12 | 13 | Navigator.of(context).push(new MaterialPageRoute(builder: (context) { 14 | return Scaffold( 15 | appBar: AppBar( 16 | title: Text(title), 17 | actions: [ 18 | IconButton( 19 | tooltip: "ajk", 20 | icon: Icon(Icons.info), 21 | onPressed: _intentToAjk, 22 | ) 23 | ], 24 | ), 25 | body: MarkdownPage(filePath: markdown), 26 | ); 27 | })); 28 | } 29 | 30 | _intentToAjk() async { 31 | if (Platform.isAndroid) { 32 | AndroidIntent intent = AndroidIntent( 33 | action: 'action_view', 34 | data: 35 | 'openanjuke://jump/app/main_tab_page?main_tab=youliao&sub_tab=focus', 36 | ); 37 | await intent.launch(); 38 | }else{ 39 | //todo iOS 40 | } 41 | } 42 | 43 | @override 44 | Widget build(BuildContext context) { 45 | return Scaffold( 46 | body: ListView.separated( 47 | padding: EdgeInsets.zero, 48 | itemCount: DemolistDatas.length, 49 | itemBuilder: (context, index) { 50 | Map map = DemolistDatas[index]; 51 | String name = map['name'] ?? ''; 52 | return ListTile( 53 | trailing: Icon(Icons.chevron_right, color: Color(0xffCCCCCC)), 54 | title: Text(map['title'], style: TextStyle(color: Color(0xff333333), fontSize: 16)), 55 | subtitle: Container( 56 | margin: EdgeInsets.only(top: 8), 57 | child: Text(name, style: TextStyle(color: Color(0xff999999), fontSize: 12)), 58 | ), 59 | onTap: () { 60 | _cellOnTap(context, map); 61 | }, 62 | ); 63 | }, 64 | separatorBuilder: (context, index) => Divider(height: 1), 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'app.dart'; 4 | 5 | void main() { 6 | SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( 7 | statusBarColor: Colors.transparent, 8 | statusBarBrightness: Brightness.dark, 9 | statusBarIconBrightness: Brightness.dark, 10 | )); 11 | runApp(MyApp()); 12 | } 13 | 14 | class MyApp extends StatelessWidget { 15 | @override 16 | Widget build(BuildContext context) { 17 | return MaterialApp( 18 | title: 'UI Widget', 19 | theme: ThemeData( 20 | // primarySwatch: Colors.deepOrange 21 | primaryColor: Color(0xff0B69F0) 22 | ), 23 | home: AppPage(), 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/lib/markdown_doc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_markdown/flutter_markdown.dart'; 3 | import 'package:flutter/services.dart'; 4 | 5 | class MarkDownDoc extends StatelessWidget { 6 | final String mdPath; 7 | final String title; 8 | 9 | const MarkDownDoc({Key key, @required this.mdPath, this.title}) 10 | : super(key: key); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return FutureBuilder( 15 | future: rootBundle.loadString(mdPath), 16 | builder: (context, data) { 17 | if (data.hasData) { 18 | return Scaffold( 19 | body: Markdown( 20 | data: data.data, 21 | onTapLink: (href) { 22 | _handleHref(context, href); 23 | }, 24 | imageBuilder: (Uri uri){ 25 | print(uri.path); 26 | try { 27 | return Image.asset('doc/${uri.path}'); 28 | } catch (e) { 29 | return Text(''); 30 | } 31 | }, 32 | ), 33 | appBar: title != null 34 | ? AppBar( 35 | title: Text(title), 36 | ) 37 | : null, 38 | ); 39 | } else { 40 | return Scaffold( 41 | body: Center( 42 | child: Text("no md"), 43 | ), 44 | ); 45 | } 46 | }, 47 | ); 48 | } 49 | 50 | /// 处理markdown点击事件 51 | _handleHref(BuildContext context, String href) { 52 | debugPrint(" ${href}"); 53 | //其他跳转,可根据href区分title 54 | var title = "markdown"; 55 | if(href.contains("how_to_add_compone")){ 56 | title = "how to add compone"; 57 | }else if(href.contains("magpie_ui")){ 58 | title = "使用文档"; 59 | } 60 | Navigator.push( 61 | context, 62 | MaterialPageRoute( 63 | builder: (context) => MarkDownDoc( 64 | mdPath: href, 65 | title: title, 66 | ))); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/lib/news.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 5 | 6 | class NewsPage extends StatefulWidget { 7 | String title; 8 | NewsPage(this.title); 9 | @override 10 | State createState() => NewsPageState(); 11 | } 12 | 13 | class NewsPageState extends State 14 | with SingleTickerProviderStateMixin { 15 | final _url = 'https://mp.weixin.qq.com/mp/homepage?__biz=MzI1NDc5MzIxMw==&hid=4&sn=4f1456867f1cabda91b4a7fecd147fa4&scene=18'; 16 | final flutterWebviewPlugin = new FlutterWebviewPlugin(); 17 | bool _showBackBtn = false; 18 | 19 | @override 20 | void initState() { 21 | flutterWebviewPlugin.onStateChanged.listen((viewState){ 22 | if (viewState.type == WebViewState.finishLoad) { 23 | _changeShowBackBtn(); //判断是否显示返回按钮 24 | 25 | } 26 | }); 27 | 28 | flutterWebviewPlugin.onBack.listen((_) { 29 | goBack(); 30 | }); 31 | super.initState(); 32 | 33 | } 34 | 35 | void goBack() { 36 | flutterWebviewPlugin.goBack().then((onValue) { 37 | _changeShowBackBtn(); 38 | }); 39 | } 40 | 41 | //判断是否显示返回按钮 42 | void _changeShowBackBtn() { 43 | flutterWebviewPlugin.canGoBack().then((canGo) { 44 | setState(() { 45 | _showBackBtn = canGo; 46 | print(_showBackBtn); 47 | }); 48 | }); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | return CupertinoPageScaffold( 54 | child: WebviewScaffold( 55 | url: _url, 56 | withZoom: false, 57 | withLocalStorage: true, 58 | withJavascript: true, 59 | hidden: true, 60 | withOverviewMode: true, 61 | useWideViewPort: true, 62 | appBar: AppBar( 63 | title: Text(widget.title ?? '58公众号'), 64 | leading: _showBackBtn 65 | ? new IconButton( 66 | icon: Icon(Icons.arrow_back), 67 | onPressed: goBack, 68 | ) 69 | : null, 70 | ), 71 | initialChild: Container( 72 | child: const Center( 73 | child: CircularProgressIndicator(), 74 | ), 75 | ), 76 | ), 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /example/lib/util/config.dart: -------------------------------------------------------------------------------- 1 | 2 | import '../demo/banner_widget/banner_widget_demo.dart'; 3 | import '../demo/draggable_btn/drag_btn_demo.dart'; 4 | import '../demo/search_input/search_input_demo.dart'; 5 | import '../demo/video/video_feed_demo.dart'; 6 | import '../demo/pinned_sliver/pinned_sliver_demo.dart'; 7 | import '../demo/popup_window/popup_window_demo.dart'; 8 | import 'package:flutter/material.dart'; 9 | import '../demo/horizonal_scroll/HorizontalScrollCustom.dart'; 10 | import '../demo/horizonal_scroll/HorizontalScrollDefault.dart'; 11 | import '../demo/horizonal_scroll/HorizontalScrollText.dart'; 12 | 13 | //rm中对应的demo 14 | typedef fuc = Widget Function(); 15 | Map markdownPathToWidget = { 16 | 'lib/demo/horizonal_scroll/HorizontalScrollDefault.dart': () => HorizontalScrollDefault(), 17 | 'lib/demo/horizonal_scroll/HorizontalScrollCustom.dart': () => HorizontalScrollCustom(), 18 | 'lib/demo/horizonal_scroll/HorizontalScrollText.dart': () => HorizontalScrollText(), 19 | 'lib/demo/pinned_sliver/pinned_sliver_demo.dart': () => PinnedSliverDemo(), 20 | 'lib/demo/popup_window/popup_window_demo.dart': () => PopupWindowTest(), 21 | 'lib/demo/search_input/search_input_demo.dart': () => SearchInputDemo(), 22 | 'lib/demo/video/video_feed_demo.dart': () => VideoFeedDemo(), 23 | 'lib/demo/draggable_btn/drag_btn_demo.dart': () => DragBtnDemo(), 24 | 'lib/demo/banner_widget/banner_widget_demo.dart': () => BannerWidgetDemo(), 25 | }; 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/lib/util/parse_markdown.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter/services.dart'; 3 | 4 | //解析markdown工具 5 | Future parseMarkdown(String filepath) async { 6 | try { 7 | String content = await rootBundle.loadString(filepath); 8 | if(content.length == 0) return []; 9 | //根据换行符号截取字符串为数组 10 | List list = content.split(RegExp(r'\r?\n')); 11 | 12 | List widgetlist = []; 13 | String markdown = ''; 14 | for (var i = 0; i < list.length; i++) { 15 | String s = list[i]; 16 | s = s.trim(); 17 | if (s.startsWith('{{"demo":') && s.endsWith('}}')) { 18 | if (markdown.length > 0) widgetlist.add(markdown); 19 | markdown = ''; 20 | String ss = s.substring(1, s.length - 1); 21 | Map map = json.decode(ss); 22 | var needAppBar = map['appbar'] ?? true; 23 | widgetlist.add(Model(demo: map['demo'], 24 | code: map['code'] ?? true, 25 | jump: map['jump'] ?? false, 26 | needAppbar: needAppBar)); 27 | } else { 28 | markdown = markdown + '\n' + s; 29 | } 30 | } 31 | if (markdown.length > 0) widgetlist.add(markdown); 32 | return widgetlist; 33 | } catch (e) { 34 | print(e.toString()); 35 | return null; 36 | } 37 | } 38 | 39 | class Model { 40 | String demo; //demo文件地址 41 | bool code = true; //是否显示代码,默认显示 42 | bool jump = false; //是否跳界面显示效果 43 | bool needAppbar = true; 44 | Model({this.demo, this.code = true, this.jump = false, this.needAppbar = true}); 45 | 46 | bool get isJump => jump ?? false; 47 | 48 | String get className { 49 | String last = demo.split("/").last; 50 | if (last.endsWith('.dart')) { 51 | return last.substring(0, last.length - 5); 52 | } 53 | return last; 54 | } 55 | 56 | @override 57 | String toString() { 58 | return '{\"demo\": \"$demo\", \"code\": $code,\"jump\": $jump}'; 59 | } 60 | } 61 | 62 | const String demoRegexp = '/^\"demo\": \"(.*)\"/'; 63 | bool isMatchDemo(String s) { 64 | RegExp demo = RegExp(demoRegexp); 65 | return demo.hasMatch(s); 66 | } 67 | -------------------------------------------------------------------------------- /example/lib/util/syntax_highlighter.dart: -------------------------------------------------------------------------------- 1 | /// @Author: 一凨 2 | /// @Date: 2019-01-14 11:42:39 3 | /// @Last Modified by: 一凨 4 | /// @Last Modified time: 2019-01-14 11:42:39 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:string_scanner/string_scanner.dart'; 8 | 9 | class SyntaxHighlighterStyle { 10 | SyntaxHighlighterStyle( 11 | {this.baseStyle, 12 | this.numberStyle, 13 | this.commentStyle, 14 | this.keywordStyle, 15 | this.stringStyle, 16 | this.punctuationStyle, 17 | this.classStyle, 18 | this.constantStyle}); 19 | 20 | static SyntaxHighlighterStyle lightThemeStyle() { 21 | return SyntaxHighlighterStyle( 22 | baseStyle: const TextStyle(color: Color(0xFF000000)), 23 | numberStyle: const TextStyle(color: Color(0xFF1565C0)), 24 | commentStyle: const TextStyle(color: Color(0xFF9E9E9E)), 25 | keywordStyle: const TextStyle(color: Color(0xFF9C27B0)), 26 | stringStyle: const TextStyle(color: Color(0xFF43A047)), 27 | punctuationStyle: const TextStyle(color: Color(0xFF000000)), 28 | classStyle: const TextStyle(color: Color(0xFF512DA8)), 29 | constantStyle: const TextStyle(color: Color(0xFF795548))); 30 | } 31 | 32 | static SyntaxHighlighterStyle darkThemeStyle() { 33 | return SyntaxHighlighterStyle( 34 | baseStyle: const TextStyle(color: Color(0xFFFFFFFF)), 35 | numberStyle: const TextStyle(color: Color(0xFF1565C0)), 36 | commentStyle: const TextStyle(color: Color(0xFF9E9E9E)), 37 | keywordStyle: const TextStyle(color: Color(0xFF80CBC4)), 38 | stringStyle: const TextStyle(color: Color(0xFF009688)), 39 | punctuationStyle: const TextStyle(color: Color(0xFFFFFFFF)), 40 | classStyle: const TextStyle(color: Color(0xFF009688)), 41 | constantStyle: const TextStyle(color: Color(0xFF795548))); 42 | } 43 | 44 | static SyntaxHighlighterStyle magpieDarkThemeStyle() { 45 | return SyntaxHighlighterStyle( 46 | baseStyle: const TextStyle(color: Color(0xFFFFFFFFF)), 47 | numberStyle: const TextStyle(color: Color.fromRGBO(168, 92, 255, 1)), 48 | commentStyle: const TextStyle(color: Color(0xFF9E9E9E)), 49 | keywordStyle: const TextStyle(color: Color.fromRGBO(255, 0, 94, 1)), 50 | stringStyle: const TextStyle(color: Color.fromRGBO(255, 215, 79, 1)), 51 | punctuationStyle: const TextStyle(color: Color(0xFFFFFFFF)), 52 | classStyle: const TextStyle(color: Color.fromRGBO(39, 213, 238, 1)), 53 | constantStyle: const TextStyle(color: Color.fromRGBO(39, 213, 238, 1))); 54 | } 55 | 56 | final TextStyle baseStyle; 57 | final TextStyle numberStyle; 58 | final TextStyle commentStyle; 59 | final TextStyle keywordStyle; 60 | final TextStyle stringStyle; 61 | final TextStyle punctuationStyle; 62 | final TextStyle classStyle; 63 | final TextStyle constantStyle; 64 | } 65 | 66 | abstract class SyntaxHighlighter { 67 | // ignore: one_member_abstracts 68 | TextSpan format(String src); 69 | } 70 | 71 | class DartSyntaxHighlighter extends SyntaxHighlighter { 72 | DartSyntaxHighlighter([this._style]) { 73 | _spans = <_HighlightSpan>[]; 74 | _style ??= SyntaxHighlighterStyle.darkThemeStyle(); 75 | } 76 | 77 | SyntaxHighlighterStyle _style; 78 | 79 | static const List _keywords = [ 80 | 'abstract', 81 | 'as', 82 | 'assert', 83 | 'async', 84 | 'await', 85 | 'break', 86 | 'case', 87 | 'catch', 88 | 'class', 89 | 'const', 90 | 'continue', 91 | 'default', 92 | 'deferred', 93 | 'do', 94 | 'dynamic', 95 | 'else', 96 | 'enum', 97 | 'export', 98 | 'external', 99 | 'extends', 100 | 'factory', 101 | 'false', 102 | 'final', 103 | 'finally', 104 | 'for', 105 | 'get', 106 | 'if', 107 | 'implements', 108 | 'import', 109 | 'in', 110 | 'is', 111 | 'library', 112 | 'new', 113 | 'null', 114 | 'operator', 115 | 'part', 116 | 'rethrow', 117 | 'return', 118 | 'set', 119 | 'static', 120 | 'super', 121 | 'switch', 122 | 'sync', 123 | 'this', 124 | 'throw', 125 | 'true', 126 | 'try', 127 | 'typedef', 128 | 'var', 129 | 'void', 130 | 'while', 131 | 'with', 132 | 'yield' 133 | ]; 134 | 135 | static const List _builtInTypes = [ 136 | 'int', 137 | 'double', 138 | 'num', 139 | 'bool' 140 | ]; 141 | 142 | String _src; 143 | StringScanner _scanner; 144 | 145 | List<_HighlightSpan> _spans; 146 | 147 | @override 148 | TextSpan format(String src) { 149 | _src = src; 150 | _scanner = StringScanner(_src); 151 | 152 | if (_generateSpans()) { 153 | // Successfully parsed the code 154 | final List formattedText = []; 155 | int currentPosition = 0; 156 | 157 | for (_HighlightSpan span in _spans) { 158 | if (currentPosition != span.start) 159 | formattedText 160 | .add(TextSpan(text: _src.substring(currentPosition, span.start))); 161 | 162 | formattedText.add(TextSpan( 163 | style: span.textStyle(_style), text: span.textForSpan(_src))); 164 | 165 | currentPosition = span.end; 166 | } 167 | 168 | if (currentPosition != _src.length) 169 | formattedText 170 | .add(TextSpan(text: _src.substring(currentPosition, _src.length))); 171 | 172 | return TextSpan(style: _style.baseStyle, children: formattedText); 173 | } else { 174 | // Parsing failed, return with only basic formatting 175 | return TextSpan(style: _style.baseStyle, text: src); 176 | } 177 | } 178 | 179 | bool _generateSpans() { 180 | int lastLoopPosition = _scanner.position; 181 | 182 | while (!_scanner.isDone) { 183 | // Skip White space 184 | _scanner.scan(RegExp(r'\s+')); 185 | 186 | // Block comments 187 | if (_scanner.scan(RegExp(r'/\*(.|\n)*\*/'))) { 188 | _spans.add(_HighlightSpan(_HighlightType.comment, 189 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 190 | continue; 191 | } 192 | 193 | // Line comments 194 | if (_scanner.scan('//')) { 195 | final int startComment = _scanner.lastMatch.start; 196 | 197 | bool eof = false; 198 | int endComment; 199 | if (_scanner.scan(RegExp(r'.*\n'))) { 200 | endComment = _scanner.lastMatch.end - 1; 201 | } else { 202 | eof = true; 203 | endComment = _src.length; 204 | } 205 | 206 | _spans.add( 207 | _HighlightSpan(_HighlightType.comment, startComment, endComment)); 208 | 209 | if (eof) break; 210 | 211 | continue; 212 | } 213 | 214 | // Raw r"String" 215 | if (_scanner.scan(RegExp(r'r".*"'))) { 216 | _spans.add(_HighlightSpan(_HighlightType.string, 217 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 218 | continue; 219 | } 220 | 221 | // Raw r'String' 222 | if (_scanner.scan(RegExp(r"r'.*'"))) { 223 | _spans.add(_HighlightSpan(_HighlightType.string, 224 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 225 | continue; 226 | } 227 | 228 | // Multiline """String""" 229 | if (_scanner.scan(RegExp(r'"""(?:[^"\\]|\\(.|\n))*"""'))) { 230 | _spans.add(_HighlightSpan(_HighlightType.string, 231 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 232 | continue; 233 | } 234 | 235 | // Multiline '''String''' 236 | if (_scanner.scan(RegExp(r"'''(?:[^'\\]|\\(.|\n))*'''"))) { 237 | _spans.add(_HighlightSpan(_HighlightType.string, 238 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 239 | continue; 240 | } 241 | 242 | // "String" 243 | if (_scanner.scan(RegExp(r'"(?:[^"\\]|\\.)*"'))) { 244 | _spans.add(_HighlightSpan(_HighlightType.string, 245 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 246 | continue; 247 | } 248 | 249 | // 'String' 250 | if (_scanner.scan(RegExp(r"'(?:[^'\\]|\\.)*'"))) { 251 | _spans.add(_HighlightSpan(_HighlightType.string, 252 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 253 | continue; 254 | } 255 | 256 | // Double 257 | if (_scanner.scan(RegExp(r'\d+\.\d+'))) { 258 | _spans.add(_HighlightSpan(_HighlightType.number, 259 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 260 | continue; 261 | } 262 | 263 | // Integer 264 | if (_scanner.scan(RegExp(r'\d+'))) { 265 | _spans.add(_HighlightSpan(_HighlightType.number, 266 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 267 | continue; 268 | } 269 | 270 | // Punctuation 271 | if (_scanner.scan(RegExp(r'[\[\]{}().!=<>&\|\?\+\-\*/%\^~;:,]'))) { 272 | _spans.add(_HighlightSpan(_HighlightType.punctuation, 273 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 274 | continue; 275 | } 276 | 277 | // Meta data 278 | if (_scanner.scan(RegExp(r'@\w+'))) { 279 | _spans.add(_HighlightSpan(_HighlightType.keyword, 280 | _scanner.lastMatch.start, _scanner.lastMatch.end)); 281 | continue; 282 | } 283 | 284 | // Words 285 | if (_scanner.scan(RegExp(r'\w+'))) { 286 | _HighlightType type; 287 | 288 | String word = _scanner.lastMatch[0]; 289 | if (word.startsWith('_')) word = word.substring(1); 290 | 291 | if (_keywords.contains(word)) 292 | type = _HighlightType.keyword; 293 | else if (_builtInTypes.contains(word)) 294 | type = _HighlightType.keyword; 295 | else if (_firstLetterIsUpperCase(word)) 296 | type = _HighlightType.klass; 297 | else if (word.length >= 2 && 298 | word.startsWith('k') && 299 | _firstLetterIsUpperCase(word.substring(1))) 300 | type = _HighlightType.constant; 301 | 302 | if (type != null) { 303 | _spans.add(_HighlightSpan( 304 | type, _scanner.lastMatch.start, _scanner.lastMatch.end)); 305 | } 306 | } 307 | 308 | // Check if this loop did anything 309 | if (lastLoopPosition == _scanner.position) { 310 | // Failed to parse this file, abort gracefully 311 | return false; 312 | } 313 | lastLoopPosition = _scanner.position; 314 | } 315 | 316 | _simplify(); 317 | return true; 318 | } 319 | 320 | void _simplify() { 321 | for (int i = _spans.length - 2; i >= 0; i -= 1) { 322 | if (_spans[i].type == _spans[i + 1].type && 323 | _spans[i].end == _spans[i + 1].start) { 324 | _spans[i] = 325 | _HighlightSpan(_spans[i].type, _spans[i].start, _spans[i + 1].end); 326 | _spans.removeAt(i + 1); 327 | } 328 | } 329 | } 330 | 331 | bool _firstLetterIsUpperCase(String str) { 332 | if (str.isNotEmpty) { 333 | final String first = str.substring(0, 1); 334 | return first == first.toUpperCase(); 335 | } 336 | return false; 337 | } 338 | } 339 | 340 | enum _HighlightType { 341 | number, 342 | comment, 343 | keyword, 344 | string, 345 | punctuation, 346 | klass, 347 | constant 348 | } 349 | 350 | class _HighlightSpan { 351 | _HighlightSpan(this.type, this.start, this.end); 352 | final _HighlightType type; 353 | final int start; 354 | final int end; 355 | 356 | String textForSpan(String src) { 357 | return src.substring(start, end); 358 | } 359 | 360 | TextStyle textStyle(SyntaxHighlighterStyle style) { 361 | if (type == _HighlightType.number) 362 | return style.numberStyle; 363 | else if (type == _HighlightType.comment) 364 | return style.commentStyle; 365 | else if (type == _HighlightType.keyword) 366 | return style.keywordStyle; 367 | else if (type == _HighlightType.string) 368 | return style.stringStyle; 369 | else if (type == _HighlightType.punctuation) 370 | return style.punctuationStyle; 371 | else if (type == _HighlightType.klass) 372 | return style.classStyle; 373 | else if (type == _HighlightType.constant) 374 | return style.constantStyle; 375 | else 376 | return style.baseStyle; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: magpie_fly_example 2 | description: Demonstrates how to use the magpie_fly plugin. 3 | publish_to: 'none' 4 | version: 0.0.1 5 | homepage: https://pub.dev/ 6 | 7 | environment: 8 | sdk: ">=2.1.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | # The following adds the Cupertino Icons font to your application. 15 | # Use with the CupertinoIcons class for iOS style icons. 16 | cupertino_icons: ^0.1.2 17 | video_player: ^0.10.5 18 | flutter_markdown: ^0.3.2 19 | android_intent: ^0.3.4+8 20 | flutter_webview_plugin: ^0.3.10+1 21 | fluttertoast: ^3.1.3 22 | 23 | dev_dependencies: 24 | flutter_test: 25 | sdk: flutter 26 | 27 | magpie_fly: 28 | path: ../ 29 | 30 | # For information on the generic Dart part of this file, see the 31 | # following page: https://dart.dev/tools/pub/pubspec 32 | 33 | # The following section is specific to Flutter. 34 | flutter: 35 | 36 | # The following line ensures that the Material Icons font is 37 | # included with your application, so that you can use the icons in 38 | # the material Icons class. 39 | uses-material-design: true 40 | 41 | # To add assets to your application, add an assets section, like this: 42 | assets: 43 | - assets/images/ 44 | - doc/ 45 | - doc/resource/ 46 | - lib/demo/ 47 | - lib/demo/horizonal_scroll/ 48 | - lib/demo/draggable_btn/ 49 | - lib/demo/pinned_sliver/ 50 | - lib/demo/pinned_sliver/component/ 51 | - lib/demo/popup_window/ 52 | - lib/demo/search_input/ 53 | - lib/demo/video/ 54 | - lib/demo/video/component/ 55 | - lib/demo/banner_widget/ 56 | 57 | -------------------------------------------------------------------------------- /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 'package:magpie_fly_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /lib/magpie_fly.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | export 'src/popup_window/popup_window.dart'; 4 | export 'src/horizontal_scroll/horizontal_scroll.dart'; 5 | export 'src/horizontal_scroll/footer_view.dart'; 6 | export 'src/feed/scroll_detect_listener.dart'; 7 | export 'src/feed/meta_consumer.dart'; 8 | export 'src/feed/video_play_model.dart'; 9 | export 'src/feed/video_meta.dart'; 10 | export 'src/sliver/pinned_appbar.dart'; 11 | export 'src/sliver/safearea_header.dart'; 12 | export 'src/search_input/search_input.dart'; 13 | export 'src/drag/draggable_btn.dart'; 14 | export 'src/banner/banner_round.dart'; 15 | -------------------------------------------------------------------------------- /lib/src/banner/banner_round.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter/material.dart'; 3 | 4 | const int IntegerMax = 0x7fffffff; 5 | 6 | typedef BuildShowView = Widget Function(int index, dynamic itemData); 7 | typedef OnBannerClickListener = void Function(int index, dynamic itemData); 8 | 9 | class BannerWidget extends StatefulWidget { 10 | final List data; 11 | final BuildShowView buildShowView; 12 | final OnBannerClickListener onBannerClickListener; 13 | final int delayTime; 14 | final int scrollTime; 15 | final double height; 16 | final double width; 17 | final Color indicatorSelectColor; 18 | final Color indicatorColor; 19 | final double indicatorRadius; 20 | final double indicatorRight; 21 | final double indicatorBottom; 22 | 23 | BannerWidget( 24 | {Key key, 25 | @required this.data, 26 | @required this.buildShowView, 27 | this.delayTime = 3, //延迟轮播时间 28 | this.scrollTime = 200, //单次轮播时间 29 | this.onBannerClickListener, //点击回调 30 | this.height = 200, //轮播高度 31 | this.width = 200, //轮播高度 32 | this.indicatorSelectColor = Colors.white, //指示器选中颜色 33 | this.indicatorColor = Colors.grey, //指示器默认颜色 34 | this.indicatorRadius = 5, //指示器大小 35 | this.indicatorRight = 30, //指示器位置距右侧 36 | this.indicatorBottom = 10 //指示器位置距底部 37 | }) 38 | : super(key: key); 39 | 40 | @override 41 | State createState() { 42 | return BannerState(); 43 | } 44 | } 45 | 46 | class BannerState extends State { 47 | final PageController pageController = 48 | PageController(initialPage: IntegerMax ~/ 2); 49 | 50 | Timer timer; 51 | int _curIndex = 0; 52 | 53 | @override 54 | void initState() { 55 | super.initState(); 56 | resetTimer(); 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return _InheritedWidget( 62 | position: getPosition(), 63 | child: Stack( 64 | children: [ 65 | initPageView(), 66 | IndicatorView( 67 | length: widget.data.length, 68 | indicatorSelectColor: widget.indicatorSelectColor, 69 | indicatorColor: widget.indicatorColor, 70 | indicatorRadius: widget.indicatorRadius, 71 | indicatorRight: widget.indicatorRight, 72 | indicatorBottom: widget.indicatorBottom, 73 | ), 74 | ], 75 | )); 76 | } 77 | 78 | int getPosition() { 79 | return _curIndex % widget.data.length; 80 | } 81 | 82 | int indexToPosition(int index) { 83 | return index % widget.data.length; 84 | } 85 | 86 | void clearTimer() { 87 | timer.cancel(); 88 | timer = null; 89 | } 90 | 91 | void resetTimer() { 92 | timer = Timer.periodic(Duration(seconds: widget.delayTime), (Timer timer) { 93 | // var i = getPosition() + 1; 94 | //print("88888:" + getPosition().toString()); 95 | pageController.animateToPage(pageController.page.toInt() + 1, 96 | duration: Duration(milliseconds: widget.scrollTime), 97 | curve: Curves.linear); 98 | }); 99 | } 100 | 101 | Widget initPageView() { 102 | return SizedBox( 103 | width: widget.width, 104 | height: widget.height, 105 | child: widget.data.length == 0 106 | ? null 107 | : GestureDetector( 108 | onTap: () { 109 | widget.onBannerClickListener( 110 | getPosition(), widget.data[getPosition()]); 111 | }, 112 | child: PageView.builder( 113 | controller: pageController, 114 | physics: 115 | const PageScrollPhysics(parent: ClampingScrollPhysics()), 116 | onPageChanged: (index) { 117 | //监听变换 主要为了同步指示器 118 | setState(() { 119 | _curIndex = index; 120 | }); 121 | }, 122 | itemCount: IntegerMax, 123 | itemBuilder: (BuildContext context, int index) { 124 | return widget.buildShowView( 125 | index, widget.data[indexToPosition(index)]); 126 | }), 127 | ), 128 | ); 129 | } 130 | 131 | @override 132 | void dispose() { 133 | clearTimer(); 134 | super.dispose(); 135 | } 136 | } 137 | 138 | class _InheritedWidget extends InheritedWidget { 139 | _InheritedWidget({ 140 | Key key, 141 | @required Widget child, 142 | @required this.position, 143 | }) : super(key: key, child: child); 144 | 145 | final int position; 146 | 147 | static _InheritedWidget of(BuildContext context) { 148 | return context.inheritFromWidgetOfExactType(_InheritedWidget); 149 | } 150 | 151 | @override 152 | bool updateShouldNotify(_InheritedWidget oldWidget) { 153 | return position != oldWidget.position; 154 | } 155 | } 156 | 157 | //指示器view 158 | class IndicatorView extends StatefulWidget { 159 | final Color indicatorSelectColor; 160 | final Color indicatorColor; 161 | final double indicatorRadius; 162 | final double indicatorRight; 163 | final double indicatorBottom; 164 | final int length; 165 | 166 | //final int position; 167 | 168 | const IndicatorView({ 169 | Key key, 170 | this.indicatorSelectColor = Colors.white, 171 | this.indicatorColor = Colors.grey, 172 | this.indicatorRadius = 5, 173 | this.indicatorRight = 30, 174 | this.indicatorBottom = 10, 175 | this.length = 0, 176 | //this.position = 0 177 | }) : super(key: key); 178 | 179 | @override 180 | State createState() { 181 | return IndicatorState(); 182 | } 183 | } 184 | 185 | class IndicatorState extends State { 186 | @override 187 | Widget build(BuildContext context) { 188 | return initIndicator(context); 189 | } 190 | 191 | Widget initIndicator(BuildContext context) { 192 | double dindicatorRight; 193 | if (widget.indicatorRight != 0) { 194 | dindicatorRight = widget.indicatorRight; 195 | } 196 | 197 | return Positioned( 198 | right: dindicatorRight, 199 | bottom: widget.indicatorBottom, 200 | child: Row( 201 | children: initIndicatorItem(context), 202 | ), 203 | ); 204 | } 205 | 206 | List initIndicatorItem(BuildContext context) { 207 | var items = []; 208 | for (var i = 0; i < widget.length; i++) { 209 | items.add(Padding( 210 | padding: const EdgeInsets.symmetric(horizontal: 3.0), 211 | child: ClipOval( 212 | child: Container( 213 | width: widget.indicatorRadius, 214 | height: widget.indicatorRadius, 215 | color: i == _InheritedWidget.of(context).position 216 | ? widget.indicatorSelectColor 217 | : widget.indicatorColor, 218 | ), 219 | ), 220 | )); 221 | } 222 | return items; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /lib/src/drag/draggable_btn.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DraggableBtn extends StatefulWidget { 4 | final Widget child; 5 | final Offset initOffset; 6 | final Offset fullScreenOffset; 7 | 8 | const DraggableBtn( 9 | {Key key, 10 | @required this.child, 11 | this.initOffset, 12 | this.fullScreenOffset = Offset.zero}) 13 | : super(key: key); 14 | 15 | @override 16 | _DraggableBtnState createState() => _DraggableBtnState(); 17 | } 18 | 19 | class _DraggableBtnState extends State 20 | with SingleTickerProviderStateMixin { 21 | var _animController; 22 | var _scaleAnim; 23 | var _left = 50.0; 24 | var _top = 50.0; 25 | var globalKey = GlobalKey(); 26 | var childWidth; 27 | var childHeight; 28 | 29 | @override 30 | void initState() { 31 | _left = widget.initOffset.dx; 32 | _top = widget.initOffset.dy; 33 | WidgetsBinding.instance.addPostFrameCallback((_) { 34 | RenderBox renderBox = globalKey.currentContext.findRenderObject(); 35 | childWidth = renderBox.size.width; 36 | childHeight = renderBox.size.height; 37 | }); 38 | _animController = 39 | AnimationController(vsync: this, duration: Duration(milliseconds: 200)); 40 | _scaleAnim = Tween(begin: 1.0, end: 1.5).animate(_animController); 41 | super.initState(); 42 | } 43 | 44 | @override 45 | void dispose() { 46 | _animController.dispose(); 47 | super.dispose(); 48 | } 49 | 50 | @override 51 | Widget build(BuildContext context) { 52 | return Stack( 53 | children: [ 54 | Positioned( 55 | left: _left, 56 | top: _top, 57 | child: Draggable( 58 | childWhenDragging: Container(), 59 | feedback: ScaleTransition(scale: _scaleAnim, child: widget.child), 60 | child: Container( 61 | key: globalKey, 62 | child: widget.child, 63 | ), 64 | onDragStarted: () { 65 | _animController.forward(); 66 | }, 67 | onDragCompleted: () {}, 68 | onDraggableCanceled: (v, offset) { 69 | debugPrint("offset y ${offset.dy}"); 70 | var size = MediaQuery.of(context).size; 71 | setState(() { 72 | _left = offset.dx - widget.fullScreenOffset.dx; 73 | _top = offset.dy - widget.fullScreenOffset.dy; 74 | if (_left < 0) _left = 0; 75 | if (_left > size.width - childWidth) { 76 | _left = size.width - childWidth; 77 | } 78 | if (_top < 0) _top = 0; 79 | if (_top > size.height - childHeight) { 80 | _top = size.height - childHeight; 81 | } 82 | }); 83 | }, 84 | ), 85 | ) 86 | ], 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/feed/meta_consumer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | import 'video_meta.dart'; 5 | import 'video_play_model.dart'; 6 | 7 | /// Wrap child with [MetaConsumer], so it can be rebuild when video index changed 8 | class MetaConsumer extends StatelessWidget { 9 | /// view index in the [ListView] 10 | final int index; 11 | 12 | /// relative data bing to the view item 13 | final T data; 14 | 15 | /// Widget builder 16 | final Widget Function( 17 | BuildContext context, VideoPlayModel model, Widget child) builder; 18 | 19 | MetaConsumer({ 20 | @required this.index, 21 | @required this.data, 22 | @required this.builder, 23 | }); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Consumer( 28 | builder: (context, model, child) { 29 | return MetaData( 30 | metaData: VideoMeta("$data", index), 31 | child: builder(context, model, child), 32 | ); 33 | }, 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/feed/scroll_detect_listener.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/rendering.dart'; 4 | import 'package:magpie_fly/magpie_fly.dart'; 5 | import 'package:provider/provider.dart'; 6 | 7 | import 'video_meta.dart'; 8 | import 'video_play_model.dart'; 9 | 10 | /// Detect scroll event through [NotificationListener]; All the child wrapped 11 | /// with [MetaConsumer] will be return in [VisibleCallback] when they appear on screen. 12 | /// 13 | /// {@tool sample} 14 | /// 15 | /// This next example shows how to use ScrollDetectListener: 16 | /// 17 | /// ```dart 18 | /// ScrollDetectListener( 19 | /// offset: 60 /*Appbar height*/, 20 | /// onVisible: (metas, model) { 21 | /// var videoItem = model.videoData; 22 | /// if (metas != null && metas.length > 0) { 23 | /// var meta = metas[0] as VideoMeta; 24 | /// var targetData = dataList[meta.index]; 25 | /// if (videoItem == null || 26 | /// (videoItem != null && videoItem != targetData)) { 27 | /// model.updateVideo(meta.index, targetData); 28 | /// } 29 | /// } else if (videoItem != null) { 30 | /// model.updateVideo(-1, null); 31 | /// } 32 | /// }, 33 | /// child: ListView.separated( 34 | /// padding: EdgeInsets.zero, 35 | /// itemBuilder: (context, index) { 36 | /// return index % 5 == 0 37 | /// ? VideoComponent(index, "video $index") 38 | /// : TextComponent(index); 39 | /// }, 40 | /// separatorBuilder: (BuildContext context, int index) => Divider( 41 | /// height: 1, 42 | /// ), 43 | /// itemCount: dataList.length, 44 | /// ), 45 | /// ) 46 | /// ``` 47 | /// {@end-tool} 48 | class ScrollDetectListener extends StatelessWidget { 49 | final _ScrollNotification _notification; 50 | 51 | ScrollDetectListener({ 52 | Key key, 53 | @required Widget child, 54 | @required VisibleCallback onVisible, 55 | int extentSize = 10, 56 | double percentIn = 0.8, 57 | int offset = 0, 58 | bool detect = true, 59 | NotificationListenerCallback onNotification, 60 | }) : assert(child != null), 61 | assert(onVisible != null), 62 | assert(percentIn > 0 && percentIn <= 1, 'percent should be (0,1]'), 63 | assert(offset > 0, 'offset should >0'), 64 | _notification = _ScrollNotification( 65 | config: Config( 66 | predicate: (_) => _ is VideoMeta, 67 | callback: onVisible, 68 | detect: detect, 69 | percentIn: percentIn, 70 | offset: offset, 71 | extentSize: extentSize, 72 | ), 73 | child: child, 74 | ); 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | return ChangeNotifierProvider( 79 | create: (_) => VideoPlayModel(), 80 | child: _notification, 81 | ); 82 | } 83 | } 84 | 85 | typedef Predicate = bool Function(dynamic metaData); 86 | typedef VisibleCallback = void Function(List data, VideoPlayModel model); 87 | 88 | /// Supported configuration 89 | class Config { 90 | final Predicate predicate; 91 | final VisibleCallback callback; 92 | final int extentSize; 93 | final bool detect; 94 | /// when the view is "visible" on screen 95 | final double percentIn; 96 | /// take care of AppBar height if exists 97 | final int offset; 98 | /// [onNotification] for [NotificationListener] 99 | final NotificationListenerCallback onNotification; 100 | 101 | Config({ 102 | this.predicate, 103 | this.callback, 104 | this.extentSize, 105 | this.detect, 106 | this.percentIn, 107 | this.offset, 108 | this.onNotification, 109 | }); 110 | } 111 | 112 | class _ScrollNotification extends StatelessWidget { 113 | final Widget _child; 114 | final Config _config; 115 | 116 | _ScrollNotification({Config config, Widget child}) 117 | : _config = config, 118 | _child = child; 119 | 120 | @override 121 | Widget build(BuildContext context) { 122 | if (_config.detect) { 123 | Future.delayed(Duration(milliseconds: 200), () { 124 | _internalHit(context, _config); 125 | }).catchError((_) => {}); 126 | } 127 | return NotificationListener( 128 | child: _child, 129 | onNotification: (n) { 130 | if (!(n is ScrollEndNotification)) { 131 | return _config.onNotification != null 132 | ? _config.onNotification(n) 133 | : false; 134 | } 135 | Future.microtask(() { 136 | return _internalHit(context, _config); 137 | }).then((_) { 138 | if (!_) { 139 | Future.delayed(Duration(milliseconds: 300), () { 140 | var result = _internalHit(context, _config); 141 | if (!result) {} 142 | }).catchError((_) => {}); 143 | } 144 | }).catchError((_) => {}); 145 | return _config.onNotification != null 146 | ? _config.onNotification(n) 147 | : false; 148 | }, 149 | ); 150 | } 151 | 152 | bool _internalHit(BuildContext context, Config config) { 153 | var height = MediaQuery.of(context).size.height; 154 | var x = MediaQuery.of(context).size.width / 2; 155 | var targets = []; 156 | for (var y = 0; y < height; y += config.extentSize) { 157 | var index = _getMeta(context, x, y.toDouble(), config.predicate, 158 | config.percentIn, config.offset); 159 | if (index != null && !targets.contains(index)) { 160 | targets.add(index); 161 | } 162 | } 163 | try { 164 | var model = Provider.of(context, listen: false); 165 | config.callback(targets, model); 166 | } catch (e) {} 167 | return targets.length > 0; 168 | } 169 | 170 | T _getMeta(BuildContext context, double x, double y, Predicate predicate, 171 | double percentIn, int top) { 172 | var renderBox = context.findRenderObject() as RenderBox; 173 | var offset = renderBox.localToGlobal(Offset(x, y)); 174 | var result = HitTestResult(); 175 | WidgetsBinding.instance.hitTest(result, offset); 176 | 177 | dynamic invalidOne; 178 | for (var i in result.path) { 179 | if (!(i.target is RenderMetaData)) { 180 | continue; 181 | } 182 | var d = i.target as RenderMetaData; 183 | if (d.child == invalidOne) { 184 | continue; 185 | } 186 | var data = d.metaData; 187 | if (!predicate(data)) { 188 | invalidOne = d.child; 189 | continue; 190 | } 191 | if (d.child == invalidOne) { 192 | continue; 193 | } 194 | var p = d.child.localToGlobal(Offset.zero); 195 | if (p.dy + (1 - percentIn) * d.child.size.height - top < 0) { 196 | invalidOne = d.child; 197 | continue; 198 | } 199 | return data as T; 200 | } 201 | return null; 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /lib/src/feed/video_meta.dart: -------------------------------------------------------------------------------- 1 | class VideoMeta { 2 | final String data; 3 | final int index; 4 | 5 | VideoMeta(this.data, this.index); 6 | 7 | @override 8 | String toString() { 9 | return "{data=$data, index=$index}"; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/src/feed/video_play_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class VideoPlayModel extends ChangeNotifier { 4 | dynamic videoData; 5 | int playIndex; 6 | 7 | void updateVideo(int index, dynamic data) { 8 | playIndex = index; 9 | videoData = data; 10 | notifyListeners(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/src/horizontal_scroll/footer_default_painter.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter/widgets.dart'; 4 | 5 | //回弹printer 6 | //需要画圆弧、长方形、text 7 | 8 | class TopicFooterPainter extends CustomPainter { 9 | TopicFooterPainter( 10 | {this.text, 11 | this.arcWidth = 0, 12 | this.rectWidth = 0, 13 | this.startDrawX = 0, 14 | this.textStyle}); 15 | double arcWidth; //圆弧的size 16 | double rectWidth; //矩形的size 17 | double startDrawX; //开始绘制的x坐标 18 | String text; //需要绘制的text 19 | TextStyle textStyle; 20 | Color paintColor; //画笔颜色 21 | Paint dPaint = Paint(); //画笔 22 | Path _arcPath; //画圆弧的path 23 | 24 | @override 25 | void paint(Canvas canvas, Size size) { 26 | //给画笔设置属性 27 | dPaint 28 | ..color = 29 | paintColor == null ? Color.fromRGBO(237, 239, 241, 1) : paintColor 30 | ..style = PaintingStyle.fill 31 | ..strokeWidth = 3; 32 | 33 | //设置圆弧的最大宽度,圆弧最大为半圆,所以宽最大为高的一半 34 | arcWidth = min(arcWidth, size.height / 2); 35 | //圆弧的宽大于0,画圆弧 36 | if (arcWidth > 0) { 37 | _drawArc(canvas, size, dPaint); 38 | } 39 | //画矩形 40 | if (rectWidth > 0) { 41 | canvas.drawRect( 42 | Rect.fromLTRB(arcWidth + startDrawX, 0, 43 | arcWidth + rectWidth + startDrawX + 10, size.height), 44 | dPaint); 45 | } 46 | //画文字 47 | if (text != null && text.length > 0) { 48 | _drawText(canvas, size, dPaint); 49 | } 50 | } 51 | 52 | //画圆弧 53 | void _drawArc(Canvas canvas, Size size, Paint paint) { 54 | if (_arcPath == null) { 55 | _arcPath = Path(); //初始化 56 | } 57 | _arcPath.reset(); //重置 58 | if (size.width <= rectWidth) { 59 | //总的宽度小于矩形的宽,不画圆弧 60 | return; 61 | } 62 | //贝塞尔曲线画圆弧 63 | var startPoint = Offset(arcWidth + startDrawX, 0); //圆弧起点 64 | var controlPoint1 = Offset(startDrawX - arcWidth * 0.2, 0); //圆弧控制点1 65 | var controlPoint2 = 66 | Offset(startDrawX - arcWidth * 0.2, size.height); //圆弧控制点2 67 | var endPoint = Offset(arcWidth + startDrawX, size.height); //圆弧终点 68 | 69 | _arcPath.moveTo(startPoint.dx, startPoint.dy); 70 | _arcPath.cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx, 71 | controlPoint2.dy, endPoint.dx, endPoint.dy); 72 | canvas.drawPath(_arcPath, paint); 73 | } 74 | 75 | //垂直绘制文字 76 | void _drawText(Canvas canvas, Size size, Paint paint) { 77 | if (textStyle == null) { 78 | textStyle = 79 | TextStyle(color: Color.fromRGBO(141, 145, 147, 1), fontSize: 15); 80 | } 81 | var left = arcWidth + startDrawX; 82 | var textWidth = 0.0; //一个字的宽度 83 | //中心点 84 | var offsetX = left + rectWidth / 2.0 - textWidth / 2.0; 85 | var offsetY = (size.height - _findAllTextHeight()) / 2; 86 | //遍历text,一个字一个字的画上去 87 | for (var rune in text.runes) { 88 | var str = String.fromCharCode(rune); 89 | var span = TextSpan(style: textStyle, text: str); 90 | var tp = TextPainter( 91 | text: span, 92 | textAlign: TextAlign.center, 93 | textDirection: TextDirection.rtl); 94 | tp.layout(); 95 | tp.paint(canvas, Offset(offsetX, offsetY)); 96 | offsetY += tp.height; 97 | } 98 | } 99 | 100 | //得到text文字的所有总高度 101 | double _findAllTextHeight() { 102 | var allHeight = 0.0; 103 | for (var rune in text.runes) { 104 | var str = String.fromCharCode(rune); 105 | var span = TextSpan(style: textStyle, text: str); 106 | var tp = TextPainter( 107 | text: span, 108 | textAlign: TextAlign.center, 109 | textDirection: TextDirection.ltr); 110 | tp.layout(); 111 | allHeight += tp.height; 112 | } 113 | return allHeight; 114 | } 115 | 116 | @override 117 | bool shouldRepaint(CustomPainter oldDelegate) { 118 | return true; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/src/horizontal_scroll/footer_view.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'package:flutter/material.dart'; 3 | import 'footer_default_painter.dart'; 4 | 5 | abstract class FootBaseView { 6 | //outRangeOffset :超出scrollview的contentsize部分 7 | void onScrollUpdate(ScrollController scrollController, double outRangeOffset){} 8 | } 9 | 10 | _FooterDefaultViewState _state; 11 | //默认的footerview 12 | class FooterDefaultView extends StatefulWidget implements FootBaseView{ 13 | 14 | FooterDefaultView({@required this.height, this.width = 70}); 15 | 16 | final double width; 17 | final double height; 18 | 19 | @override 20 | State createState() { 21 | _state = _FooterDefaultViewState(); 22 | return _state; 23 | } 24 | 25 | @override 26 | void onScrollUpdate(ScrollController scrollController, double outRangeOffset) { 27 | if(_state != null) { 28 | _state.onScrollUpdate(scrollController, outRangeOffset); 29 | } 30 | } 31 | } 32 | 33 | class _FooterDefaultViewState extends State{ 34 | 35 | TopicFooterPainter _footerPainter; 36 | 37 | @override 38 | void dispose() { 39 | super.dispose(); 40 | _state = null; 41 | } 42 | 43 | void onScrollUpdate(ScrollController scrollController, double outRangeOffset) { 44 | var _fresh = false; 45 | var arcWidth = 0.0; 46 | //大于widget.width的部分画半圆 47 | if(outRangeOffset > widget.width){ 48 | arcWidth = outRangeOffset - widget.width; 49 | } 50 | arcWidth = min(widget.height / 2, arcWidth); 51 | if (_footerPainter.arcWidth != arcWidth) { 52 | _footerPainter.arcWidth = arcWidth; 53 | _footerPainter.text = arcWidth>0?'松开查看':'查看更多'; 54 | _fresh = true; 55 | } 56 | 57 | if (_fresh) { 58 | setState(() {}); 59 | } 60 | } 61 | 62 | @override 63 | void initState() { 64 | super.initState(); 65 | _footerPainter = 66 | TopicFooterPainter(rectWidth: widget.width, text: '查看更多'); 67 | } 68 | 69 | @override 70 | Widget build(BuildContext context) { 71 | return Container( 72 | child: CustomPaint( 73 | painter: _footerPainter, 74 | size: Size(widget.width + _footerPainter.arcWidth, widget.height), 75 | ), 76 | ); 77 | } 78 | } 79 | 80 | 81 | //直接显示text的footerview 82 | class FooterTextView extends StatelessWidget implements FootBaseView{ 83 | FooterTextView({@required this.height, this.width = 70, this.text = '加载中...'}); 84 | 85 | final double width; 86 | final double height; 87 | final String text; 88 | 89 | @override 90 | Widget build(BuildContext context) { 91 | return Container( 92 | width: width, 93 | height: height, 94 | child: Center( 95 | child: Text(text), 96 | ), 97 | ); 98 | } 99 | 100 | @override 101 | void onScrollUpdate(ScrollController scrollController, double footerViewX) { 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /lib/src/horizontal_scroll/horizontal_scroll.dart: -------------------------------------------------------------------------------- 1 | //底部加载更多 2 | 3 | import 'dart:math'; 4 | import 'package:flutter/material.dart'; 5 | import 'footer_view.dart'; 6 | 7 | enum FooterViewShowStyle { 8 | dismiss, //滑动到最底部,回弹之后,不显示footerview 9 | showHalf, //滑动到最底部,回弹之后,显示一半footerview3 10 | showWhole //滑动到最底部,回弹之后,完全footerview 11 | } 12 | 13 | class HorizontalScrollView extends StatefulWidget { 14 | HorizontalScrollView( 15 | {this.footerView, //footerview可用定义好的,也可自定义 16 | this.itemMargin = const EdgeInsets.all(0), 17 | this.itembuilder, 18 | this.itemCount = 0, 19 | this.footerWidth = 0, 20 | this.width, 21 | this.height, 22 | this.footerShowStyle = FooterViewShowStyle.showHalf, 23 | this.footViewFlowScrollAlways = false, 24 | this.onFooterLoadingCallBack}); 25 | 26 | final EdgeInsets itemMargin; 27 | final IndexedWidgetBuilder itembuilder; 28 | final int itemCount; 29 | final Widget footerView; 30 | final FooterViewShowStyle footerShowStyle; 31 | final bool footViewFlowScrollAlways; //footerview滑动到它的宽度之后,是否还跟着scrollview滑动 32 | final double footerWidth; 33 | final double height; 34 | final double width; 35 | final VoidCallback onFooterLoadingCallBack; 36 | 37 | @override 38 | State createState() => _HorizontalScrollViewState(); 39 | } 40 | 41 | class _HorizontalScrollViewState extends State { 42 | ScrollController _scrollController; 43 | double _fotterViewRight = 0; 44 | 45 | @override 46 | void initState() { 47 | super.initState(); 48 | _fotterViewRight = -widget.footerWidth; 49 | _scrollController = ScrollController(); 50 | _scrollController.addListener(_scrollUpdate); 51 | } 52 | 53 | double _showFooterWidth() { 54 | switch (widget.footerShowStyle) { 55 | case FooterViewShowStyle.dismiss: 56 | return 0; 57 | case FooterViewShowStyle.showHalf: 58 | return widget.footerWidth / 2; 59 | case FooterViewShowStyle.showWhole: 60 | return widget.footerWidth; 61 | } 62 | return 0; 63 | } 64 | 65 | void _scrollUpdate() { 66 | if (widget.footerView == null) return; 67 | //超出scrollview最大宽度的部分 68 | var outRangeOffset = _scrollController.position.pixels - 69 | _scrollController.position.maxScrollExtent + 70 | _showFooterWidth(); 71 | var _fresh = false; //是否刷新 72 | 73 | //如果footerLeft大于0,说明footview需要跟着scroll滑动了 74 | if (outRangeOffset >= 0) { 75 | var footerViewRight = min(0, outRangeOffset - widget.footerWidth); 76 | if (widget.footViewFlowScrollAlways == true) { 77 | footerViewRight = outRangeOffset - widget.footerWidth; 78 | } 79 | footerViewRight = footerViewRight.toDouble(); 80 | if (_fotterViewRight != footerViewRight) { 81 | _fotterViewRight = footerViewRight; 82 | _fresh = true; 83 | } 84 | } else if (_fotterViewRight != -widget.footerWidth) { 85 | _fotterViewRight = -widget.footerWidth; 86 | _fresh = true; 87 | } 88 | if (widget.footerView is FootBaseView) { 89 | var footerView = widget.footerView as FootBaseView; 90 | footerView.onScrollUpdate(_scrollController, outRangeOffset); 91 | } 92 | if (_fresh) { 93 | setState(() {}); 94 | } 95 | } 96 | 97 | void _onPointerUp(){ 98 | var outRangeOffset = _scrollController.position.pixels - 99 | _scrollController.position.maxScrollExtent + 100 | _showFooterWidth(); 101 | if(widget.onFooterLoadingCallBack != null){ 102 | if(outRangeOffset > _showFooterWidth() + widget.footerWidth/2){ 103 | widget.onFooterLoadingCallBack(); 104 | } 105 | } 106 | } 107 | 108 | @override 109 | Widget build(BuildContext context) { 110 | 111 | return Container( 112 | width: widget.width ?? MediaQuery.of(context).size.width, 113 | height: widget.height ?? 0, 114 | child: Stack( 115 | children: [ 116 | Positioned( 117 | bottom: widget.itemMargin.bottom, 118 | right: _fotterViewRight, 119 | top: widget.itemMargin.top, 120 | child: widget.footerView ?? Text(''), 121 | ), 122 | Listener( 123 | onPointerCancel: (event){_onPointerUp();}, 124 | onPointerUp: (event){_onPointerUp();}, 125 | child: CustomScrollView( 126 | controller: _scrollController, 127 | scrollDirection: Axis.horizontal, 128 | physics: BouncingScrollPhysics(), 129 | slivers: [ 130 | SliverList( 131 | delegate: SliverChildListDelegate( 132 | List.generate(widget.itemCount, (index) { 133 | return Container( 134 | margin: widget.itemMargin, 135 | child: widget.itembuilder(context, index) ?? Text(''), 136 | ); 137 | })), 138 | ), 139 | SliverToBoxAdapter( 140 | child: Container( 141 | width: _showFooterWidth(), //假的footerview 142 | ), 143 | ) 144 | ], 145 | ), 146 | ) 147 | ], 148 | ), 149 | ); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lib/src/popup_window/popup_window.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // ignore: must_be_immutable 4 | class PopupWindow extends StatefulWidget { 5 | static void showPopWindow(context, GlobalKey popKey, 6 | [PopDirection popDirection = PopDirection.bottom, 7 | Widget popWidget, 8 | double offset = 0]) { 9 | Navigator.push( 10 | context, 11 | PopRoute( 12 | child: PopupWindow( 13 | popKey: popKey, 14 | popDirection: popDirection, 15 | popWidget: popWidget, 16 | offset: offset, 17 | ))); 18 | } 19 | 20 | Widget window; 21 | GlobalKey popKey; 22 | PopDirection popDirection; 23 | Widget popWidget; //自定义widget 24 | double offset; //popupWindow偏移量 25 | 26 | PopupWindow( 27 | {this.popWidget, 28 | this.popKey, 29 | this.popDirection = PopDirection.bottom, 30 | this.offset}); 31 | 32 | @override 33 | State createState() { 34 | return _PopupWindowState(); 35 | } 36 | } 37 | 38 | class _PopupWindowState extends State { 39 | GlobalKey buttonKey; 40 | double left = -100; 41 | double top = -100; 42 | 43 | @override 44 | void initState() { 45 | super.initState(); 46 | buttonKey = GlobalKey(); 47 | WidgetsBinding.instance.addPostFrameCallback((_) { 48 | RenderBox renderBox = widget.popKey.currentContext.findRenderObject(); 49 | Offset localToGlobal = 50 | renderBox.localToGlobal(Offset.zero); //targetWidget的坐标位置 51 | Size size = renderBox.size; //targetWidget的size 52 | 53 | RenderBox buttonBox = buttonKey.currentContext.findRenderObject(); 54 | var buttonSize = buttonBox.size; //button的size 55 | switch (widget.popDirection) { 56 | case PopDirection.left: 57 | left = localToGlobal.dx - buttonSize.width - widget.offset; 58 | top = localToGlobal.dy + size.height / 2 - buttonSize.height / 2; 59 | break; 60 | case PopDirection.top: 61 | left = localToGlobal.dx + size.width / 2 - buttonSize.width / 2; 62 | top = localToGlobal.dy - buttonSize.height - widget.offset; 63 | fixPosition(buttonSize); 64 | break; 65 | case PopDirection.right: 66 | left = localToGlobal.dx + size.width + widget.offset; 67 | top = localToGlobal.dy + size.height / 2 - buttonSize.height / 2; 68 | break; 69 | case PopDirection.bottom: 70 | left = localToGlobal.dx + size.width / 2 - buttonSize.width / 2; 71 | top = localToGlobal.dy + size.height + widget.offset; 72 | fixPosition(buttonSize); 73 | break; 74 | } 75 | setState(() {}); 76 | }); 77 | } 78 | 79 | void fixPosition(Size buttonSize) { 80 | if (left < 0) { 81 | left = 0; 82 | } 83 | if (left + buttonSize.width >= MediaQuery.of(context).size.width) { 84 | left = MediaQuery.of(context).size.width - buttonSize.width; 85 | } 86 | } 87 | 88 | @override 89 | Widget build(BuildContext context) { 90 | return Material( 91 | color: Colors.transparent, 92 | child: GestureDetector( 93 | child: Stack( 94 | children: [ 95 | Container( 96 | width: MediaQuery.of(context).size.width, 97 | height: MediaQuery.of(context).size.height, 98 | color: Colors.transparent, 99 | ), 100 | Positioned( 101 | child: GestureDetector( 102 | child: _buildCustomWidget(widget.popWidget), 103 | ), 104 | left: left, 105 | top: top, 106 | ) 107 | ], 108 | ), 109 | onTap: () { 110 | Navigator.pop(context); 111 | }, 112 | ), 113 | ); 114 | } 115 | 116 | Widget _buildCustomWidget(Widget child) => Container( 117 | key: buttonKey, 118 | child: child, 119 | ); 120 | } 121 | 122 | class PopRoute extends PopupRoute { 123 | final Duration _duration = Duration(milliseconds: 200); 124 | Widget child; 125 | 126 | PopRoute({@required this.child}); 127 | 128 | @override 129 | Color get barrierColor => null; 130 | 131 | @override 132 | bool get barrierDismissible => true; 133 | 134 | @override 135 | String get barrierLabel => null; 136 | 137 | @override 138 | Widget buildPage(BuildContext context, Animation animation, 139 | Animation secondaryAnimation) { 140 | return child; 141 | } 142 | 143 | @override 144 | Widget buildTransitions(BuildContext context, Animation animation, 145 | Animation secondaryAnimation, Widget child) { 146 | return FadeTransition( 147 | opacity: Tween(begin: 0.0, end: 1.0).animate(CurvedAnimation( 148 | parent: animation, 149 | curve: Curves.fastOutSlowIn, 150 | )), 151 | child: child, 152 | ); 153 | } 154 | 155 | @override 156 | Duration get transitionDuration => _duration; 157 | } 158 | 159 | enum PopDirection { left, top, right, bottom } 160 | -------------------------------------------------------------------------------- /lib/src/search_input/search_input.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SearchInput extends StatelessWidget { 4 | final Color borderColor; 5 | final Color fillColor; 6 | final String hintText; 7 | final double blurRadius; 8 | final double circleRadius; 9 | final Color hintTextColor; 10 | final Color prefixIconColor; 11 | final bool isBorderShadow; 12 | final bool enabel; 13 | final ValueChanged onChanged; 14 | final VoidCallback onEditingComplete; 15 | final ValueChanged onSubmitted; 16 | final TextEditingController controller; 17 | 18 | SearchInput( 19 | {Key key, 20 | this.borderColor, 21 | this.fillColor = Colors.white, 22 | this.hintText, 23 | this.hintTextColor, 24 | this.blurRadius = 5.0, 25 | this.circleRadius = 10.0, 26 | this.isBorderShadow = true, 27 | this.prefixIconColor, 28 | this.enabel, 29 | this.controller, 30 | this.onChanged, 31 | this.onEditingComplete, 32 | this.onSubmitted}) 33 | : super(key: key); 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Container( 38 | decoration: isBorderShadow 39 | ? BoxDecoration( 40 | color: fillColor, 41 | boxShadow: [ 42 | BoxShadow(color: borderColor, blurRadius: blurRadius), 43 | ], 44 | borderRadius: BorderRadius.all(Radius.circular(circleRadius))) 45 | : null, 46 | child: TextField( 47 | controller: controller ?? TextEditingController(), 48 | enabled: enabel, 49 | onChanged: onChanged, 50 | onEditingComplete: onEditingComplete, 51 | onSubmitted: onSubmitted, 52 | decoration: InputDecoration( 53 | prefixIcon: Icon(Icons.search, 54 | color: prefixIconColor ?? Theme.of(context).accentColor), 55 | border: _createBorder(), 56 | enabledBorder: _createBorder(), 57 | hintText: hintText, 58 | hintStyle: TextStyle(color: hintTextColor)), 59 | ), 60 | ); 61 | } 62 | 63 | InputBorder _createBorder() { 64 | if (!isBorderShadow) { 65 | return OutlineInputBorder( 66 | borderSide: BorderSide(color: borderColor), 67 | borderRadius: BorderRadius.circular(blurRadius)); 68 | } 69 | return InputBorder.none; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/sliver/pinned_appbar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class PinnedAppBar extends StatelessWidget { 5 | final PreferredSizeWidget child; 6 | final Color color; 7 | 8 | const PinnedAppBar({Key key, this.child, this.color}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return SliverPersistentHeader( 13 | delegate: _SliverAppBarDelegate(child, color), 14 | pinned: true, 15 | ); 16 | } 17 | } 18 | 19 | class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { 20 | _SliverAppBarDelegate(this._tabBar, this.color); 21 | 22 | final PreferredSizeWidget _tabBar; 23 | final Color color; 24 | 25 | @override 26 | double get minExtent => _tabBar.preferredSize.height; 27 | 28 | @override 29 | double get maxExtent => _tabBar.preferredSize.height; 30 | 31 | @override 32 | Widget build( 33 | BuildContext context, double shrinkOffset, bool overlapsContent) { 34 | return Container(child: _tabBar, color: color ?? Colors.white); 35 | } 36 | 37 | @override 38 | bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { 39 | return false; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/sliver/safearea_header.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class _SafeAreaHeader extends StatelessWidget { 5 | final Widget child; 6 | final double placeHolderSize; 7 | final double height; 8 | 9 | const _SafeAreaHeader({ 10 | Key key, 11 | @required this.child, 12 | @required this.height, 13 | this.placeHolderSize, 14 | }) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | var top = MediaQuery.of(context).padding.top + placeHolderSize ?? 0; 19 | return Container( 20 | height: height - top, 21 | child: Stack( 22 | fit: StackFit.expand, 23 | overflow: Overflow.visible, 24 | children: [Positioned(top: -top, child: child)], 25 | )); 26 | } 27 | } 28 | 29 | class _SafeAreaPinnedPlaceholder extends StatelessWidget { 30 | final double placeHolderSize; 31 | final Color color; 32 | 33 | _SafeAreaPinnedPlaceholder(this.placeHolderSize, {this.color = Colors.white}); 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return SliverPersistentHeader( 38 | pinned: true, 39 | delegate: _Delegate( 40 | color, 41 | height: MediaQuery.of(context).padding.top, 42 | placeHolderSize: placeHolderSize, 43 | ), 44 | ); 45 | } 46 | } 47 | 48 | class _Delegate extends SliverPersistentHeaderDelegate { 49 | _Delegate(this.color, {@required this.height, this.placeHolderSize = 0}); 50 | 51 | final double height; 52 | final double placeHolderSize; 53 | final Color color; 54 | 55 | @override 56 | double get minExtent => height; 57 | 58 | @override 59 | double get maxExtent => height + placeHolderSize; 60 | 61 | @override 62 | Widget build( 63 | BuildContext context, double shrinkOffset, bool overlapsContent) { 64 | var alpha = (shrinkOffset / maxExtent * 255).clamp(0, 255).toInt(); 65 | return Column( 66 | children: [ 67 | Container(color: color.withAlpha(alpha), height: height), 68 | Expanded( 69 | child: Container(height: placeHolderSize, color: Colors.transparent), 70 | ) 71 | ], 72 | ); 73 | } 74 | 75 | @override 76 | bool shouldRebuild(_Delegate oldDelegate) { 77 | return false; 78 | } 79 | } 80 | 81 | NestedScrollViewHeaderSliversBuilder pinnedBuilder( 82 | {double placeHolderSize = 0, 83 | Color color = Colors.white, 84 | double height, 85 | WidgetBuilder headerBuilder, 86 | NestedScrollViewHeaderSliversBuilder builder}) { 87 | assert(height > 0); 88 | return (BuildContext context, bool innerBoxIsScrolled) { 89 | var header = [ 90 | _SafeAreaPinnedPlaceholder(placeHolderSize, color: color), 91 | SliverToBoxAdapter( 92 | child: _SafeAreaHeader( 93 | height: height, 94 | placeHolderSize: placeHolderSize, 95 | child: SizedBox( 96 | height: height, 97 | child: headerBuilder(context), 98 | ), 99 | ), 100 | ), 101 | ]; 102 | return builder(context, innerBoxIsScrolled)..insertAll(0, header); 103 | }; 104 | } 105 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: magpie_fly 2 | description: A new flutter plugin project. 3 | version: 0.0.1 4 | author: 5 | homepage: 6 | 7 | environment: 8 | sdk: ">=2.1.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | provider: ^3.1.0+1 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | # For information on the generic Dart part of this file, see the 20 | # following page: https://dart.dev/tools/pub/pubspec 21 | 22 | # The following section is specific to Flutter. 23 | flutter: 24 | # This section identifies this Flutter project as a plugin project. 25 | # The androidPackage and pluginClass identifiers should not ordinarily 26 | # be modified. They are used by the tooling to maintain consistency when 27 | # adding or updating assets for this project. 28 | 29 | # To add assets to your plugin package, add an assets section, like this: 30 | # assets: 31 | # - images/a_dot_burr.jpeg 32 | # - images/a_dot_ham.jpeg 33 | # 34 | # For details regarding assets in packages, see 35 | # https://flutter.dev/assets-and-images/#from-packages 36 | # 37 | # An image asset can refer to one or more resolution-specific "variants", see 38 | # https://flutter.dev/assets-and-images/#resolution-aware. 39 | 40 | # To add custom fonts to your plugin package, add a fonts section here, 41 | # in this "flutter" section. Each entry in this list should have a 42 | # "family" key with the font family name, and a "fonts" key with a 43 | # list giving the asset and other descriptors for the font. For 44 | # example: 45 | # fonts: 46 | # - family: Schyler 47 | # fonts: 48 | # - asset: fonts/Schyler-Regular.ttf 49 | # - asset: fonts/Schyler-Italic.ttf 50 | # style: italic 51 | # - family: Trajan Pro 52 | # fonts: 53 | # - asset: fonts/TrajanPro.ttf 54 | # - asset: fonts/TrajanPro_Bold.ttf 55 | # weight: 700 56 | # 57 | # For details regarding fonts in packages, see 58 | # https://flutter.dev/custom-fonts/#from-packages 59 | --------------------------------------------------------------------------------