├── .DS_Store ├── README.md ├── flutter_wanandroid ├── .gitignore ├── .idea │ ├── dictionaries │ │ └── jakey.xml │ ├── libraries │ │ ├── Dart_Packages.xml │ │ ├── Dart_SDK.xml │ │ └── Flutter_Plugins.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations │ │ └── main_dart.xml │ ├── vcs.xml │ └── workspace.xml ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── jakey │ │ │ │ └── flutterwanandroid │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── assets │ ├── drawer_header_bg.jpg │ └── logo.png ├── flutter_wanandroid.iml ├── flutter_wanandroid_android.iml ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── icon-1024.png │ │ │ ├── icon-20-ipad.png │ │ │ ├── icon-20@2x-ipad.png │ │ │ ├── icon-20@2x.png │ │ │ ├── icon-20@3x.png │ │ │ ├── icon-29-ipad.png │ │ │ ├── icon-29.png │ │ │ ├── icon-29@2x-ipad.png │ │ │ ├── icon-29@2x.png │ │ │ ├── icon-29@3x.png │ │ │ ├── icon-40.png │ │ │ ├── icon-40@2x.png │ │ │ ├── icon-40@3x.png │ │ │ ├── icon-50.png │ │ │ ├── icon-50@2x.png │ │ │ ├── icon-57.png │ │ │ ├── icon-57@2x.png │ │ │ ├── icon-60@2x.png │ │ │ ├── icon-60@3x.png │ │ │ ├── icon-72.png │ │ │ ├── icon-72@2x.png │ │ │ ├── icon-76.png │ │ │ ├── icon-76@2x.png │ │ │ └── icon-83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── adapter │ │ ├── base_adapter.dart │ │ └── feed_item_adapter.dart │ ├── common │ │ ├── application.dart │ │ ├── config.dart │ │ └── wanandroid_api.dart │ ├── home │ │ ├── drawer_page.dart │ │ ├── index_page.dart │ │ ├── main_screen.dart │ │ ├── navi_page.dart │ │ ├── project_page.dart │ │ ├── tree │ │ │ ├── tree_item_tabs_page.dart │ │ │ └── tree_page.dart │ │ └── web_view_page.dart │ ├── main.dart │ ├── model │ │ └── data_model.dart │ ├── notifications │ │ └── notifications.dart │ ├── startup │ │ ├── login_page.dart │ │ └── register_page.dart │ ├── test_page.dart │ ├── utils │ │ ├── dialog_util.dart │ │ ├── http_util.dart │ │ ├── log.dart │ │ ├── shares_util.dart │ │ └── snackbar_util.dart │ └── widgets │ │ ├── pull_to_refresh │ │ ├── pull_to_refresh.dart │ │ └── src │ │ │ ├── indicator │ │ │ └── classic_indicator.dart │ │ │ ├── internals │ │ │ ├── default_constants.dart │ │ │ ├── indicator_config.dart │ │ │ ├── indicator_wrap.dart │ │ │ └── refresh_physics.dart │ │ │ └── smart_refresher.dart │ │ └── xbanner.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart └── raw ├── .DS_Store ├── Screenshot_1.png ├── Screenshot_2.png ├── Screenshot_3.png ├── Screenshot_4.png ├── Screenshot_5.png ├── Screenshot_6.png ├── app-release.apk └── screenrecord.gif /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_wanandroid 2 | Flutter版 [玩Android \- wanandroid\.com \- 每日推荐优质文章](http://www.wanandroid.com/) 网站的移动端练手项目,总体架构完成,还有部分细节未完成。 3 | 4 | # Android apk下载测试 5 | [apk 下载](https://github.com/JakeyYe/flutter_wanandroid/blob/master/raw/app-release.apk),该apk为release版,iOS安装包没有,可以自己下载代码安装测试。 6 | 7 | # 效果演示(Android手机,iOS效果类似) 8 | 9 | 10 | 11 | 12 | 17 | 18 | 23 | 24 | 25 | # 不足之处 26 | 1. ~~`TabBar+TabBarView`布局(最后一张图片的布局)点击Tab快速跳转界面会出现bug,关于此问题官方还为解决[Use TabBarView with AutomaticKeepAliveClientMixin and with 4 or more pages will cause error · Issue \#16502 · flutter/flutter](https://github.com/flutter/flutter/issues/16502);~~(已解决该问题,可查看代码了解解决方案) 27 | 28 | 2. WebView控件支持不是太好,[Inline Android and iOS WebView · Issue \#730 · flutter/flutter](https://github.com/flutter/flutter/issues/730); 29 | 30 | 3. 项目不足之处是并未完全完成:)。 -------------------------------------------------------------------------------- /flutter_wanandroid/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .flutter-plugins 10 | -------------------------------------------------------------------------------- /flutter_wanandroid/.idea/dictionaries/jakey.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /flutter_wanandroid/.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /flutter_wanandroid/.idea/libraries/Flutter_Plugins.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /flutter_wanandroid/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /flutter_wanandroid/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /flutter_wanandroid/.idea/runConfigurations/main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /flutter_wanandroid/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /flutter_wanandroid/.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: 44b7e7d3f42f050a79712daab253af06e9daf530 8 | channel: beta 9 | -------------------------------------------------------------------------------- /flutter_wanandroid/README.md: -------------------------------------------------------------------------------- 1 | # flutter_wanandroid 2 | 3 | A new Flutter project for WanAndroid. 4 | 5 | ## Getting Started 6 | 7 | For help getting started with Flutter, view our online 8 | [documentation](https://flutter.io/). 9 | -------------------------------------------------------------------------------- /flutter_wanandroid/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /key.properties 6 | /.idea/workspace.xml 7 | /.idea/libraries 8 | .DS_Store 9 | /build 10 | /captures 11 | GeneratedPluginRegistrant.java 12 | -------------------------------------------------------------------------------- /flutter_wanandroid/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 | apply plugin: 'com.android.application' 15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 16 | 17 | //需要添加签名文件 18 | def keystorePropertiesFile = rootProject.file("key.properties") 19 | def keystoreProperties = new Properties() 20 | keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) 21 | 22 | android { 23 | compileSdkVersion 27 24 | 25 | lintOptions { 26 | disable 'InvalidPackage' 27 | } 28 | 29 | defaultConfig { 30 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 31 | applicationId "com.jakey.flutterwanandroid" 32 | minSdkVersion 16 33 | targetSdkVersion 27 34 | versionCode 1 35 | versionName "1.0" 36 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 37 | } 38 | 39 | signingConfigs{ 40 | release{ 41 | keyAlias keystoreProperties['keyAlias'] 42 | keyPassword keystoreProperties['keyPassword'] 43 | storeFile file(keystoreProperties['storeFile']) 44 | storePassword keystoreProperties['storePassword'] 45 | } 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.release 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | testImplementation 'junit:junit:4.12' 63 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 64 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 65 | } 66 | -------------------------------------------------------------------------------- /flutter_wanandroid/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /flutter_wanandroid/android/app/src/main/java/com/jakey/flutterwanandroid/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.jakey.flutterwanandroid; 2 | 3 | import android.os.Bundle; 4 | 5 | import io.flutter.app.FlutterActivity; 6 | import io.flutter.plugins.GeneratedPluginRegistrant; 7 | 8 | public class MainActivity extends FlutterActivity { 9 | @Override 10 | protected void onCreate(Bundle savedInstanceState) { 11 | super.onCreate(savedInstanceState); 12 | GeneratedPluginRegistrant.registerWith(this); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /flutter_wanandroid/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /flutter_wanandroid/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter_wanandroid/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /flutter_wanandroid/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /flutter_wanandroid/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.0.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /flutter_wanandroid/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /flutter_wanandroid/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /flutter_wanandroid/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-4.1-all.zip 7 | -------------------------------------------------------------------------------- /flutter_wanandroid/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /flutter_wanandroid/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /flutter_wanandroid/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 | -------------------------------------------------------------------------------- /flutter_wanandroid/assets/drawer_header_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/assets/drawer_header_bg.jpg -------------------------------------------------------------------------------- /flutter_wanandroid/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/assets/logo.png -------------------------------------------------------------------------------- /flutter_wanandroid/flutter_wanandroid.iml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /flutter_wanandroid/flutter_wanandroid_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | UIRequiredDeviceCapabilities 24 | 25 | arm64 26 | 27 | MinimumOSVersion 28 | 8.0 29 | 30 | 31 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | def parse_KV_file(file, separator='=') 8 | file_abs_path = File.expand_path(file) 9 | if !File.exists? file_abs_path 10 | return []; 11 | end 12 | pods_ary = [] 13 | skip_line_start_symbols = ["#", "/"] 14 | File.foreach(file_abs_path) { |line| 15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 16 | plugin = line.split(pattern=separator) 17 | if plugin.length == 2 18 | podname = plugin[0].strip() 19 | path = plugin[1].strip() 20 | podpath = File.expand_path("#{path}", file_abs_path) 21 | pods_ary.push({:name => podname, :path => podpath}); 22 | else 23 | puts "Invalid plugin specification: #{line}" 24 | end 25 | } 26 | return pods_ary 27 | end 28 | 29 | target 'Runner' do 30 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 31 | # referring to absolute paths on developers' machines. 32 | system('rm -rf Pods/.symlinks') 33 | system('mkdir -p Pods/.symlinks/plugins') 34 | 35 | # Flutter Pods 36 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 37 | if generated_xcode_build_settings.empty? 38 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 39 | end 40 | generated_xcode_build_settings.map { |p| 41 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 42 | symlink = File.join('Pods', '.symlinks', 'flutter') 43 | File.symlink(File.dirname(p[:path]), symlink) 44 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 45 | end 46 | } 47 | 48 | # Plugin Pods 49 | plugin_pods = parse_KV_file('../.flutter-plugins') 50 | plugin_pods.map { |p| 51 | symlink = File.join('Pods', '.symlinks', 'plugins', p[:name]) 52 | File.symlink(p[:path], symlink) 53 | pod p[:name], :path => File.join(symlink, 'ios') 54 | } 55 | end 56 | 57 | post_install do |installer| 58 | installer.pods_project.targets.each do |target| 59 | target.build_configurations.each do |config| 60 | config.build_settings['ENABLE_BITCODE'] = 'NO' 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_web_view (0.0.1): 4 | - Flutter 5 | - flutter_webview_plugin (0.0.1): 6 | - Flutter 7 | - path_provider (0.0.1): 8 | - Flutter 9 | - shared_preferences (0.0.1): 10 | - Flutter 11 | 12 | DEPENDENCIES: 13 | - Flutter (from `Pods/.symlinks/flutter/ios`) 14 | - flutter_web_view (from `Pods/.symlinks/plugins/flutter_web_view/ios`) 15 | - flutter_webview_plugin (from `Pods/.symlinks/plugins/flutter_webview_plugin/ios`) 16 | - path_provider (from `Pods/.symlinks/plugins/path_provider/ios`) 17 | - shared_preferences (from `Pods/.symlinks/plugins/shared_preferences/ios`) 18 | 19 | EXTERNAL SOURCES: 20 | Flutter: 21 | :path: Pods/.symlinks/flutter/ios 22 | flutter_web_view: 23 | :path: Pods/.symlinks/plugins/flutter_web_view/ios 24 | flutter_webview_plugin: 25 | :path: Pods/.symlinks/plugins/flutter_webview_plugin/ios 26 | path_provider: 27 | :path: Pods/.symlinks/plugins/path_provider/ios 28 | shared_preferences: 29 | :path: Pods/.symlinks/plugins/shared_preferences/ios 30 | 31 | SPEC CHECKSUMS: 32 | Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 33 | flutter_web_view: 6172ec2a6b4c7a33d840300a74354b913416782e 34 | flutter_webview_plugin: 116575b48572029304775b768e9f15ebfc316274 35 | path_provider: 09407919825bfe3c2deae39453b7a5b44f467873 36 | shared_preferences: 5a1d487c427ee18fcd3ea1f2a131569481834b53 37 | 38 | PODFILE CHECKSUM: 13dcf421f4da2e937a57e8ba760ed880beae536f 39 | 40 | COCOAPODS: 1.4.0 41 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 7 | [GeneratedPluginRegistrant registerWithRegistry:self]; 8 | // Override point for customization after application launch. 9 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 10 | } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "size": "20x20", 5 | "idiom": "iphone", 6 | "filename": "icon-20@2x.png", 7 | "scale": "2x" 8 | }, 9 | { 10 | "size": "20x20", 11 | "idiom": "iphone", 12 | "filename": "icon-20@3x.png", 13 | "scale": "3x" 14 | }, 15 | { 16 | "size": "29x29", 17 | "idiom": "iphone", 18 | "filename": "icon-29.png", 19 | "scale": "1x" 20 | }, 21 | { 22 | "size": "29x29", 23 | "idiom": "iphone", 24 | "filename": "icon-29@2x.png", 25 | "scale": "2x" 26 | }, 27 | { 28 | "size": "29x29", 29 | "idiom": "iphone", 30 | "filename": "icon-29@3x.png", 31 | "scale": "3x" 32 | }, 33 | { 34 | "size": "40x40", 35 | "idiom": "iphone", 36 | "filename": "icon-40@2x.png", 37 | "scale": "2x" 38 | }, 39 | { 40 | "size": "40x40", 41 | "idiom": "iphone", 42 | "filename": "icon-40@3x.png", 43 | "scale": "3x" 44 | }, 45 | { 46 | "size": "57x57", 47 | "idiom": "iphone", 48 | "filename": "icon-57.png", 49 | "scale": "1x" 50 | }, 51 | { 52 | "size": "57x57", 53 | "idiom": "iphone", 54 | "filename": "icon-57@2x.png", 55 | "scale": "2x" 56 | }, 57 | { 58 | "size": "60x60", 59 | "idiom": "iphone", 60 | "filename": "icon-60@2x.png", 61 | "scale": "2x" 62 | }, 63 | { 64 | "size": "60x60", 65 | "idiom": "iphone", 66 | "filename": "icon-60@3x.png", 67 | "scale": "3x" 68 | }, 69 | { 70 | "size": "20x20", 71 | "idiom": "ipad", 72 | "filename": "icon-20-ipad.png", 73 | "scale": "1x" 74 | }, 75 | { 76 | "size": "20x20", 77 | "idiom": "ipad", 78 | "filename": "icon-20@2x-ipad.png", 79 | "scale": "2x" 80 | }, 81 | { 82 | "size": "29x29", 83 | "idiom": "ipad", 84 | "filename": "icon-29-ipad.png", 85 | "scale": "1x" 86 | }, 87 | { 88 | "size": "29x29", 89 | "idiom": "ipad", 90 | "filename": "icon-29@2x-ipad.png", 91 | "scale": "2x" 92 | }, 93 | { 94 | "size": "40x40", 95 | "idiom": "ipad", 96 | "filename": "icon-40.png", 97 | "scale": "1x" 98 | }, 99 | { 100 | "size": "40x40", 101 | "idiom": "ipad", 102 | "filename": "icon-40@2x.png", 103 | "scale": "2x" 104 | }, 105 | { 106 | "size": "50x50", 107 | "idiom": "ipad", 108 | "filename": "icon-50.png", 109 | "scale": "1x" 110 | }, 111 | { 112 | "size": "50x50", 113 | "idiom": "ipad", 114 | "filename": "icon-50@2x.png", 115 | "scale": "2x" 116 | }, 117 | { 118 | "size": "72x72", 119 | "idiom": "ipad", 120 | "filename": "icon-72.png", 121 | "scale": "1x" 122 | }, 123 | { 124 | "size": "72x72", 125 | "idiom": "ipad", 126 | "filename": "icon-72@2x.png", 127 | "scale": "2x" 128 | }, 129 | { 130 | "size": "76x76", 131 | "idiom": "ipad", 132 | "filename": "icon-76.png", 133 | "scale": "1x" 134 | }, 135 | { 136 | "size": "76x76", 137 | "idiom": "ipad", 138 | "filename": "icon-76@2x.png", 139 | "scale": "2x" 140 | }, 141 | { 142 | "size": "83.5x83.5", 143 | "idiom": "ipad", 144 | "filename": "icon-83.5@2x.png", 145 | "scale": "2x" 146 | }, 147 | { 148 | "size": "1024x1024", 149 | "idiom": "ios-marketing", 150 | "filename": "icon-1024.png", 151 | "scale": "1x" 152 | } 153 | ], 154 | "info": { 155 | "version": 1, 156 | "author": "icon.wuruihong.com" 157 | } 158 | } -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20-ipad.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@2x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@2x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-50@2x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-57@2x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-72@2x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-76@2x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/AppIcon.appiconset/icon-83.5@2x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /flutter_wanandroid/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 | -------------------------------------------------------------------------------- /flutter_wanandroid/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 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_wanandroid 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | arm64 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /flutter_wanandroid/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/adapter/base_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | abstract class BaseAdapter{ 4 | Widget getItemView(BuildContext context,item); 5 | } -------------------------------------------------------------------------------- /flutter_wanandroid/lib/adapter/feed_item_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../adapter/base_adapter.dart'; 3 | import '../home/web_view_page.dart'; 4 | 5 | class FeedItemAdapter extends BaseAdapter{ 6 | 7 | @override 8 | Widget getItemView(BuildContext context, item) { 9 | 10 | Color primaryColor = Theme.of(context).primaryColor; 11 | 12 | List spanList = []; 13 | 14 | ///Article Item 中的'项目'标志 15 | if (item['tags'].length > 0) { 16 | String name = item['tags'][0]['name']; 17 | spanList.add( 18 | new TextSpan(text: name, style: new TextStyle(color: primaryColor))); 19 | } 20 | 21 | spanList.addAll([ 22 | new TextSpan(text: ' 作者:${item['author']} '), 23 | new TextSpan( 24 | text: '分类:${item['superChapterName']}/${item['chapterName']} ', 25 | style: new TextStyle(color: primaryColor)), 26 | new TextSpan( 27 | text: '时间:${item['niceDate']}', 28 | style: new TextStyle(color: Colors.grey)), 29 | ]); 30 | 31 | Widget itemAuthors = new Text.rich(new TextSpan(children: spanList)); 32 | 33 | ///TODO 这里请求时若添加cookie,返回值这个'collect'应该就不一样 34 | Widget itemCollect = new IconButton( 35 | icon: item['collect'] 36 | ? const Icon(Icons.favorite) 37 | : const Icon(Icons.favorite_border), 38 | onPressed: () { 39 | print('这里这里 ${item['title']}'); 40 | }); 41 | 42 | Widget itemTitle = 43 | new Text(item['title'], style: new TextStyle(fontSize: 18.0)); 44 | 45 | return new Material( 46 | child: new Column( 47 | children: [ 48 | new InkWell( 49 | onTap: () { 50 | Navigator 51 | .of(context) 52 | .push(new MaterialPageRoute(builder: (context) { 53 | return new WebViewPage( 54 | key: new Key(item['title']), 55 | title: item['title'], 56 | url: item['link']); 57 | })); 58 | }, 59 | child: new Padding( 60 | padding: const EdgeInsets.only(top: 4.0, bottom: 4.0), 61 | child: new Row( 62 | crossAxisAlignment: CrossAxisAlignment.center, 63 | children: [ 64 | new Expanded( 65 | child: new Column( 66 | mainAxisAlignment: MainAxisAlignment.start, 67 | children: [ 68 | new Row( 69 | mainAxisAlignment: MainAxisAlignment.center, 70 | crossAxisAlignment: CrossAxisAlignment.center, 71 | children: [ 72 | new SizedBox( 73 | width: 8.0, 74 | ), 75 | new Expanded(child: itemTitle), 76 | ], 77 | ), 78 | new Row( 79 | mainAxisAlignment: MainAxisAlignment.start, 80 | crossAxisAlignment: CrossAxisAlignment.center, 81 | children: [ 82 | new SizedBox( 83 | width: 8.0, 84 | ), 85 | new Expanded(child: itemAuthors), 86 | ], 87 | ), 88 | ], 89 | ), 90 | ), 91 | new SizedBox( 92 | width: 4.0, 93 | ), 94 | itemCollect, 95 | new SizedBox( 96 | width: 8.0, 97 | ), 98 | ], 99 | ), 100 | )), 101 | new Divider( 102 | height: 1.0, 103 | ), 104 | ], 105 | ), 106 | ); 107 | } 108 | 109 | 110 | } -------------------------------------------------------------------------------- /flutter_wanandroid/lib/common/application.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | ///保存常量 4 | const int timeOut = 5000; 5 | 6 | class Application { 7 | static Widget progressWidget = new Center( 8 | child: new CircularProgressIndicator(), 9 | ); 10 | 11 | static Widget loadMoreWidget = new Row( 12 | mainAxisAlignment: MainAxisAlignment.center, 13 | crossAxisAlignment: CrossAxisAlignment.center, 14 | children: [ 15 | new CircularProgressIndicator(value: 8.0,), 16 | new Text('加载中...',style: new TextStyle(fontSize: 16.0),) 17 | ], 18 | ); 19 | 20 | static Widget noMoreWidget = new Center( 21 | child: new Text('没有更多了:(',style: new TextStyle(fontSize: 16.0),), 22 | ); 23 | 24 | static Widget getLoadMoreFailedWidget({VoidCallback onPressed}) { 25 | return new Row( 26 | mainAxisAlignment: MainAxisAlignment.center, 27 | crossAxisAlignment: CrossAxisAlignment.center, 28 | children: [ 29 | new Icon(Icons.sms_failed), 30 | new FlatButton(onPressed: onPressed, child: new Text('加载失败,点击重试',style: new TextStyle(fontSize: 16.0),)) 31 | ], 32 | ); 33 | } 34 | 35 | static Widget getReloadWidget({VoidCallback onPressed}) { 36 | return new Center( 37 | child: new FlatButton( 38 | onPressed: onPressed, 39 | child: new Text( 40 | '请求数据出错,请点击重试.', 41 | style: new TextStyle( 42 | fontSize: 16.0, 43 | fontStyle: FontStyle.italic, 44 | color: Colors.blue), 45 | )), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/common/config.dart: -------------------------------------------------------------------------------- 1 | 2 | ///将需要引入的基本配置全部声明在该文件中,然后直接引入该文件 3 | ///使用export导出一个更大的库 4 | 5 | export 'package:flutter/material.dart'; 6 | export 'dart:convert'; 7 | export 'dart:async'; 8 | export 'package:meta/meta.dart'; 9 | 10 | export '../utils/snackbar_util.dart'; 11 | export '../utils/dialog_util.dart'; 12 | export '../utils/log.dart'; 13 | export '../utils/http_util.dart'; 14 | export '../utils/shares_util.dart'; 15 | export '../common/wanandroid_api.dart'; 16 | export '../common/application.dart'; -------------------------------------------------------------------------------- /flutter_wanandroid/lib/common/wanandroid_api.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | ///WanAndroid API常量 4 | 5 | ///post 登录 6 | const login_api='http://www.wanandroid.com/user/login'; 7 | 8 | ///post 注册 9 | const register_api='http://www.wanandroid.com/user/register'; 10 | 11 | ///get 首页 banner api 12 | const banner_api='http://www.wanandroid.com/banner/json'; 13 | 14 | ///get 首页文章列表(网站中首页的最新博文),param 是页码,页码从0开始,页码拼接 15 | const home_articles_api='http://www.wanandroid.com/article/list/{param}/json'; 16 | 17 | ///get 搜索热词(大家都在搜) 18 | const search_hotkey_api='http://www.wanandroid.com/hotkey/json'; 19 | 20 | ///post 根据关键字搜索,搜索关键字以空格隔开,多个关键字,将空格换成加号 21 | const search_api='http://www.wanandroid.com/article/query/{param}/json'; 22 | 23 | ///get 体系数据 24 | const tree_api='http://www.wanandroid.com/tree/json'; 25 | 26 | ///get 体系下的文章API 27 | const tree_article_api='http://www.wanandroid.com/article/list/{param}/json?cid={param}'; 28 | 29 | const navi_api='http://www.wanandroid.com/navi/json'; 30 | 31 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/home/drawer_page.dart: -------------------------------------------------------------------------------- 1 | import '../common/config.dart'; 2 | import '../startup/login_page.dart'; 3 | import '../notifications/notifications.dart'; 4 | 5 | ///DrawerHeader 6 | class DrawerHeaderPage extends StatefulWidget { 7 | const DrawerHeaderPage({Key key}) : super(key: key); 8 | 9 | @override 10 | _DrawerHeaderState createState() => new _DrawerHeaderState(); 11 | } 12 | 13 | class _DrawerHeaderState extends State { 14 | ///是否登录 15 | bool isLogin = false; 16 | String username; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | 22 | ///每次打开Drawer都会调用这个方法 23 | print('drawer Page initState'); 24 | 25 | ///去获取cookie值 26 | getCookie().then((cookie) { 27 | if (cookie != null && cookie.isNotEmpty) { 28 | 29 | print('cookie $cookie'); 30 | ///这里解析Cookie 31 | ///loginUserName=JakeyYe; Expires=Wed, 13-Jun-2018 09:13:39 GMT; Path=/ 32 | ///loginUserPassword=ye829225; Expires=Wed, 13-Jun-2018 09:13:39 GMT; Path=/ 33 | if (cookie.contains('loginUserName=')) { 34 | List list = cookie.split(';'); 35 | for (String s in list) { 36 | if (s.contains('loginUserName=')) { 37 | String loginUserName = s.substring('loginUserName'.length + 1); 38 | setState(() { 39 | isLogin = true; 40 | username = loginUserName; 41 | }); 42 | break; 43 | } 44 | } 45 | } 46 | } 47 | }); 48 | } 49 | 50 | Widget getDrawerHeaderWidgets() { 51 | //可以使用通知来刷新这个 52 | 53 | Widget widget; 54 | 55 | ///登录状态 56 | if (isLogin) { 57 | widget = new Stack( 58 | children: [ 59 | new Container( 60 | child: new Row( 61 | mainAxisAlignment: MainAxisAlignment.center, 62 | crossAxisAlignment: CrossAxisAlignment.center, 63 | children: [ 64 | 65 | ///圆形Image 66 | new SizedBox( 67 | 68 | height: 64.0, 69 | width: 64.0, 70 | child: new DecoratedBox( 71 | decoration: new BoxDecoration( 72 | border: new Border.all( 73 | width: 1.0, color: const Color(0xFFFFFFFF)), 74 | shape: BoxShape.circle, 75 | // borderRadius: new BorderRadius.circular(6.0), 76 | image: new DecorationImage( 77 | image: new AssetImage( 78 | 'assets/logo.png', 79 | ), 80 | fit: BoxFit.contain, 81 | alignment: Alignment.center, 82 | ), 83 | ), 84 | ), 85 | ), 86 | 87 | ///圆形图片+用户名(登录状态) 88 | const SizedBox( 89 | width: 12.0, 90 | ), 91 | 92 | ///用户名 93 | Text('JakeyYe') 94 | ], 95 | ), 96 | ), 97 | 98 | ///右下角加一个点击弹出选择框,一项操作'退出登录' 99 | new PositionedDirectional( 100 | bottom: 12.0, 101 | end: 12.0, 102 | child: new GestureDetector( 103 | child: new Icon(Icons.exit_to_app), 104 | onTap: () { 105 | ///弹出弹窗提示用户是否确认退出登录 106 | showAlertDialog(context, '退出登录?').then((value) { 107 | if (value == true) { 108 | ///退出登录操作 109 | setState(() { 110 | isLogin = false; 111 | }); 112 | } 113 | }); 114 | }, 115 | ), 116 | ), 117 | ], 118 | ); 119 | } else { 120 | ///非登录状态 121 | widget = new Container( 122 | child: new Row( 123 | mainAxisAlignment: MainAxisAlignment.center, 124 | crossAxisAlignment: CrossAxisAlignment.center, 125 | children: [ 126 | ///未登录状态的圆形Image,前景加一层灰色 127 | new SizedBox( 128 | ///使用SizedBox来控制大小 129 | height: 64.0, 130 | width: 64.0, 131 | child: new DecoratedBox( 132 | // child: new Material(color: Colors.grey[400]), 133 | decoration: new BoxDecoration( 134 | border: new Border.all( 135 | width: 1.0, color: const Color(0xFFFFFFFF)), 136 | shape: BoxShape.circle, 137 | // borderRadius: new BorderRadius.circular(6.0), 138 | image: new DecorationImage( 139 | image: new AssetImage( 140 | 'assets/logo.png', 141 | ), 142 | fit: BoxFit.contain, 143 | alignment: Alignment.center, 144 | ), 145 | ), 146 | ), 147 | 148 | ///这个装饰是作为背景的,当然也可以设置为前景 149 | ), 150 | 151 | ///圆形图片+用户名(登录状态) 152 | const SizedBox( 153 | width: 12.0, 154 | ), 155 | FlatButton( 156 | child: Text( 157 | '去登陆', 158 | style: new TextStyle(color: Colors.blue, fontSize: 16.0), 159 | ), 160 | onPressed: () { 161 | Navigator.push(context, 162 | new MaterialPageRoute(builder: (context) { 163 | return new LoginPage(); 164 | })); 165 | }, 166 | ) 167 | ], 168 | ), 169 | ); 170 | } 171 | return widget; 172 | } 173 | 174 | @override 175 | Widget build(BuildContext context) { 176 | print('drawer page build'); 177 | 178 | ///DrawerHeader有两种状态,登录状态和未登录状态 179 | 180 | ///NotificationListener通知 181 | return new NotificationListener( 182 | onNotification: (notification){ 183 | isLogin=true; 184 | }, 185 | child: new DrawerHeader( 186 | //这个margin是 187 | margin: EdgeInsets.only(top: 0.0, bottom: 12.0), 188 | 189 | ///背景图片 190 | // decoration: new BoxDecoration(color: Colors.amberAccent), 191 | decoration: BoxDecoration( 192 | image: new DecorationImage( 193 | fit: BoxFit.fill, 194 | image: new AssetImage('assets/drawer_header_bg.jpg'))), 195 | duration: const Duration(milliseconds: 750), 196 | child: getDrawerHeaderWidgets(), 197 | )); 198 | } 199 | } 200 | 201 | ///Drawer,由两部分组成,顶部用户信息和下面的条目 202 | class DrawerPage extends StatefulWidget { 203 | final GlobalKey scaffoldKey; 204 | 205 | DrawerPage({@required this.scaffoldKey}); 206 | 207 | @override 208 | _DrawerPageState createState() => new _DrawerPageState(); 209 | } 210 | 211 | class _DrawerPageState extends State { 212 | static const List _drawerContents = const ['收藏', '设置', '关于']; 213 | 214 | @override 215 | Widget build(BuildContext context) { 216 | final List allDrawerItems = [ 217 | new DrawerHeaderPage(), 218 | 219 | ///要使用这个MediaQuery.removePadding()才可以将Drawer的布局绘制到statusBar上 220 | new MediaQuery.removePadding( 221 | context: context, 222 | removeTop: true, 223 | child: new Expanded( 224 | child: new ListView( 225 | children: [_getDrawerItems()], 226 | ))) 227 | ]; 228 | 229 | ///这里直接使用Drawer+ListView 230 | return new Drawer( 231 | child: new Column( 232 | children: allDrawerItems, 233 | )); 234 | } 235 | 236 | void _showNotImplementedMessage(String item) { 237 | Navigator.pop(context); // Dismiss the drawer. 238 | widget.scaffoldKey.currentState.showSnackBar(new SnackBar( 239 | content: new Text('Drawer item: $item'))); 240 | } 241 | 242 | Widget _buildListTile(String title, Widget icon, GestureTapCallback onTap) { 243 | return new ListTile( 244 | leading: new CircleAvatar(child: icon), 245 | title: new Text(title), 246 | onTap: onTap, 247 | ); 248 | } 249 | 250 | _getDrawerItems() { 251 | return new Column( 252 | mainAxisSize: MainAxisSize.min, 253 | crossAxisAlignment: CrossAxisAlignment.stretch, 254 | children: [ 255 | new Divider( 256 | height: 2.0, 257 | ), 258 | _buildListTile(_drawerContents[0], new Icon(Icons.favorite), () { 259 | _showNotImplementedMessage('收藏'); 260 | }), 261 | new Divider( 262 | height: 2.0, 263 | ), 264 | _buildListTile(_drawerContents[1], new Icon(Icons.settings), () { 265 | _showNotImplementedMessage('设置'); 266 | }), 267 | new Divider( 268 | height: 2.0, 269 | ), 270 | _buildListTile(_drawerContents[2], new Icon(Icons.description), () { 271 | _showNotImplementedMessage('关于'); 272 | }), 273 | ], 274 | ); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/home/index_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import '../widgets/pull_to_refresh/pull_to_refresh.dart'; 3 | 4 | import '../common/config.dart'; 5 | import '../home/web_view_page.dart'; 6 | import '../widgets/xbanner.dart'; 7 | import '../adapter/feed_item_adapter.dart'; 8 | 9 | class Banner extends StatefulWidget { 10 | Banner({@required Key key}) : super(key: key); 11 | 12 | @override 13 | _BannerState createState() => new _BannerState(); 14 | } 15 | 16 | class _BannerState extends State { 17 | ///有没有点击重新加载的第三种情况??? 18 | bool loading = true; 19 | bool isReload = false; 20 | List data; 21 | 22 | /// "desc":"", 23 | /// "id":4, 24 | /// "imagePath":"http://www.wanandroid.com/blogimgs/ab17e8f9-6b79-450b-8079-0f2287eb6f0f.png", 25 | /// "isVisible":1, 26 | /// "order":0, 27 | /// "title":"看看别人的面经,搞定面试~", 28 | /// "type":1, 29 | /// "url":"http://www.wanandroid.com/article/list/0?cid=73" 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | print('Banner initState'); 35 | _loadDataBanner(); 36 | } 37 | 38 | _loadDataBanner() async { 39 | print('_loadData'); 40 | await HttpUtil.get(banner_api).then((dataModel) { 41 | if (!mounted) return; 42 | 43 | ///这个errorCode只有两种情况 44 | if (dataModel.errorCode == 0) { 45 | data = dataModel.data; 46 | setState(() { 47 | loading = false; 48 | isReload = false; 49 | }); 50 | } else { 51 | setState(() { 52 | loading = false; 53 | isReload = true; 54 | }); 55 | } 56 | }); 57 | } 58 | 59 | _buildBanner() { 60 | return new XBanner( 61 | data.map((obj) { 62 | ///TODO 这里有缺陷,还需要更改??? 63 | return new DecoratedBox( 64 | decoration: new BoxDecoration( 65 | image: new DecorationImage( 66 | ///对齐方式,如果不指定,默认就是Center方式 67 | alignment: Alignment.topLeft, 68 | 69 | ///这个才是按原来的比例缩放展示 70 | fit: BoxFit.fill, 71 | image: new CachedNetworkImageProvider(obj['imagePath']), 72 | ), 73 | ), 74 | child: new Container( 75 | ///这里设置宽度无效 76 | margin: EdgeInsets.only(bottom: 16.0), 77 | 78 | child: new Align( 79 | child: new SizedBox( 80 | height: 36.0, 81 | width: double.infinity, 82 | child: new DecoratedBox( 83 | decoration: new BoxDecoration(color: Colors.black45), 84 | child: new Align( 85 | child: new Padding( 86 | padding: const EdgeInsets.only(left: 8.0), 87 | child: new Text( 88 | obj['title'], 89 | style: new TextStyle(color: Colors.white), 90 | ), 91 | ), 92 | alignment: Alignment.centerLeft, 93 | ), 94 | ), 95 | ), 96 | alignment: Alignment.bottomLeft, 97 | ), 98 | )); 99 | }).toList(), 100 | pageClick: (i) { 101 | print('click $i ${data[i]['title']} ${data[i]['url']}'); 102 | 103 | ///TODO 这里跳转也会重建 104 | Navigator.of(context).push(new MaterialPageRoute(builder: (context) { 105 | return new WebViewPage( 106 | key: new Key('WebViewPage'), 107 | title: data[i]['title'], 108 | url: data[i]['url']); 109 | })); 110 | }, 111 | ); 112 | } 113 | 114 | _buildBannerBody() { 115 | Widget body; 116 | if (loading == true) { 117 | body = Application.progressWidget; 118 | } else { 119 | ///获取数据出错,需要重新加载 120 | if (isReload) { 121 | body = Application.getReloadWidget(onPressed: () { 122 | if (!mounted) return; 123 | 124 | setState(() { 125 | loading = true; 126 | isReload = false; 127 | }); 128 | _loadDataBanner(); 129 | }); 130 | } else { 131 | body = _buildBanner(); 132 | } 133 | } 134 | return body; 135 | } 136 | 137 | @override 138 | Widget build(BuildContext context) { 139 | print('Banner build'); 140 | 141 | ///这个高度可以控制Banner的高度 142 | return new SizedBox( 143 | height: 200.0, 144 | child: _buildBannerBody(), 145 | ); 146 | } 147 | } 148 | 149 | class ArticleList extends StatefulWidget { 150 | ArticleList({@required Key key}) : super(key: key); 151 | 152 | @override 153 | _ArticleListState createState() => new _ArticleListState(); 154 | } 155 | 156 | class _ArticleListState extends State { 157 | bool loading = true; 158 | bool isReload = false; 159 | List data; 160 | 161 | ///是否还有更多 162 | bool hasMore = true; 163 | 164 | ///Banner 165 | bool loadingBanner = true; 166 | bool isReloadBanner = false; 167 | List bannerData; 168 | 169 | ///当前页数 170 | int curPage = 0; 171 | int curPageSize = 0; 172 | 173 | RefreshController _refreshController; 174 | 175 | FeedItemAdapter _feedItemAdapter; 176 | 177 | /// "apkLink":"", 178 | /// "author":"HIT-Alibaba", 179 | /// "chapterId":73, 180 | /// "chapterName":"面试相关", 181 | /// "collect":false, 182 | /// "courseId":13, 183 | /// "desc":"", 184 | /// "envelopePic":"", 185 | /// "fresh":true, 186 | /// "id":2945, 187 | /// "link":"https://hit-alibaba.github.io/interview/basic/network/HTTP.html", 188 | /// "niceDate":"1天前", 189 | /// "origin":"", 190 | /// "projectLink":"", 191 | /// "publishTime":1526988545000, 192 | /// "superChapterId":61, 193 | /// "superChapterName":"热门专题", 194 | /// "tags":[ 195 | /// 196 | /// ], 197 | /// "title":"笔试面试知识整理", 198 | /// "type":0, 199 | /// "userId":-1, 200 | /// "visible":1, 201 | /// "zan":0 202 | 203 | _loadDataBanner() async { 204 | print('_loadDataBanner'); 205 | 206 | await HttpUtil.get(banner_api).then((dataModel) { 207 | if (!mounted) return; 208 | 209 | ///这个errorCode只有两种情况 210 | if (dataModel.errorCode == 0) { 211 | bannerData = dataModel.data; 212 | setState(() { 213 | loadingBanner = false; 214 | isReloadBanner = false; 215 | }); 216 | } else { 217 | setState(() { 218 | loadingBanner = false; 219 | isReloadBanner = true; 220 | }); 221 | } 222 | }); 223 | } 224 | 225 | _buildBanner() { 226 | print('_buildBanner'); 227 | 228 | return new XBanner( 229 | bannerData.map((obj) { 230 | return new DecoratedBox( 231 | decoration: new BoxDecoration( 232 | image: new DecorationImage( 233 | ///对齐方式,如果不指定,默认就是Center方式 234 | alignment: Alignment.topLeft, 235 | 236 | ///这个才是按原来的比例缩放展示 237 | fit: BoxFit.fill, 238 | image: new CachedNetworkImageProvider(obj['imagePath']), 239 | ), 240 | ), 241 | child: new Container( 242 | margin: EdgeInsets.only(bottom: 16.0), 243 | child: new Align( 244 | child: new SizedBox( 245 | height: 36.0, 246 | width: double.infinity, 247 | child: new DecoratedBox( 248 | decoration: new BoxDecoration(color: Colors.black45), 249 | child: new Align( 250 | child: new Padding( 251 | padding: const EdgeInsets.only(left: 8.0), 252 | child: new Text( 253 | obj['title'], 254 | style: new TextStyle(color: Colors.white), 255 | ), 256 | ), 257 | alignment: Alignment.centerLeft, 258 | ), 259 | ), 260 | ), 261 | alignment: Alignment.bottomLeft, 262 | ), 263 | )); 264 | }).toList(), 265 | pageClick: (i) { 266 | print('click $i ${bannerData[i]['title']} ${bannerData[i]['url']}'); 267 | Navigator.of(context).push(new MaterialPageRoute(builder: (context) { 268 | return new WebViewPage( 269 | 270 | ///TODO 这样是不是不会重新创建同一个页面了 271 | key: new Key(bannerData[i]['title']), 272 | title: bannerData[i]['title'], 273 | url: bannerData[i]['url']); 274 | })); 275 | }, 276 | ); 277 | } 278 | 279 | _buildBannerBody() { 280 | print('_buildBannerBody'); 281 | 282 | Widget body; 283 | if (loadingBanner == true) { 284 | body = Application.progressWidget; 285 | } else { 286 | ///获取数据出错,需要重新加载 287 | if (isReloadBanner) { 288 | body = Application.getReloadWidget(onPressed: () { 289 | if (!mounted) return; 290 | 291 | setState(() { 292 | loadingBanner = true; 293 | isReloadBanner = false; 294 | }); 295 | _loadDataBanner(); 296 | }); 297 | } else { 298 | body = _buildBanner(); 299 | } 300 | } 301 | return body; 302 | } 303 | 304 | @override 305 | void initState() { 306 | super.initState(); 307 | _refreshController = new RefreshController(); 308 | _feedItemAdapter = new FeedItemAdapter(); 309 | 310 | print('IndexPage initState'); 311 | 312 | ///Banner data 313 | _loadDataBanner(); 314 | 315 | ///ArticleList data 316 | _loadData(); 317 | } 318 | 319 | _loadData() async { 320 | print('_loadData'); 321 | 322 | await HttpUtil.get(home_articles_api, [curPage]).then((dataModel) { 323 | if (!mounted) return; 324 | 325 | ///这个errorCode只有两种情况 326 | if (dataModel.errorCode == 0) { 327 | curPage++; 328 | 329 | ///判断是否还有更多数据 330 | if (dataModel.data['curPage'] < dataModel.data['pageCount']) { 331 | hasMore = true; 332 | } else { 333 | hasMore = false; 334 | } 335 | 336 | if (data != null) { 337 | data.addAll(dataModel.data['datas']); 338 | curPageSize += dataModel.data['size']; 339 | 340 | _refreshController.sendBack(false, RefreshStatus.idle); 341 | } else { 342 | data = dataModel.data['datas']; 343 | curPageSize = dataModel.data['size']; 344 | } 345 | 346 | setState(() { 347 | loading = false; 348 | isReload = false; 349 | }); 350 | } else { 351 | if (data != null) { 352 | _refreshController.sendBack(false, RefreshStatus.failed); 353 | } else { 354 | setState(() { 355 | loading = false; 356 | isReload = true; 357 | }); 358 | } 359 | } 360 | }); 361 | } 362 | 363 | _buildBody() { 364 | print('_buildBody'); 365 | 366 | Widget body; 367 | if (loading == true) { 368 | body = Application.progressWidget; 369 | } else { 370 | ///获取数据出错,需要重新加载 371 | if (isReload) { 372 | body = Application.getReloadWidget(onPressed: () { 373 | if (!mounted) return; 374 | 375 | setState(() { 376 | loading = true; 377 | isReload = false; 378 | }); 379 | _loadData(); 380 | }); 381 | } else { 382 | ///TODO 这里ListView会在一开始将所有Item都加载出来 383 | body = new SmartRefresher( 384 | enablePullUp: true, 385 | enablePullDown: false, 386 | controller: _refreshController, 387 | onRefresh: _onRefresh, 388 | footerBuilder: _footerCreate, 389 | child: new ListView.builder( 390 | physics: const NeverScrollableScrollPhysics(), 391 | shrinkWrap: true, 392 | itemCount: curPageSize + 1, 393 | itemBuilder: (context, index) { 394 | print('index $index'); 395 | 396 | if (index == 0) { 397 | ///这里没直接使用new Banner()是有原因的,如果使用new Banner()就会在Banner 398 | ///消失再出现时返回创建Banner,Banner的流程都会走一遍 399 | // return new Banner(); 400 | ///这个高度可以控制Banner的高度 401 | return new SizedBox( 402 | height: 200.0, 403 | child: _buildBannerBody(), 404 | ); 405 | } else { 406 | return _feedItemAdapter.getItemView( 407 | context, data[index - 1]); 408 | } 409 | })); 410 | } 411 | } 412 | return body; 413 | } 414 | 415 | Widget _footerCreate(BuildContext context, int mode) { 416 | return new ClassicIndicator( 417 | mode: mode, 418 | refreshingText: 'loading...', 419 | idleIcon: const Icon(Icons.arrow_upward), 420 | idleText: '上拉加载更多...', 421 | ); 422 | } 423 | 424 | ///无论顶部还是底部的指示器,当进入刷新状态,onRefresh都会被回调 425 | _onRefresh(bool up) { 426 | if (!up) { 427 | if (hasMore) { 428 | _loadData(); 429 | }else{ 430 | _refreshController.sendBack(false, RefreshStatus.noMore); 431 | } 432 | } 433 | } 434 | 435 | @override 436 | Widget build(BuildContext context) { 437 | print('Article build'); 438 | return _buildBody(); 439 | } 440 | } 441 | 442 | class IndexPage extends StatefulWidget { 443 | final GlobalKey scaffoldKey; 444 | 445 | IndexPage({@required Key key, @required this.scaffoldKey}) : super(key: key); 446 | 447 | @override 448 | _IndexPageState createState() => new _IndexPageState(); 449 | } 450 | 451 | class _IndexPageState extends State { 452 | @override 453 | Widget build(BuildContext context) { 454 | return new Container( 455 | decoration: new BoxDecoration( 456 | color: Colors.grey[100], 457 | ), 458 | child: new ArticleList( 459 | key: new Key('IndexPage'), 460 | ), 461 | ); 462 | } 463 | } 464 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/home/main_screen.dart: -------------------------------------------------------------------------------- 1 | import '../common/config.dart'; 2 | import '../home/drawer_page.dart'; 3 | import '../home/index_page.dart'; 4 | import '../home/navi_page.dart'; 5 | import '../home/project_page.dart'; 6 | import '../home/tree/tree_page.dart'; 7 | 8 | enum _NavigationItemName { indexs, tree, navi, project } 9 | 10 | class NavigationIconView { 11 | NavigationIconView({ 12 | Widget icon, 13 | String title, 14 | Color color, 15 | _NavigationItemName name, 16 | }): 17 | _name = name, 18 | item = new BottomNavigationBarItem( 19 | 20 | ///BottomNavigationBarItem,底部导航栏的Item 21 | icon: icon, 22 | title: new Text(title), 23 | backgroundColor: color, 24 | ); 25 | 26 | final BottomNavigationBarItem item; 27 | _NavigationItemName _name; 28 | Widget page; 29 | 30 | Widget build(BuildContext context, GlobalKey scaffoldKey) { 31 | if (page != null) { 32 | return page; 33 | } 34 | switch (_name) { 35 | case _NavigationItemName.indexs: 36 | page = new IndexPage( 37 | scaffoldKey: scaffoldKey, 38 | key: new Key(_name.toString()), 39 | ); 40 | break; 41 | case _NavigationItemName.tree: 42 | page = new TreePage( 43 | scaffoldKey: scaffoldKey, 44 | key: new Key(_name.toString()), 45 | ); 46 | break; 47 | case _NavigationItemName.navi: 48 | page = new NaviPage( 49 | scaffoldKey: scaffoldKey, 50 | key: new Key(_name.toString()), 51 | ); 52 | break; 53 | case _NavigationItemName.project: 54 | page = new ProjectPage( 55 | key: new Key(_name.toString()), 56 | scaffoldKey: scaffoldKey); 57 | break; 58 | default: 59 | //error 60 | print('error $_name'); 61 | } 62 | return page; 63 | } 64 | } 65 | 66 | ///主界面 67 | class MainScreen extends StatefulWidget { 68 | @override 69 | _MainScreenState createState() => new _MainScreenState(); 70 | } 71 | 72 | ///TODO AutomaticKeepAliveClientMixin 对IndexPage跳转WebViewPage会调用IndexPage的build方法没效果 73 | class _MainScreenState extends State{ 74 | 75 | int _currentIndex = 0; 76 | 77 | ///底部导航栏的类型 78 | BottomNavigationBarType _type = BottomNavigationBarType.fixed; 79 | 80 | ///导航元素 81 | List _navigationViews; 82 | 83 | ///对应导航元素的页面 84 | List pages = []; 85 | 86 | List _titleList = ['首页', '体系', '导航', '项目']; 87 | 88 | ///GlobalKey 全局Key,使用全局Key来唯一标志widget 89 | ///可以通过GlobalKey.currentState获取当前key对应的Widget的状态 90 | final GlobalKey _scaffoldKey = new GlobalKey(); 91 | 92 | 93 | @override 94 | void initState() { 95 | super.initState(); 96 | ///TODO Dart项目中的StackTrance和Flutter中的不一样,所以需要不同分析 97 | Log.d(StackTrace.current, 'Log initState'); 98 | } 99 | 100 | Widget _buildNavigationBar(BuildContext context) { 101 | if (_navigationViews == null || _navigationViews.length == 0) { 102 | _navigationViews = [ 103 | new NavigationIconView( 104 | icon: const Icon(Icons.home), 105 | title: _titleList[0], 106 | color: Colors.blue, 107 | name: _NavigationItemName.indexs 108 | ), 109 | new NavigationIconView( 110 | icon: const Icon(Icons.extension), 111 | title: _titleList[1], 112 | color: Colors.blue, 113 | name: _NavigationItemName.tree 114 | ), 115 | new NavigationIconView( 116 | icon: const Icon(Icons.navigation), 117 | title: _titleList[2], 118 | color: Colors.blue, 119 | name: _NavigationItemName.navi 120 | ), 121 | new NavigationIconView( 122 | icon: const Icon(Icons.collections_bookmark), 123 | title: _titleList[3], 124 | color: Colors.blue, 125 | name: _NavigationItemName.project 126 | ) 127 | ]; 128 | } 129 | 130 | return new BottomNavigationBar( 131 | items: _navigationViews 132 | .map((NavigationIconView navigationView) => navigationView.item) 133 | .toList(), 134 | currentIndex: _currentIndex, 135 | type: _type, 136 | onTap: (int index) { 137 | ///ValueChanged 138 | if (index != _currentIndex) { 139 | print('BottomNavigationBar onTap $index'); 140 | setState(() { 141 | _currentIndex = index; 142 | }); 143 | } 144 | }, 145 | ); 146 | } 147 | 148 | Widget _buildPage(BuildContext context, 149 | GlobalKey scaffoldKey) { 150 | 151 | print('_buildPage $_currentIndex'); 152 | 153 | _navigationViews[_currentIndex].build(context, scaffoldKey); 154 | 155 | _navigationViews.forEach((NavigationIconView item) { 156 | if (!(pages.indexOf(item.page) >= 0) && item.page != null) { 157 | pages.add(item.page); 158 | } 159 | }); 160 | 161 | ///IndexedStack可以直接控制那个Widget在最上面 162 | ///这里如果使用IndexedStack的话就不能使用懒加载PageView了 163 | // return new IndexedStack( 164 | // index: _currentIndex, 165 | // children:pages, 166 | // ); 167 | 168 | ///相当于List重新排序 169 | pages 170 | ..removeWhere((Widget page) => page == _navigationViews[_currentIndex].page) 171 | ..add(_navigationViews[_currentIndex].page); 172 | 173 | return new Stack( 174 | children: pages, 175 | ); 176 | } 177 | 178 | @override 179 | Widget build(BuildContext context) { 180 | print('build $_currentIndex'); 181 | 182 | Widget botNavBar = _buildNavigationBar(context); 183 | 184 | String appBarTitle; 185 | if(_currentIndex==0){ 186 | appBarTitle='WanAndroid'; 187 | }else{ 188 | appBarTitle=_titleList[_currentIndex]; 189 | } 190 | 191 | return new Scaffold( 192 | key: _scaffoldKey, 193 | 194 | ///这里只切换了AppBar和BottomNavigationBar之间的布局 195 | appBar: new AppBar( 196 | title: new Text(appBarTitle), 197 | ), 198 | 199 | drawer: new DrawerPage(scaffoldKey: _scaffoldKey), 200 | ///BottomNavigationBar模式的,使用Stack作为页面的容器,为每个节目传入一个Key的话,这样 201 | ///随着底部导航栏切换页面的时候就不会每次都重建页面了 202 | body: _buildPage(context, _scaffoldKey), 203 | 204 | ///这里界面只是Stack中的一层布局,所以要求每次背景都不能是透明的,否则界面将穿透 205 | 206 | ///这里好像调用了几次 207 | bottomNavigationBar: botNavBar, 208 | ); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/home/navi_page.dart: -------------------------------------------------------------------------------- 1 | import '../common/config.dart'; 2 | import '../home/web_view_page.dart'; 3 | import 'package:rect_getter/rect_getter.dart'; 4 | import 'package:after_layout/after_layout.dart'; 5 | 6 | class _ChipsTile extends StatelessWidget { 7 | const _ChipsTile({ 8 | Key key, 9 | this.label, 10 | this.children, 11 | }) : super(key: key); 12 | 13 | final String label; 14 | final List children; 15 | 16 | // Wraps a list of chips into a ListTile for display as a section in the demo. 17 | @override 18 | Widget build(BuildContext context) { 19 | return new ListTile( 20 | title: new Padding( 21 | padding: const EdgeInsets.only(top: 16.0, bottom: 4.0), 22 | child: new Text(label, textAlign: TextAlign.start), 23 | ), 24 | subtitle: new Wrap( 25 | children: children 26 | .map((Widget chip) => new Padding( 27 | padding: const EdgeInsets.all(4.0), 28 | child: chip, 29 | )) 30 | .toList(), 31 | ), 32 | ); 33 | } 34 | } 35 | 36 | ///当前界面关于这个问题 https://github.com/flutter/flutter/issues/12319 37 | 38 | class NaviPage extends StatefulWidget { 39 | final GlobalKey scaffoldKey; 40 | 41 | NaviPage({@required Key key, @required this.scaffoldKey}) : super(key: key); 42 | 43 | @override 44 | _NaviPageState createState() => new _NaviPageState(); 45 | } 46 | 47 | class _NaviPageState extends State with AfterLayoutMixin { 48 | bool loading = true; 49 | bool isReload = false; 50 | List data; 51 | int itemSize; 52 | 53 | ///左边列表数据 54 | List leftList = []; 55 | 56 | int selectedIndex = 0; 57 | 58 | ScrollController _scrollController; 59 | 60 | List rightListKey = []; 61 | 62 | List rightListItemTop = []; 63 | 64 | @override 65 | void initState() { 66 | super.initState(); 67 | _scrollController = new ScrollController(); 68 | _loadData(); 69 | } 70 | 71 | _loadData() async { 72 | print('NaviPage _loadData'); 73 | 74 | await HttpUtil 75 | .get( 76 | navi_api, 77 | ) 78 | .then((dataModel) { 79 | if (!mounted) return; 80 | 81 | ///这个errorCode只有两种情况 82 | if (dataModel.errorCode == 0) { 83 | data = dataModel.data; 84 | itemSize = data.length; 85 | print(itemSize); 86 | 87 | for (int i = 0; i < itemSize; i++) { 88 | leftList.add(data[i]['name']); 89 | } 90 | 91 | setState(() { 92 | loading = false; 93 | isReload = false; 94 | }); 95 | } else { 96 | setState(() { 97 | loading = false; 98 | isReload = true; 99 | }); 100 | } 101 | }); 102 | } 103 | 104 | _buildBody() { 105 | print('NaviPage _buildBody'); 106 | Widget body; 107 | if (loading == true) { 108 | body = Application.progressWidget; 109 | } else { 110 | ///获取数据出错,需要重新加载 111 | if (isReload) { 112 | body = Application.getReloadWidget(onPressed: () { 113 | if (!mounted) return; 114 | 115 | setState(() { 116 | loading = true; 117 | isReload = false; 118 | }); 119 | _loadData(); 120 | }); 121 | } else { 122 | body = new Row( 123 | children: [_buildLeftList(), _buildRightList()], 124 | ); 125 | } 126 | } 127 | return body; 128 | } 129 | 130 | _buildLeftList() { 131 | print('NaviPage _buildLeftList'); 132 | 133 | return new Expanded( 134 | child: new NotificationListener( 135 | onNotification: (onNotification) { 136 | ///在布局完成的第一次操作前完成Widget信息的获取 137 | if (rightListItemTop.isEmpty) { 138 | for (int i = 0; i < rightListKey.length; i++) { 139 | var rect = RectGetter.getRectFromKey(rightListKey[i]); 140 | rightListItemTop.add(rect.top); 141 | } 142 | } 143 | }, 144 | child: new ListView.builder( 145 | itemCount: leftList.length, 146 | itemBuilder: (context, index) { 147 | return new Column( 148 | mainAxisAlignment: MainAxisAlignment.center, 149 | crossAxisAlignment: CrossAxisAlignment.center, 150 | children: [ 151 | new SizedBox( 152 | height: 48.0, 153 | width: double.infinity, 154 | child: new InkWell( 155 | onTap: () { 156 | ///在布局完成的第一次操作前完成Widget信息的获取 157 | if (rightListItemTop.isEmpty) { 158 | for (int i = 0; i < rightListKey.length; i++) { 159 | var rect = 160 | RectGetter.getRectFromKey(rightListKey[i]); 161 | rightListItemTop.add(rect.top); 162 | } 163 | } 164 | 165 | if (selectedIndex != index) { 166 | selectedIndex = index; 167 | ///调用这里就可以设置右边滑动 168 | _scrollController.jumpTo( 169 | rightListItemTop[selectedIndex] - 170 | rightListItemTop[0]); 171 | ///调用这里设置左边Item切换 172 | setState(() {}); 173 | } 174 | }, 175 | 176 | ///设置Item选中的背景颜色 177 | child: new Container( 178 | color: selectedIndex == index 179 | ? Colors.grey[300] 180 | : Colors.transparent, 181 | child: new Center( 182 | child: new Text( 183 | leftList[index], 184 | style: new TextStyle(fontSize: 16.0), 185 | )), 186 | ), 187 | ), 188 | ), 189 | new Divider( 190 | height: 1.0, 191 | ), 192 | ]); 193 | })), 194 | ); 195 | } 196 | 197 | _buildRightList() { 198 | print('_NaviPage _buildRightList'); 199 | 200 | rightListKey.clear(); 201 | 202 | List children = []; 203 | 204 | for (int i = 0; i < leftList.length; i++) { 205 | Map itemRightList = new Map(); 206 | 207 | var articles = data[i]['articles']; 208 | for (int i = 0; i < articles.length; i++) { 209 | itemRightList.putIfAbsent( 210 | articles[i]['title'], () => articles[i]['link']); 211 | } 212 | 213 | final List actionChips = itemRightList.keys.map( 214 | (String name) { 215 | return new ActionChip( 216 | key: new ValueKey(name), 217 | label: new Text(name), 218 | onPressed: () { 219 | Navigator 220 | .of(context) 221 | .push(new MaterialPageRoute(builder: (context) { 222 | return new WebViewPage( 223 | key: new Key(name), title: name, url: itemRightList[name]); 224 | })); 225 | }, 226 | ); 227 | }, 228 | ).toList(); 229 | 230 | ///将这个globalKey收集起来,后面获取Widget的信息 231 | var globalKey = RectGetter.createGlobalKey(); 232 | 233 | rightListKey.add(globalKey); 234 | 235 | children.add(new RectGetter( 236 | key: globalKey, 237 | child: new GestureDetector( 238 | onTap: () { 239 | ///在布局完成的第一次操作前完成Widget信息的获取 240 | if (rightListItemTop.isEmpty) { 241 | for (int i = 0; i < rightListKey.length; i++) { 242 | var rect = RectGetter.getRectFromKey(rightListKey[i]); 243 | rightListItemTop.add(rect.top); 244 | } 245 | } 246 | }, 247 | child: new _ChipsTile( 248 | label: leftList[i], 249 | children: actionChips, 250 | ), 251 | ))); 252 | } 253 | 254 | return new Expanded( 255 | flex: 2, 256 | child: new NotificationListener( 257 | onNotification: (notification) { 258 | ///在布局完成的第一次操作前完成Widget信息的获取 259 | if (rightListItemTop.isEmpty) { 260 | for (int i = 0; i < rightListKey.length; i++) { 261 | var rect = RectGetter.getRectFromKey(rightListKey[i]); 262 | rightListItemTop.add(rect.top); 263 | } 264 | } 265 | }, 266 | 267 | ///直接使用new ListView,children就会在之前全部被创建 268 | child: new SingleChildScrollView( 269 | controller: _scrollController, 270 | child: new Column( 271 | children: children, 272 | ), 273 | ))); 274 | } 275 | 276 | @override 277 | Widget build(BuildContext context) { 278 | print('NaviPage build'); 279 | return new Container( 280 | decoration: new BoxDecoration( 281 | color: Colors.grey[100], 282 | ), 283 | child: new Material( 284 | child: _buildBody(), 285 | ), 286 | ); 287 | } 288 | 289 | ///TODO 这个方法这里不太好使,因为第一次布局一定是进度条布局,真正的布局至少是在第二次 290 | @override 291 | void afterFirstLayout(BuildContext context) { 292 | // TODO: implement afterFirstLayout 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/home/project_page.dart: -------------------------------------------------------------------------------- 1 | import '../common/config.dart'; 2 | 3 | ///ProjectPage可以直接使用tree_item_tab_page来代替 4 | class ProjectPage extends StatefulWidget { 5 | final GlobalKey scaffoldKey; 6 | 7 | ProjectPage({@required Key key, @required this.scaffoldKey}) 8 | : super(key: key); 9 | 10 | @override 11 | _ProjectPageState createState() => new _ProjectPageState(); 12 | } 13 | 14 | class _ProjectPageState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | return new Container( 18 | decoration: new BoxDecoration( 19 | color: Colors.grey[100], 20 | ), 21 | child: new Center( 22 | child: new Column( 23 | crossAxisAlignment: CrossAxisAlignment.center, 24 | children: [ 25 | new Text('项目页面',style: new TextStyle(fontSize: 18.0),), 26 | new Padding( 27 | padding: const EdgeInsets.all(8.0), 28 | child: new Text('与前面一个页面(结构TabBar+TabBarView)相似,故该页面不实现了',style: new TextStyle(fontSize: 16.0),), 29 | ) 30 | ], 31 | ))); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/home/tree/tree_item_tabs_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_wanandroid/common/config.dart'; 2 | import 'package:flutter_wanandroid/adapter/feed_item_adapter.dart'; 3 | 4 | import 'package:flutter_wanandroid/widgets/pull_to_refresh/pull_to_refresh.dart'; 5 | 6 | class SortItemPageBean { 7 | int cid; 8 | 9 | ///当前TabBar page 10 | Widget page; 11 | 12 | ///当前页面滑动的位置 13 | double offset; 14 | 15 | SortItemPageBean({@required this.cid}); 16 | } 17 | 18 | ///TODO SortItemPage和IndexPage中的ListView相同,后期是不是可以提取出来 19 | ///SortItemPage代表 tree 的下的TabBarView Page 20 | class SortItemPage extends StatefulWidget { 21 | final int cid; 22 | ScrollController controller; 23 | double offset; 24 | 25 | bool loading = true; 26 | bool isReload = false; 27 | List data; 28 | 29 | bool hasMore = true; 30 | 31 | int itemSize; 32 | 33 | bool isLoadMoring = false; 34 | bool isLoadMoreFailed = false; 35 | 36 | ///当前页数,参数从0开始 37 | int curPage = 0; 38 | 39 | SortItemPage({@required this.cid, @required this.offset}); 40 | 41 | @override 42 | _SortItemPageState createState() => new _SortItemPageState(); 43 | } 44 | 45 | ///AutomaticKeepAliveClientMixin对保留Page状态有作用 46 | class _SortItemPageState extends State { 47 | // RefreshController _refreshController; 48 | FeedItemAdapter _feedItemAdapter; 49 | 50 | _loadData() async { 51 | print('SortItemPage _loadData'); 52 | 53 | await HttpUtil.get( 54 | tree_article_api, [widget.curPage, widget.cid]).then((dataModel) { 55 | if (!mounted) return; 56 | 57 | ///这个errorCode只有两种情况 58 | if (dataModel.errorCode == 0) { 59 | widget.curPage++; 60 | 61 | ///判断是否还有更多数据 62 | if (dataModel.data['curPage'] < dataModel.data['pageCount']) { 63 | widget.hasMore = true; 64 | } else { 65 | widget.hasMore = false; 66 | } 67 | 68 | if (widget.data != null) { 69 | widget.data.addAll(dataModel.data['datas']); 70 | widget.itemSize = widget.data.length; 71 | } else { 72 | widget.data = dataModel.data['datas']; 73 | widget.itemSize = widget.data.length; 74 | } 75 | 76 | setState(() { 77 | widget.loading = false; 78 | widget.isReload = false; 79 | if (widget.isLoadMoring) { 80 | widget.isLoadMoring = false; 81 | if (widget.isLoadMoreFailed) { 82 | widget.isLoadMoreFailed = false; 83 | } 84 | } 85 | }); 86 | } else { 87 | setState(() { 88 | widget.loading = false; 89 | widget.isReload = true; 90 | 91 | ///加载更多,加载失败 92 | if (widget.isLoadMoring) { 93 | widget.isLoadMoreFailed = true; 94 | } 95 | }); 96 | } 97 | }); 98 | } 99 | 100 | _buildTabPage() { 101 | Widget body; 102 | if (widget.loading == true) { 103 | body = Application.progressWidget; 104 | } else { 105 | ///获取数据出错,需要重新加载 106 | if (widget.isReload) { 107 | body = Application.getReloadWidget(onPressed: () { 108 | if (!mounted) return; 109 | 110 | setState(() { 111 | widget.loading = true; 112 | widget.isReload = false; 113 | }); 114 | _loadData(); 115 | }); 116 | } else { 117 | ///TODO 这里上拉加载不使用SmartRefresher了,自己写个简单的上拉加载 118 | body = new ListView.builder( 119 | physics: const AlwaysScrollableScrollPhysics(), 120 | controller: widget.controller, 121 | shrinkWrap: true, 122 | itemCount: widget.itemSize + 1, 123 | itemBuilder: (context, index) { 124 | print('SortItemPage _buildTabPage index $index'); 125 | 126 | ///已加载更多一次,而且加载失败了 127 | if (widget.isLoadMoring && widget.isLoadMoreFailed) { 128 | return Application.getLoadMoreFailedWidget(onPressed: () { 129 | widget.isLoadMoreFailed=false; 130 | _loadData(); 131 | }); 132 | } 133 | 134 | ///滑动到最后一个,显示加载更多的布局 135 | if (index == widget.itemSize) { 136 | if (widget.hasMore) { 137 | return Application.loadMoreWidget; 138 | } else { 139 | return Application.noMoreWidget; 140 | } 141 | } 142 | return _feedItemAdapter.getItemView(context, widget.data[index]); 143 | }); 144 | } 145 | } 146 | return body; 147 | } 148 | 149 | @override 150 | void initState() { 151 | super.initState(); 152 | 153 | print('_SortItemPageState initState'); 154 | 155 | ///避免多次请求网络数据 156 | if (widget.loading) { 157 | _loadData(); 158 | } 159 | _feedItemAdapter = new FeedItemAdapter(); 160 | } 161 | 162 | @override 163 | Widget build(BuildContext context) { 164 | print('_SortItemPageState build ${widget.cid}'); 165 | 166 | ///ScrollController是控制ListView滑动到那个位置的,设置 167 | widget.controller = 168 | new ScrollController(initialScrollOffset: widget.offset); 169 | widget.controller.addListener(() { 170 | ///当绑定了该ScrollController的ListView滑动时就会调用该方法 171 | widget.offset = widget.controller.offset; 172 | print('_SortItemPageState _buildTabPage ${widget.offset}'); 173 | 174 | ///这里判断是否滑动到底部了,就可以进行加载更多的操作了 175 | if (widget.controller.position.pixels == 176 | widget.controller.position.maxScrollExtent) { 177 | if (!widget.isLoadMoring) { 178 | widget.isLoadMoring = true; 179 | _loadData(); 180 | } 181 | } 182 | }); 183 | 184 | return _buildTabPage(); 185 | } 186 | } 187 | 188 | class TreeItemTabsPage extends StatefulWidget { 189 | final Map tabsLabel; 190 | final String title; 191 | 192 | TreeItemTabsPage( 193 | {@required Key key, @required this.title, @required this.tabsLabel}) 194 | : super(key: key); 195 | 196 | @override 197 | _TreeItemTabsPageState createState() => new _TreeItemTabsPageState(); 198 | } 199 | 200 | class _TreeItemTabsPageState extends State 201 | with SingleTickerProviderStateMixin { 202 | TabController _tabController; 203 | List sortItemPageBean = []; 204 | 205 | @override 206 | void initState() { 207 | super.initState(); 208 | 209 | print('_TreeItemTabsPageState initState'); 210 | _tabController = 211 | new TabController(length: widget.tabsLabel.length, vsync: this); 212 | 213 | widget.tabsLabel.keys.map((name) { 214 | print('_TreeItemTabsPageState $name'); 215 | sortItemPageBean.add(new SortItemPageBean(cid: widget.tabsLabel[name])); 216 | }).toList(); 217 | } 218 | 219 | @override 220 | void dispose() { 221 | _tabController.dispose(); 222 | super.dispose(); 223 | } 224 | 225 | @override 226 | Widget build(BuildContext context) { 227 | return new Scaffold( 228 | appBar: new AppBar( 229 | title: new Text(widget.title), 230 | bottom: new TabBar( 231 | controller: _tabController, 232 | isScrollable: true, 233 | indicator: const UnderlineTabIndicator(), 234 | tabs: widget.tabsLabel.keys.map((label) { 235 | return new Tab(text: label); 236 | }).toList()), 237 | ), 238 | body: new TabBarView( 239 | // key: new Key('tree_item_tabs'), 240 | controller: _tabController, 241 | children: sortItemPageBean.map((bean) { 242 | if (bean.page == null) { 243 | bean.offset = 0.0; 244 | bean.page = new SortItemPage( 245 | cid: bean.cid, 246 | offset: bean.offset, 247 | ); 248 | } 249 | return bean.page; 250 | }).toList(), 251 | ), 252 | ); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/home/tree/tree_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_wanandroid/common/config.dart'; 2 | import 'package:flutter_wanandroid/home/tree/tree_item_tabs_page.dart'; 3 | 4 | ///知识体系页面 5 | class TreePage extends StatefulWidget { 6 | final GlobalKey scaffoldKey; 7 | 8 | TreePage({@required Key key, @required this.scaffoldKey}) : super(key: key); 9 | 10 | @override 11 | _TreePageState createState() => new _TreePageState(); 12 | } 13 | 14 | ///"children":[ 15 | /// { 16 | /// "children":[ 17 | /// ], 18 | /// "courseId":13, 19 | /// "id":60,--------------cid 20 | /// "name":"Android Studio相关", 21 | /// "order":1000, 22 | /// "parentChapterId":150, 23 | /// "visible":1 24 | /// }, 25 | /// { 26 | /// "children":Array[0], 27 | /// "courseId":13, 28 | /// "id":169, 29 | /// "name":"gradle", 30 | /// "order":1001, 31 | /// "parentChapterId":150, 32 | /// "visible":1 33 | /// }, 34 | /// Object{...} 35 | /// ], 36 | /// "courseId":13, 37 | /// "id":150, 38 | /// "name":"开发环境", 39 | /// "order":1, 40 | /// "parentChapterId":0,-------------id 41 | /// "visible":1 42 | class _TreePageState extends State { 43 | bool loading = true; 44 | bool isReload = false; 45 | List data; 46 | 47 | int itemSize; 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | _loadData(); 53 | } 54 | 55 | _loadData() async { 56 | print('TreePage _loadData'); 57 | 58 | await HttpUtil 59 | .get( 60 | tree_api, 61 | ) 62 | .then((dataModel) { 63 | if (!mounted) return; 64 | 65 | ///这个errorCode只有两种情况 66 | if (dataModel.errorCode == 0) { 67 | data = dataModel.data; 68 | 69 | itemSize = data.length; 70 | setState(() { 71 | loading = false; 72 | isReload = false; 73 | }); 74 | } else { 75 | setState(() { 76 | loading = false; 77 | isReload = true; 78 | }); 79 | } 80 | }); 81 | } 82 | 83 | _buildBody() { 84 | Widget body; 85 | if (loading == true) { 86 | body = Application.progressWidget; 87 | } else { 88 | ///获取数据出错,需要重新加载 89 | if (isReload) { 90 | body = Application.getReloadWidget(onPressed: () { 91 | if (!mounted) return; 92 | 93 | setState(() { 94 | loading = true; 95 | isReload = false; 96 | }); 97 | _loadData(); 98 | }); 99 | } else { 100 | body = new ListView.builder( 101 | physics: const ScrollPhysics(), 102 | shrinkWrap: true, 103 | itemCount: itemSize, 104 | itemBuilder: (context, index) { 105 | print('TreePage index $index'); 106 | return _buildTreeListItem(data[index]); 107 | }); 108 | } 109 | } 110 | return body; 111 | } 112 | 113 | _buildTreeListItem(item) { 114 | Color primaryColor = Theme.of(context).primaryColor; 115 | 116 | Map tabsLabel = new Map(); 117 | StringBuffer stringBuffer = new StringBuffer(); 118 | 119 | int length = item['children'].length; 120 | for (int i = 0; i < length; i++) { 121 | Map map = item['children'][i]; 122 | 123 | tabsLabel.putIfAbsent(map['name'], () => map['id']); 124 | 125 | stringBuffer.write(map['name']); 126 | if (i < length - 1) { 127 | stringBuffer.write(' '); 128 | } 129 | } 130 | 131 | Widget childsText = new Text(stringBuffer.toString()); 132 | 133 | return new Material( 134 | child: new InkWell( 135 | onTap: () { 136 | Navigator.of(context).push(new MaterialPageRoute(builder: (context) { 137 | return new TreeItemTabsPage( 138 | key: new Key(item['name']), 139 | title: item['name'], 140 | tabsLabel: tabsLabel 141 | ); 142 | })); 143 | }, 144 | child: new Card( 145 | elevation: 4.0, 146 | child: new Padding( 147 | padding: const EdgeInsets.all(4.0), 148 | child: new Row( 149 | children: [ 150 | new Expanded( 151 | child: new Column( 152 | children: [ 153 | new Text(item['name'], 154 | style: new TextStyle( 155 | color: primaryColor, fontSize: 18.0)), 156 | childsText, 157 | ], 158 | ), 159 | ), 160 | const Icon(Icons.chevron_right), 161 | ], 162 | ), 163 | )), 164 | ), 165 | ); 166 | } 167 | 168 | @override 169 | Widget build(BuildContext context) { 170 | return new Container( 171 | decoration: new BoxDecoration( 172 | color: Colors.grey[100], 173 | ), 174 | child: _buildBody(), 175 | ); 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/home/web_view_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; 2 | 3 | import '../common/config.dart'; 4 | import 'package:url_launcher/url_launcher.dart'; 5 | 6 | ///WebViewPage 具体内容页面 TODO WebView这个页面效果不好 7 | class WebViewPage extends StatefulWidget { 8 | final String title; 9 | final String url; 10 | 11 | WebViewPage({@required Key key, @required this.title, @required this.url}) 12 | : super(key: key); 13 | 14 | @override 15 | _DetailPageState createState() => new _DetailPageState(); 16 | } 17 | 18 | class _DetailPageState extends State { 19 | 20 | bool isFavorite=false; 21 | @override 22 | initState() { 23 | super.initState(); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | 29 | ///flutter_webview_plugin, The webview is not integrated in the widget tree, 30 | /// it is a native view on top of the flutter view. you won't be able to use snackbars, dialogs ... 31 | return new WebviewScaffold( 32 | url: widget.url, 33 | appBar: new AppBar( 34 | ///TODO AppBar上的title设置为跑马灯效果 35 | title: new Text(widget.title), 36 | ///TODO 弹出式的Menu不能用,会被native WebView遮挡 37 | actions: [ 38 | new GestureDetector( 39 | child: new Icon(isFavorite?Icons.favorite:Icons.favorite_border), 40 | onTap: (){ 41 | setState(() { 42 | isFavorite=!isFavorite; 43 | }); 44 | }, 45 | ), 46 | new SizedBox(width: 8.0,), 47 | new GestureDetector( 48 | child: const Icon(Icons.explore), 49 | onTap: () async { 50 | if (await canLaunch(widget.url)) { 51 | await launch(widget.url); 52 | } else { 53 | ///这里使用Toast 54 | print('Could not launch ${widget.url}'); 55 | } 56 | }, 57 | ), 58 | new SizedBox(width: 8.0,), 59 | ], 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_wanandroid/common/config.dart'; 2 | import 'package:flutter_wanandroid/home/main_screen.dart'; 3 | import 'package:stack_trace/stack_trace.dart'; 4 | 5 | void main() { 6 | 7 | // https://pub.flutter-io.cn/packages/stack_trace 8 | // 这里使用是这个开源库,可以处理StackTrack(堆栈跟踪)信息,但是直接这样使用好像是没有效果的 9 | // Chain.capture(() { 10 | // runApp(new MyApp()); 11 | // }, onError: (final error, final Chain chain) { 12 | // print("Caught error $error\n" 13 | // "${chain.terse}"); 14 | // }); 15 | runApp(new MyApp()); 16 | } 17 | 18 | class MyApp extends StatelessWidget { 19 | // This widget is the root of your application. 20 | @override 21 | Widget build(BuildContext context) { 22 | 23 | ///调用这个可以让状态栏隐藏,下滑还是可以出现,一段时间后消失 24 | // SystemChrome.setEnabledSystemUIOverlays([]); 25 | return new MaterialApp( 26 | title: 'WanAndroid APP', 27 | //设置debug模式下是否展示banner 28 | debugShowCheckedModeBanner: false, 29 | theme: new ThemeData( 30 | primarySwatch: Colors.blue, 31 | backgroundColor: Colors.grey[100], 32 | ), 33 | home: MainScreen(), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/model/data_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:meta/meta.dart'; 2 | 3 | ///DataModel 是基础返回数据Model 4 | class DataModel { 5 | int errorCode; 6 | String errorMsg; 7 | dynamic data;///动态类型,根据不同返回接口,数据类型不同 8 | 9 | DataModel( 10 | {@required this.data, @required this.errorCode, @required this.errorMsg}); 11 | 12 | @override 13 | String toString() { 14 | return '{"errorCode": $errorCode,"errorMsg": $errorMsg,"data": $data}'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/notifications/notifications.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/src/widgets/notification_listener.dart'; 2 | 3 | class LoginChangeNotification extends Notification{ 4 | 5 | } -------------------------------------------------------------------------------- /flutter_wanandroid/lib/startup/login_page.dart: -------------------------------------------------------------------------------- 1 | import '../common/config.dart'; 2 | import '../notifications/notifications.dart'; 3 | import '../startup/register_page.dart'; 4 | 5 | ///登录页面 6 | class LoginPage extends StatefulWidget { 7 | @override 8 | _LoginPageState createState() => new _LoginPageState(); 9 | } 10 | 11 | class _LoginPageState extends State { 12 | ///GlobalKey 全局Key,使用全局Key来唯一标志widget 13 | final GlobalKey _scaffoldKey = new GlobalKey(); 14 | 15 | String username; 16 | String password; 17 | 18 | bool _autoValidate = false; 19 | bool _formWasEdited = false; 20 | 21 | bool _obscureText = true; 22 | 23 | final GlobalKey _formKey = new GlobalKey(); 24 | 25 | ///处理表单的提交 26 | _handleSubmitted(BuildContext context) async { 27 | final FormState form = _formKey.currentState; 28 | if (!form.validate()) { 29 | ///FormState.validate()如果没有错误将会返回true 30 | _autoValidate = true; 31 | SnackBarUtil.showInSnackBar(_scaffoldKey, '填写信息有误.'); 32 | } else { 33 | ///调用该方法,才会调用输入控件中的'onSave'方法 34 | form.save(); 35 | 36 | showProgressDialog(context: context); 37 | 38 | Map map = { 39 | "username": username, 40 | "password": password, 41 | }; 42 | 43 | ///TODO 这里不知为什么用户名和密码一直不对,明明是对的才对??? 44 | print('post map: $map'); 45 | await HttpUtil.post(login_api, map).then((dataModel) { 46 | if (!mounted) return; 47 | 48 | ///将前面那个Progress隐藏 49 | Navigator.pop(context, true); 50 | 51 | print(dataModel ?? 'dataModel is null'); 52 | 53 | if (dataModel.errorCode == 0) { 54 | ///登录成功,跳转界面 55 | Navigator.of(context).pop(); 56 | 57 | ///Notification 通知,Notification.dispatch()方法发起通知 58 | LoginChangeNotification notification = new LoginChangeNotification(); 59 | 60 | ///在State<>类中的任意位置都可以获取到context对象 61 | notification.dispatch(context); //发起通知 62 | 63 | } else { 64 | SnackBarUtil.showInSnackBar(_scaffoldKey, dataModel.errorMsg); 65 | } 66 | }); 67 | } 68 | } 69 | 70 | ///验证电话的有效性 71 | String _validateUserName(String value) { 72 | _formWasEdited = true; 73 | if (value.trim().isEmpty) return '用户名不能为空'; 74 | return null; 75 | } 76 | 77 | ///验证密码 78 | String _validatePassword(String value) { 79 | _formWasEdited = true; 80 | if (value.trim().isEmpty) return '密码不能为空'; 81 | return null; 82 | } 83 | 84 | ///new ListView( 85 | /// reverse: true, 86 | /// children: [ 87 | /// // put your text fields here 88 | /// ].reversed.toList(), 89 | ///), 90 | @override 91 | Widget build(BuildContext context) { 92 | return new Scaffold( 93 | key: _scaffoldKey, //这里一定要加,因为只能在布局是Scaffold的Context中使用showSnacker()方法 94 | appBar: new AppBar( 95 | elevation: 0.0, 96 | title: const Text( 97 | "登录", 98 | ), 99 | ), 100 | body: new Form( 101 | key: _formKey, 102 | //是否每次在内容更改后,自动检测内容 103 | autovalidate: _autoValidate, 104 | //拦截返回按钮事件 105 | // onWillPop: _warnUserAboutInvalidData, 106 | 107 | child: new ListView( 108 | reverse: true, 109 | children: [ 110 | const SizedBox( 111 | height: 24.0, 112 | ), 113 | 114 | ///跳转注册页面 115 | new Container( 116 | height: 24.0, 117 | 118 | ///alignment 对齐方式 119 | alignment: Alignment.bottomRight, 120 | padding: EdgeInsets.only(right: 24.0), 121 | child: new InkWell( 122 | child: new Text( 123 | '没有账号?去注册', 124 | style: new TextStyle(color: Colors.blue), 125 | ), 126 | onTap: () { 127 | Navigator.of(context) 128 | 129 | ///pushReplacement替换前一个界面 130 | .pushReplacement( 131 | new MaterialPageRoute(builder: (context) { 132 | return new RegisterPage(); 133 | })); 134 | }, 135 | ), 136 | ), 137 | const SizedBox( 138 | height: 12.0, 139 | ), 140 | 141 | new ConstrainedBox( 142 | constraints: const BoxConstraints.expand(height: 48.0), 143 | child: new Padding( 144 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 145 | child: new RaisedButton( 146 | color: Colors.blue, 147 | child: new Text( 148 | '登录', 149 | style: new TextStyle(color: Colors.white, fontSize: 18.0), 150 | ), 151 | onPressed: () { 152 | //提交执行操作 153 | _handleSubmitted(context); 154 | }), 155 | ), 156 | ), 157 | 158 | const SizedBox( 159 | height: 24.0, 160 | ), 161 | 162 | new Padding( 163 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 164 | child: new TextFormField( 165 | obscureText: _obscureText, //true代表不可见 166 | onSaved: (String value) { 167 | password = value.trim(); 168 | }, 169 | validator: _validatePassword, 170 | decoration: new InputDecoration( 171 | border: const UnderlineInputBorder(), 172 | hintText: '密码', 173 | filled: true, 174 | fillColor: Colors.transparent, 175 | 176 | ///suffixIcon 尾部的icon图标 177 | suffixIcon: new GestureDetector( 178 | onTap: () { 179 | setState(() { 180 | _obscureText = !_obscureText; 181 | }); 182 | }, 183 | child: new Icon( 184 | _obscureText ? Icons.visibility_off : Icons.visibility), 185 | ), 186 | ), 187 | ), 188 | ), 189 | 190 | const SizedBox( 191 | height: 24.0, 192 | ), 193 | 194 | new Padding( 195 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 196 | child: new TextFormField( 197 | decoration: const InputDecoration( 198 | //输入控件底部的横线,border边框 199 | border: const UnderlineInputBorder(), 200 | // hintText: 201 | labelText: '用户名 *', 202 | //下面两个属性要结合使用 203 | filled: true, 204 | fillColor: Colors.transparent, //输入框的背景颜色 205 | ), 206 | onSaved: (String value) { 207 | //保存TextFormField输入的字段 208 | username = value.trim(); //保存输入数据 209 | }, 210 | //validator 验证器 211 | validator: _validateUserName, //验证输入数据 212 | ), 213 | ), 214 | 215 | const SizedBox( 216 | height: 24.0, 217 | ), 218 | 219 | //登录页面顶部图片 220 | new SizedBox( 221 | height: 200.0, 222 | child: new Image.asset( 223 | 'assets/logo.png', 224 | fit: BoxFit.scaleDown, 225 | ), 226 | ), 227 | ], 228 | ), 229 | // ), 230 | ), 231 | ); 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/startup/register_page.dart: -------------------------------------------------------------------------------- 1 | import '../common/config.dart'; 2 | import '../startup/login_page.dart'; 3 | import '../model/data_model.dart'; 4 | 5 | ///注册页面 6 | class RegisterPage extends StatefulWidget { 7 | @override 8 | _RegisterPageState createState() => new _RegisterPageState(); 9 | } 10 | 11 | class _RegisterPageState extends State { 12 | ///GlobalKey 全局Key,使用全局Key来唯一标志widget 13 | final GlobalKey _scaffoldKey = new GlobalKey(); 14 | 15 | String username; 16 | String password; 17 | String repassword; 18 | 19 | bool _autoValidate = false; 20 | bool _formWasEdited = false; 21 | 22 | bool _obscureText = true; 23 | 24 | final GlobalKey _formKey = new GlobalKey(); 25 | 26 | ///处理表单的提交 27 | _handleSubmitted(BuildContext context) async { 28 | final FormState form = _formKey.currentState; 29 | if (!form.validate()) { 30 | ///FormState.validate()如果没有错误将会返回true 31 | _autoValidate = true; 32 | SnackBarUtil.showInSnackBar(_scaffoldKey, '填写信息有误.'); 33 | } else { 34 | ///调用该方法,才会调用输入控件中的'onSave'方法 35 | form.save(); 36 | 37 | showProgressDialog(context: context); 38 | 39 | Map map = { 40 | 'username': username, 41 | "password": password, 42 | "repassword": repassword 43 | }; 44 | 45 | DataModel dataModel = await HttpUtil.post(register_api, map); 46 | 47 | if (!mounted) return; 48 | 49 | ///将前面那个Progress隐藏 50 | Navigator.pop(context, true); 51 | 52 | if (dataModel.errorCode == 0) { 53 | ///登录成功,跳转界面 54 | 55 | ///SnackBar的默认展示时间是1500毫秒 56 | SnackBarUtil.showInSnackBar(_scaffoldKey, '注册成功,将跳转到登录界面'); 57 | 58 | new Timer(const Duration(milliseconds: 1800), () { 59 | ///注册成功,将调整到登录界面 60 | Navigator 61 | .of(context) 62 | .pushReplacement(new MaterialPageRoute(builder: (context) { 63 | ///注册成功后就会跳转'登录'界面 64 | return new LoginPage(); 65 | })); 66 | }); 67 | } else { 68 | SnackBarUtil.showInSnackBar(_scaffoldKey, dataModel.errorMsg); 69 | } 70 | } 71 | } 72 | 73 | ///验证电话的有效性 74 | String _validateUserName(String value) { 75 | _formWasEdited = true; 76 | if (value.trim().isEmpty) return '用户名不能为空'; 77 | return null; 78 | } 79 | 80 | ///验证密码 81 | String _validatePassword(String value) { 82 | _formWasEdited = true; 83 | value = value.trim(); 84 | if (value.isEmpty) return '密码不能为空'; 85 | password = value; 86 | return null; 87 | } 88 | 89 | String _validateSame(String value) { 90 | _formWasEdited = true; 91 | value = value.trim(); 92 | if (value.isEmpty) return '确认密码不能为空.'; 93 | if (value != password) return '确认密码与前面输入不一致.'; 94 | return null; 95 | } 96 | 97 | @override 98 | Widget build(BuildContext context) { 99 | return new Scaffold( 100 | key: _scaffoldKey, //这里一定要加,因为只能在布局是Scaffold的Context中使用showSnackBar()方法 101 | appBar: new AppBar( 102 | elevation: 0.0, 103 | title: const Text( 104 | "注册", 105 | ), 106 | ), 107 | body: new Form( 108 | key: _formKey, 109 | //是否每次在内容更改后,自动检测内容 110 | autovalidate: _autoValidate, 111 | 112 | child: new ListView( 113 | reverse: true, 114 | children: [ 115 | const SizedBox( 116 | height: 18.0, 117 | ), 118 | 119 | ///跳转注册页面 120 | new Container( 121 | height: 24.0, 122 | 123 | ///alignment 对齐方式 124 | alignment: Alignment.bottomRight, 125 | padding: EdgeInsets.only(right: 24.0), 126 | child: new InkWell( 127 | child: new Text( 128 | '已有账号?去登录', 129 | style: new TextStyle(color: Colors.blue), 130 | ), 131 | onTap: () { 132 | Navigator.of(context) 133 | 134 | ///pushReplacement替换前一个界面 135 | .pushReplacement( 136 | new MaterialPageRoute(builder: (context) { 137 | return new LoginPage(); 138 | })); 139 | }, 140 | ), 141 | ), 142 | const SizedBox( 143 | height: 12.0, 144 | ), 145 | 146 | new ConstrainedBox( 147 | constraints: const BoxConstraints.expand(height: 48.0), 148 | child: new Padding( 149 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 150 | child: new RaisedButton( 151 | color: Colors.blue, 152 | child: new Text( 153 | '注册', 154 | style: new TextStyle(color: Colors.white, fontSize: 18.0), 155 | ), 156 | onPressed: () { 157 | //提交执行操作 158 | _handleSubmitted(context); 159 | }), 160 | ), 161 | ), 162 | 163 | const SizedBox( 164 | height: 12.0, 165 | ), 166 | 167 | new Padding( 168 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 169 | child: new TextFormField( 170 | obscureText: _obscureText, //true代表不可见 171 | onSaved: (String value) { 172 | repassword = value.trim(); 173 | }, 174 | validator: _validateSame, 175 | decoration: new InputDecoration( 176 | border: const UnderlineInputBorder(), 177 | hintText: '确认密码 *', 178 | filled: true, 179 | fillColor: Colors.transparent, 180 | 181 | ///suffixIcon 尾部的icon图标 182 | suffixIcon: new GestureDetector( 183 | onTap: () { 184 | setState(() { 185 | _obscureText = !_obscureText; 186 | }); 187 | }, 188 | child: new Icon( 189 | _obscureText ? Icons.visibility_off : Icons.visibility), 190 | ), 191 | ), 192 | ), 193 | ), 194 | const SizedBox( 195 | height: 12.0, 196 | ), 197 | 198 | new Padding( 199 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 200 | child: new TextFormField( 201 | obscureText: _obscureText, //true代表不可见 202 | onSaved: (String value) { 203 | password = value.trim(); 204 | }, 205 | validator: _validatePassword, 206 | decoration: new InputDecoration( 207 | border: const UnderlineInputBorder(), 208 | hintText: '密码 *', 209 | filled: true, 210 | fillColor: Colors.transparent, 211 | 212 | ///suffixIcon 尾部的icon图标 213 | suffixIcon: new GestureDetector( 214 | onTap: () { 215 | setState(() { 216 | _obscureText = !_obscureText; 217 | }); 218 | }, 219 | child: new Icon( 220 | _obscureText ? Icons.visibility_off : Icons.visibility), 221 | ), 222 | ), 223 | ), 224 | ), 225 | 226 | const SizedBox( 227 | height: 12.0, 228 | ), 229 | 230 | new Padding( 231 | padding: const EdgeInsets.symmetric(horizontal: 16.0), 232 | child: new TextFormField( 233 | decoration: const InputDecoration( 234 | //输入控件底部的横线,border边框 235 | border: const UnderlineInputBorder(), 236 | // hintText: 237 | labelText: '用户名 *', 238 | //下面两个属性要结合使用 239 | filled: true, 240 | fillColor: Colors.transparent, //输入框的背景颜色 241 | ), 242 | onSaved: (String value) { 243 | //保存TextFormField输入的字段 244 | username = value.trim(); //保存输入数据 245 | }, 246 | //validator 验证器 247 | validator: _validateUserName, //验证输入数据 248 | ), 249 | ), 250 | 251 | const SizedBox( 252 | height: 12.0, 253 | ), 254 | 255 | new SizedBox( 256 | height: 200.0, 257 | child: new Image.asset( 258 | 'assets/logo.png', 259 | fit: BoxFit.scaleDown, 260 | ), 261 | ), 262 | 263 | //登录页面顶部图片 264 | ], 265 | ), 266 | ), 267 | ); 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/test_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DoubleHolder { 4 | double value = 0.0; 5 | } 6 | 7 | class StatefulListView extends StatefulWidget { 8 | final DoubleHolder offset = new DoubleHolder(); 9 | 10 | StatefulListView(this._itemCount, 11 | this._indexedWidgetBuilder, 12 | { 13 | Key key 14 | }) 15 | : super(key: key); 16 | 17 | double getOffsetMethod() { 18 | return offset.value; 19 | } 20 | 21 | void setOffsetMethod(double val) { 22 | offset.value = val; 23 | } 24 | 25 | final int _itemCount; 26 | final IndexedWidgetBuilder _indexedWidgetBuilder; 27 | 28 | @override 29 | _StatefulListViewState createState() => 30 | new _StatefulListViewState(_itemCount, _indexedWidgetBuilder); 31 | } 32 | 33 | class _StatefulListViewState extends State { 34 | 35 | ScrollController scrollController; 36 | final int _itemCount; 37 | final IndexedWidgetBuilder _itemBuilder; 38 | 39 | _StatefulListViewState(this._itemCount, this._itemBuilder); 40 | 41 | @override 42 | void initState() { 43 | super.initState(); 44 | scrollController = new ScrollController( 45 | initialScrollOffset: widget.getOffsetMethod() 46 | ); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return new NotificationListener( 52 | child: new ListView.builder( 53 | controller: scrollController, 54 | itemCount: _itemCount, 55 | itemBuilder: _itemBuilder 56 | ), 57 | onNotification: (notification) { 58 | if (notification is ScrollNotification) { 59 | widget.setOffsetMethod(notification.metrics.pixels); 60 | } 61 | }, 62 | ); 63 | } 64 | } -------------------------------------------------------------------------------- /flutter_wanandroid/lib/utils/dialog_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | 4 | showProgressDialog({BuildContext context}) { 5 | final ThemeData theme = Theme.of(context); 6 | final TextStyle dialogTextStyle = 7 | theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color); 8 | 9 | showDialog( 10 | context: context, 11 | barrierDismissible: false, 12 | 13 | ///添加该属性,设置该属性为false,点击Dialog之外的区域就不会消失,按返回键还是会消失 14 | builder: (BuildContext context) => 15 | new AlertDialog( 16 | content: new Row( 17 | mainAxisAlignment: MainAxisAlignment.start, 18 | crossAxisAlignment: CrossAxisAlignment.center, 19 | children: [ 20 | new CircularProgressIndicator(), 21 | const SizedBox( 22 | width: 18.0, 23 | ), 24 | new Text( 25 | '请求中...', 26 | style: dialogTextStyle, 27 | ), 28 | ], 29 | ), 30 | )); 31 | } 32 | 33 | Future showAlertDialog(BuildContext context, String alert) { 34 | final ThemeData theme = Theme.of(context); 35 | final TextStyle dialogTextStyle = 36 | theme.textTheme.subhead.copyWith(color: theme.textTheme.caption.color); 37 | 38 | return showDialog( 39 | context: context, 40 | builder: (BuildContext context) => 41 | new AlertDialog( 42 | content: new Text( 43 | alert, 44 | style: dialogTextStyle, 45 | ), 46 | actions: [ 47 | new FlatButton( 48 | child: const Text('取消'), 49 | onPressed: () { 50 | Navigator.pop(context, false); 51 | }), 52 | new FlatButton( 53 | child: const Text('确认'), 54 | onPressed: () { 55 | Navigator.pop(context, true); 56 | }) 57 | ]), 58 | ); 59 | } 60 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/utils/http_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/dio.dart'; 4 | 5 | import '../common/config.dart'; 6 | import '../model/data_model.dart'; 7 | 8 | class HttpUtil { 9 | 10 | static Dio dio = new Dio(new Options( 11 | connectTimeout: timeOut, headers: {"content-Type": "application/json"})); 12 | 13 | ///get网络请求 14 | static Future get(String url, [List params]) async { 15 | ///页码 16 | if (params != null) { 17 | url = fillPage(url, params); 18 | } 19 | 20 | try { 21 | Response response = await dio.get(url); 22 | 23 | if (response.statusCode == HttpStatus.OK) { 24 | ///200,有两种情况,数据为空,数据不为空 25 | Map map = response.data; 26 | print('get response data ${response.data}'); 27 | 28 | return new DataModel( 29 | data: map['data'], 30 | 31 | ///0 32 | errorCode: map['errorCode'], 33 | errorMsg: map['errorMsg']); 34 | } else { 35 | print('url :$url \n错误msg :\n${response.statusCode}'); 36 | return getErrorModel(); 37 | } 38 | } on DioError catch (e) { 39 | 40 | ///网络请求超时异常 41 | if(e.type==DioErrorType.CONNECT_TIMEOUT){ 42 | print('---Error DioErrorType connect timeOut---'); 43 | return getErrorModel(1); 44 | } 45 | 46 | ///DioError.response响应信息,如果发生在服务器返回数据之前,则为null 47 | if (e.response != null) { 48 | print('---Error response is not null------------'); 49 | print(e.response.data); 50 | print(e.response.headers); 51 | print(e.response.request); 52 | return getErrorModel(); 53 | } else { 54 | // Something happened in setting up or sending the request that triggered an Error 55 | print('---Error response is null------------------'); 56 | print(e.message); 57 | return getErrorModel(2); 58 | } 59 | } 60 | } 61 | 62 | ///post网络请求 63 | static Future post(String url, Map map, [List params]) async { 64 | ///页码 65 | if (params != null) { 66 | url = fillPage(url, params); 67 | } 68 | 69 | try { 70 | Response response = await dio.post(url, data: map); 71 | 72 | if (response.statusCode == HttpStatus.OK) { 73 | print('post reponse.body ${response.data}'); 74 | 75 | ///Response.data已经解析为Map数据类型了 76 | Map map = response.data; 77 | 78 | ///Response.headers 请求头数据 79 | print(response.headers['set-cookie']);///返回的是List的 80 | 81 | // ///登录请求,请求成功后保存cookie,后面可以根据cookie做自动登录 82 | // if (map['errorCode'] == 0 && url == login_api) { 83 | // print(response.headers.cookies); 84 | // setCookie(response.headers.cookies); 85 | // } 86 | return new DataModel( 87 | data: map['data'], 88 | errorCode: map['errorCode'], 89 | errorMsg: map['errorMsg']); 90 | } else { 91 | print('url :$url \n错误msg :\n${response.statusCode}'); 92 | return getErrorModel(); 93 | } 94 | } on DioError catch (e) { 95 | ///网络请求超时异常 96 | if (e.type == DioErrorType.CONNECT_TIMEOUT) { 97 | print('---Error DioErrorType connect timeOut---'); 98 | return getErrorModel(1); 99 | } 100 | 101 | ///DioError.response响应信息,如果发生在服务器返回数据之前,则为null 102 | if (e.response != null) { 103 | print('---Error response is not null------------'); 104 | print(e.response.data); 105 | print(e.response.headers); 106 | print(e.response.request); 107 | return getErrorModel(); 108 | } else { 109 | ///无网络的请求下跳转到这里,抛出SocketException 异常 110 | 111 | ///事件发生在设置或发送请求,触发一个错误 112 | /// Something happened in setting up or sending the request that triggered an Error 113 | print('---Error response is null------------------'); 114 | print(e.message); 115 | return getErrorModel(2); 116 | } 117 | } 118 | } 119 | 120 | static DataModel getErrorModel([int tag = 0]) { 121 | String errorMsg; 122 | switch (tag) { 123 | case 0: 124 | errorMsg = '网络请求错误,请稍后重试'; 125 | break; 126 | case 1: 127 | errorMsg = '网络请求超时,请稍后重试'; 128 | break; 129 | case 2: 130 | errorMsg = '网络未连接,请检查网络后重试'; 131 | break; 132 | default: 133 | errorMsg = '未知异常'; 134 | break; 135 | } 136 | return new DataModel(data: null, errorCode: -1, errorMsg: errorMsg); 137 | } 138 | 139 | static String fillPage(String url, List params) { 140 | 141 | int index=0; 142 | for(int param in params){ 143 | if (url.contains('{param}',index)) { 144 | index=url.indexOf(new RegExp(r'{param}'))+'{param}'.length; 145 | 146 | url=url.replaceFirst(new RegExp(r'{param}'), param.toString()); 147 | } 148 | } 149 | 150 | return url; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/utils/log.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | class Log{ 4 | 5 | static String fixInt(int num, [int fix = 2]) { 6 | String str = num.toString(); 7 | fix -= str.length; 8 | 9 | return '${'0' * fix}$str'; 10 | } 11 | 12 | static d(StackTrace stackTrace, Object obj) { 13 | // StackTrack中的第一行数据 14 | String st = stackTrace.toString().split('\n')[0]; 15 | // 方法和文件 16 | String methodAFile=st.substring('#0'.length).trim(); 17 | String method = methodAFile.substring(0,methodAFile.indexOf(' ')); 18 | String file = methodAFile.substring(methodAFile.indexOf('(')+1,methodAFile.indexOf(')')); 19 | DateTime time = new DateTime.now(); 20 | print(''' 21 | -------------------------------------------------------------------------------- 22 | Method : $method 23 | File : file://$file 24 | ${fixInt(time.hour)}:${fixInt(time.minute)}:${fixInt(time.second)}.${fixInt(time.millisecond,3)} 25 | Msg:${obj.toString()} 26 | -------------------------------------------------------------------------------- 27 | ''' 28 | ); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/utils/shares_util.dart: -------------------------------------------------------------------------------- 1 | import '../common/config.dart'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | 4 | Future getCookie() async { 5 | SharedPreferences prefs = await SharedPreferences.getInstance(); 6 | return prefs.getString('set-cookie'); 7 | } 8 | 9 | setCookie(String cookie) async { 10 | SharedPreferences prefs = await SharedPreferences.getInstance(); 11 | await prefs.setString('set-cookie', cookie); 12 | } 13 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/utils/snackbar_util.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | class SnackBarUtil{ 5 | 6 | static showInSnackBar(GlobalKey scaffoldKey,String value){ 7 | ///隐藏之前要显示或将要显示的Snacker 8 | scaffoldKey.currentState 9 | .hideCurrentSnackBar(reason: SnackBarClosedReason.hide); 10 | 11 | ///这个currentState一定要是Scaffold,否则将找不到showSnacker方法 12 | scaffoldKey.currentState 13 | .showSnackBar(new SnackBar(content: new Text(value))); 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /flutter_wanandroid/lib/widgets/pull_to_refresh/pull_to_refresh.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Jpeng 3 | Email: peng8350@gmail.com 4 | createTime:2018-05-03 15:39 5 | */ 6 | 7 | library pulltorefresh; 8 | 9 | export 'src/smart_refresher.dart'; 10 | export 'src/indicator/classic_indicator.dart'; 11 | export 'src/internals/indicator_config.dart'; -------------------------------------------------------------------------------- /flutter_wanandroid/lib/widgets/pull_to_refresh/src/indicator/classic_indicator.dart: -------------------------------------------------------------------------------- 1 | /** 2 | Author: Jpeng 3 | Email: peng8350@gmail.com 4 | createTime:2018-05-14 17:39 5 | */ 6 | 7 | import 'package:flutter/material.dart' hide RefreshIndicator; 8 | import 'package:flutter/widgets.dart'; 9 | import 'package:flutter_wanandroid/widgets/pull_to_refresh/pull_to_refresh.dart'; 10 | import 'package:meta/meta.dart'; 11 | 12 | enum IconPosition { left, right, top, bottom } 13 | 14 | class ClassicIndicator extends Indicator { 15 | final String releaseText, 16 | idleText, 17 | refreshingText, 18 | completeText, 19 | failedText, 20 | noDataText; 21 | 22 | final Widget releaseIcon, 23 | idleIcon, 24 | refreshingIcon, 25 | completeIcon, 26 | failedIcon, 27 | noMoreIcon; 28 | 29 | final double height; 30 | 31 | final double spacing; 32 | 33 | final IconPosition iconPos; 34 | 35 | final TextStyle textStyle; 36 | 37 | const ClassicIndicator( 38 | {@required int mode, 39 | Key key, 40 | this.textStyle: const TextStyle(color: const Color(0xff555555)), 41 | this.releaseText: 'Refresh when release', 42 | this.refreshingText: 'Refreshing...', 43 | this.completeText: 'Refresh complete!', 44 | this.noDataText: 'No more data', 45 | this.height: 60.0, 46 | this.noMoreIcon: const Icon(Icons.clear, color: Colors.grey), 47 | this.failedText: 'Refresh failed', 48 | this.idleText: 'Pull down to refresh', 49 | this.iconPos: IconPosition.left, 50 | this.spacing: 15.0, 51 | this.refreshingIcon: const CircularProgressIndicator(strokeWidth: 2.0), 52 | this.failedIcon: const Icon(Icons.clear, color: Colors.grey), 53 | this.completeIcon: const Icon(Icons.done, color: Colors.grey), 54 | this.idleIcon = const Icon(Icons.arrow_downward, color: Colors.grey), 55 | this.releaseIcon = const Icon(Icons.arrow_upward, color: Colors.grey), 56 | int completeTime: 800, 57 | double visibleRange: 60.0, 58 | double triggerDistance: 80.0}) 59 | : super(key: key, mode: mode); 60 | 61 | @override 62 | State createState() { 63 | // TODO: implement createState 64 | return new _ClassicIndicatorState(); 65 | } 66 | } 67 | 68 | class _ClassicIndicatorState extends State { 69 | Widget _buildText() { 70 | return new Text( 71 | widget.mode == RefreshStatus.canRefresh 72 | ? widget.releaseText 73 | : widget.mode == RefreshStatus.completed 74 | ? widget.completeText 75 | : widget.mode == RefreshStatus.failed 76 | ? widget.failedText 77 | : widget.mode == RefreshStatus.refreshing 78 | ? widget.refreshingText:widget.mode==RefreshStatus.noMore?widget.noDataText 79 | : widget.idleText, 80 | style: widget.textStyle); 81 | } 82 | 83 | Widget _buildIcon() { 84 | Widget icon = widget.mode == RefreshStatus.canRefresh 85 | ? widget.releaseIcon:widget.mode==RefreshStatus.noMore?widget.noMoreIcon 86 | : widget.mode == RefreshStatus.idle 87 | ? widget.idleIcon 88 | : widget.mode == RefreshStatus.completed 89 | ? widget.completeIcon 90 | : widget.mode == RefreshStatus.failed 91 | ? widget.failedIcon 92 | : new SizedBox( 93 | width: 25.0, 94 | height: 25.0, 95 | child: 96 | const CircularProgressIndicator(strokeWidth: 2.0), 97 | ); 98 | return icon; 99 | } 100 | 101 | @override 102 | Widget build(BuildContext context) { 103 | // TODO: implement buildContent 104 | Widget textWidget = _buildText(); 105 | Widget iconWidget = _buildIcon(); 106 | List childrens = [ 107 | iconWidget, 108 | new Container( 109 | width: widget.spacing, 110 | height: widget.spacing, 111 | ), 112 | textWidget 113 | ]; 114 | Widget container = (widget.iconPos == IconPosition.top || 115 | widget.iconPos == IconPosition.bottom) 116 | ? new Column( 117 | mainAxisAlignment: MainAxisAlignment.center, 118 | verticalDirection: widget.iconPos == IconPosition.top 119 | ? VerticalDirection.down 120 | : VerticalDirection.up, 121 | children: childrens, 122 | ) 123 | : new Row( 124 | textDirection: widget.iconPos == IconPosition.right 125 | ? TextDirection.rtl 126 | : TextDirection.ltr, 127 | mainAxisAlignment: MainAxisAlignment.center, 128 | children: childrens, 129 | ); 130 | return new Container( 131 | alignment: Alignment.center, 132 | height: widget.height, 133 | child: new Center( 134 | child: container, 135 | ), 136 | ); 137 | } 138 | 139 | @override 140 | void initState() { 141 | // TODO: implement initState 142 | super.initState(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/widgets/pull_to_refresh/src/internals/default_constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart'; 2 | 3 | /* 4 | Author: Jpeng 5 | Email: peng8350@gmail.com 6 | createTime:2018-05-17 10:39 7 | */ 8 | 9 | 10 | typedef void OnRefresh(bool up); 11 | typedef void OnOffsetChange(bool up, double offset); 12 | typedef Widget IndicatorBuilder(BuildContext context, int mode); 13 | 14 | const int default_completeDuration = 800; 15 | 16 | const double default_refresh_triggerDistance = 100.0; 17 | 18 | const double default_load_triggerDistance = 5.0; 19 | 20 | const double default_VisibleRange = 50.0; 21 | 22 | const bool default_AutoLoad = true; 23 | 24 | const bool default_enablePullDown = true; 25 | 26 | const bool default_enablePullUp = false; 27 | 28 | const bool default_BottomWhenBuild = true; 29 | 30 | const bool default_enableOverScroll = true; 31 | 32 | const int spaceAnimateMill=300; 33 | 34 | const double minSpace = 0.000001; -------------------------------------------------------------------------------- /flutter_wanandroid/lib/widgets/pull_to_refresh/src/internals/indicator_config.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Jpeng 3 | Email: peng8350@gmail.com 4 | createTime:2018-05-14 15:39 5 | */ 6 | 7 | import 'default_constants.dart'; 8 | 9 | /* 10 | * This will use to configure the Wrapper action 11 | */ 12 | abstract class Config { 13 | // How many distances should be dragged to trigger refresh 14 | final double triggerDistance; 15 | 16 | const Config({this.triggerDistance}); 17 | } 18 | 19 | class RefreshConfig extends Config { 20 | // display time of success or failed 21 | final int completeDuration; 22 | // emptySpace height 23 | final double visibleRange; 24 | const RefreshConfig( 25 | {this.visibleRange: default_VisibleRange, 26 | double triggerDistance: default_refresh_triggerDistance, 27 | this.completeDuration: default_completeDuration}) 28 | : super(triggerDistance: triggerDistance); 29 | } 30 | 31 | class LoadConfig extends Config { 32 | // if autoLoad when touch outside 33 | final bool autoLoad; 34 | // Whether the interface is at the bottom when the interface is loaded 35 | final bool bottomWhenBuild; 36 | 37 | final bool enableOverScroll; 38 | const LoadConfig({ 39 | this.autoLoad: default_AutoLoad, 40 | this.bottomWhenBuild:default_BottomWhenBuild, 41 | this.enableOverScroll :default_enableOverScroll, 42 | double triggerDistance: default_load_triggerDistance, 43 | }) : super(triggerDistance: triggerDistance); 44 | } 45 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/widgets/pull_to_refresh/src/internals/indicator_wrap.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Jpeng 3 | Email: peng8350@gmail.com 4 | createTime:2018-05-14 15:39 5 | */ 6 | 7 | import 'dart:async'; 8 | import 'package:flutter/widgets.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'default_constants.dart'; 11 | import 'package:flutter_wanandroid/widgets/pull_to_refresh/pull_to_refresh.dart'; 12 | import 'package:meta/meta.dart'; 13 | 14 | abstract class Wrapper extends StatefulWidget { 15 | final ValueNotifier modeListener; 16 | 17 | final IndicatorBuilder builder; 18 | 19 | final bool up; 20 | 21 | final double triggerDistance; 22 | 23 | bool get _isRefreshing => this.mode == RefreshStatus.refreshing; 24 | 25 | bool get _isComplete => 26 | this.mode != RefreshStatus.idle && 27 | this.mode != RefreshStatus.refreshing && 28 | this.mode != RefreshStatus.canRefresh; 29 | 30 | int get mode => this.modeListener.value; 31 | 32 | set mode(int mode) => this.modeListener.value = mode; 33 | 34 | Wrapper( 35 | {Key key, 36 | @required this.up, 37 | @required this.modeListener, 38 | this.builder, 39 | this.triggerDistance}) 40 | : assert(up != null, modeListener != null), 41 | super(key: key); 42 | 43 | bool _isScrollToOutSide(ScrollNotification notification) { 44 | if (up) { 45 | if (notification.metrics.minScrollExtent - notification.metrics.pixels > 46 | 0) { 47 | return true; 48 | } 49 | } else { 50 | if (notification.metrics.pixels - notification.metrics.maxScrollExtent > 51 | 0) { 52 | return true; 53 | } 54 | } 55 | return false; 56 | } 57 | } 58 | 59 | //idle,refreshing,completed,failed,canRefresh 60 | class RefreshWrapper extends Wrapper { 61 | final int completeDuration; 62 | 63 | final Function onOffsetChange; 64 | 65 | final double visibleRange; 66 | 67 | RefreshWrapper({ 68 | Key key, 69 | IndicatorBuilder builder, 70 | ValueNotifier modeLis, 71 | this.onOffsetChange, 72 | this.completeDuration: default_completeDuration, 73 | double triggerDistance, 74 | this.visibleRange: default_VisibleRange, 75 | bool up: true, 76 | }) : assert(up != null), 77 | super( 78 | up: up, 79 | key: key, 80 | modeListener: modeLis, 81 | builder: builder, 82 | triggerDistance: triggerDistance, 83 | ); 84 | 85 | @override 86 | State createState() { 87 | // TODO: implement createState 88 | return new RefreshWrapperState(); 89 | } 90 | } 91 | 92 | class RefreshWrapperState extends State 93 | with TickerProviderStateMixin 94 | implements GestureProcessor { 95 | AnimationController _sizeController; 96 | 97 | /* 98 | up indicate drag from top (pull down) 99 | */ 100 | void _dismiss() { 101 | /* 102 | why the value is 0.00001? 103 | If this value is 0, no controls will 104 | cause Flutter to automatically retrieve widget. 105 | */ 106 | _sizeController.animateTo(minSpace).then((Null val) { 107 | widget.mode = RefreshStatus.idle; 108 | }); 109 | } 110 | 111 | int get mode => widget.modeListener.value; 112 | 113 | double _measure(ScrollNotification notification) { 114 | if (widget.up) { 115 | return (notification.metrics.minScrollExtent - 116 | notification.metrics.pixels) / 117 | widget.triggerDistance; 118 | } else { 119 | return (notification.metrics.pixels - 120 | notification.metrics.maxScrollExtent) / 121 | widget.triggerDistance; 122 | } 123 | } 124 | 125 | @override 126 | void onDragStart(ScrollStartNotification notification) { 127 | // TODO: implement onDragStart 128 | } 129 | 130 | @override 131 | void onDragMove(ScrollUpdateNotification notification) { 132 | // TODO: implement onDragMove 133 | if (!widget._isScrollToOutSide(notification)) { 134 | return; 135 | } 136 | if (widget._isComplete || widget._isRefreshing) return; 137 | 138 | double offset = _measure(notification); 139 | if (offset >= 1.0) { 140 | widget.mode = RefreshStatus.canRefresh; 141 | } else { 142 | widget.mode = RefreshStatus.idle; 143 | } 144 | } 145 | 146 | @override 147 | void onDragEnd(ScrollNotification notification) { 148 | // TODO: implement onDragEnd 149 | if (!widget._isScrollToOutSide(notification)) { 150 | return; 151 | } 152 | if (widget._isComplete || widget._isRefreshing) return; 153 | bool reachMax = _measure(notification) >= 1.0; 154 | if (!reachMax) { 155 | _sizeController.animateTo(0.0); 156 | return; 157 | } else { 158 | widget.mode = RefreshStatus.refreshing; 159 | } 160 | } 161 | 162 | @override 163 | void initState() { 164 | // TODO: implement initState 165 | super.initState(); 166 | this._sizeController = new AnimationController( 167 | vsync: this, 168 | lowerBound: minSpace, 169 | duration: const Duration(milliseconds: spaceAnimateMill)); 170 | widget.modeListener.addListener(() { 171 | switch (mode) { 172 | case RefreshStatus.refreshing: 173 | _sizeController.value = 1.0; 174 | break; 175 | case RefreshStatus.completed: 176 | new Future.delayed( 177 | new Duration(milliseconds: widget.completeDuration), () { 178 | _dismiss(); 179 | }); 180 | break; 181 | case RefreshStatus.failed: 182 | new Future.delayed( 183 | new Duration(milliseconds: widget.completeDuration), () { 184 | _dismiss(); 185 | }).then((val) { 186 | widget.mode = RefreshStatus.idle; 187 | }); 188 | break; 189 | } 190 | setState(() {}); 191 | }); 192 | } 193 | 194 | @override 195 | Widget build(BuildContext context) { 196 | // TODO: implement build 197 | if (widget.up) { 198 | return new Column( 199 | children: [ 200 | new SizeTransition( 201 | sizeFactor: _sizeController, 202 | child: new Container(height: widget.visibleRange), 203 | ), 204 | widget.builder(context, widget.mode) 205 | ], 206 | ); 207 | } 208 | return new Column( 209 | children: [ 210 | widget.builder(context, widget.mode), 211 | new SizeTransition( 212 | sizeFactor: _sizeController, 213 | child: new Container(height: widget.visibleRange), 214 | ) 215 | ], 216 | ); 217 | } 218 | } 219 | 220 | //status: failed,nomore,idle,refreshing 221 | class LoadWrapper extends Wrapper { 222 | final bool autoLoad; 223 | 224 | LoadWrapper( 225 | {Key key, 226 | @required bool up, 227 | @required ValueNotifier modeListener, 228 | double triggerDistance, 229 | this.autoLoad, 230 | IndicatorBuilder builder}) 231 | : assert(up != null, modeListener != null), 232 | super( 233 | key: key, 234 | up: up, 235 | builder: builder, 236 | modeListener: modeListener, 237 | triggerDistance: triggerDistance, 238 | ); 239 | 240 | @override 241 | State createState() { 242 | // TODO: implement createState 243 | return new LoadWrapperState(); 244 | } 245 | } 246 | 247 | class LoadWrapperState extends State implements GestureProcessor { 248 | @override 249 | Widget build(BuildContext context) { 250 | // TODO: implement build 251 | return widget.builder(context, widget.mode); 252 | } 253 | 254 | @override 255 | void initState() { 256 | // TODO: implement initState 257 | super.initState(); 258 | widget.modeListener.addListener(() { 259 | setState(() {}); 260 | }); 261 | } 262 | 263 | @override 264 | void onDragStart(ScrollStartNotification notification) { 265 | // TODO: implement onDragStart 266 | } 267 | 268 | @override 269 | void onDragMove(ScrollUpdateNotification notification) { 270 | // TODO: implement onDragMove 271 | // if (!widget._isScrollToOutSide(notification)) { 272 | // return; 273 | // } 274 | if (widget._isRefreshing || widget._isComplete) return; 275 | if (widget.autoLoad) { 276 | if (widget.up && notification.metrics.extentBefore <= widget.triggerDistance) 277 | widget.mode = RefreshStatus.refreshing; 278 | if (!widget.up && notification.metrics.extentAfter <= widget.triggerDistance) 279 | widget.mode = RefreshStatus.refreshing; 280 | } 281 | } 282 | 283 | @override 284 | void onDragEnd(ScrollNotification notification) { 285 | // TODO: implement onDragEnd 286 | } 287 | } 288 | 289 | abstract class GestureProcessor { 290 | void onDragStart(ScrollStartNotification notification); 291 | 292 | void onDragMove(ScrollUpdateNotification notification); 293 | 294 | void onDragEnd(ScrollNotification notification); 295 | } 296 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/widgets/pull_to_refresh/src/internals/refresh_physics.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Jpeng 3 | Email: peng8350@gmail.com 4 | createTime:2018-05-02 14:39 5 | */ 6 | 7 | import 'package:flutter/widgets.dart'; 8 | import 'dart:math' as math; 9 | import 'package:flutter/material.dart'; 10 | 11 | /* 12 | this class is copy from BouncingScrollPhysics, 13 | because it doesn't fit my idea, 14 | Fixed the problem that child parts could not be dragged without data. 15 | */ 16 | class RefreshScrollPhysics extends ScrollPhysics { 17 | 18 | final bool enableOverScroll; 19 | 20 | /// Creates scroll physics that bounce back from the edge. 21 | const RefreshScrollPhysics({ScrollPhysics parent,this.enableOverScroll:true}) : super(parent: parent); 22 | 23 | @override 24 | RefreshScrollPhysics applyTo(ScrollPhysics ancestor) { 25 | return new RefreshScrollPhysics(parent: buildParent(ancestor),enableOverScroll: enableOverScroll); 26 | } 27 | 28 | /// The multiple applied to overscroll to make it appear that scrolling past 29 | /// the edge of the scrollable contents is harder than scrolling the list. 30 | /// This is done by reducing the ratio of the scroll effect output vs the 31 | /// scroll gesture input. 32 | /// 33 | /// This factor starts at 0.52 and progressively becomes harder to overscroll 34 | /// as more of the area past the edge is dragged in (represented by an increasing 35 | /// `overscrollFraction` which starts at 0 when there is no overscroll). 36 | double frictionFactor(double overscrollFraction) => 37 | 0.52 * math.pow(1 - overscrollFraction, 2); 38 | 39 | @override 40 | bool shouldAcceptUserOffset(ScrollMetrics position) { 41 | // TODO: implement shouldAcceptUserOffset 42 | return true; 43 | } 44 | 45 | @override 46 | double applyPhysicsToUserOffset(ScrollMetrics position, double offset) { 47 | assert(offset != 0.0); 48 | assert(position.minScrollExtent <= position.maxScrollExtent); 49 | 50 | if (!position.outOfRange) return offset; 51 | 52 | final double overscrollPastStart = 53 | math.max(position.minScrollExtent - position.pixels, 0.0); 54 | final double overscrollPastEnd = 55 | math.max(position.pixels - position.maxScrollExtent, 0.0); 56 | final double overscrollPast = 57 | math.max(overscrollPastStart, overscrollPastEnd); 58 | final bool easing = (overscrollPastStart > 0.0 && offset < 0.0) || 59 | (overscrollPastEnd > 0.0 && offset > 0.0); 60 | 61 | final double friction = easing 62 | // Apply less resistance when easing the overscroll vs tensioning. 63 | ? frictionFactor( 64 | (overscrollPast - offset.abs()) / position.viewportDimension) 65 | : frictionFactor(overscrollPast / position.viewportDimension); 66 | final double direction = offset.sign; 67 | return direction * _applyFriction(overscrollPast, offset.abs(), friction); 68 | } 69 | 70 | static double _applyFriction( 71 | double extentOutside, double absDelta, double gamma) { 72 | 73 | assert(absDelta > 0); 74 | double total = 0.0; 75 | if (extentOutside > 0) { 76 | final double deltaToLimit = extentOutside / gamma; 77 | if (absDelta < deltaToLimit) return absDelta * gamma; 78 | total += extentOutside; 79 | absDelta -= deltaToLimit; 80 | } 81 | return total + absDelta; 82 | } 83 | 84 | @override 85 | double applyBoundaryConditions(ScrollMetrics position, double value) { 86 | if(!enableOverScroll) { 87 | if (value < position.pixels && 88 | position.pixels <= position.minScrollExtent) // underscroll 89 | return value - position.pixels; 90 | if (value < position.minScrollExtent && 91 | position.minScrollExtent < position.pixels) // hit top edge 92 | return value - position.minScrollExtent; 93 | if (position.maxScrollExtent <= position.pixels && 94 | position.pixels < value) // overscroll 95 | return value - position.pixels; 96 | 97 | if (position.pixels < position.maxScrollExtent && 98 | position.maxScrollExtent < value) // hit bottom edge 99 | return value - position.maxScrollExtent; 100 | } 101 | return 0.0; 102 | 103 | } 104 | 105 | @override 106 | Simulation createBallisticSimulation( 107 | ScrollMetrics position, double velocity) { 108 | final Tolerance tolerance = this.tolerance; 109 | if (velocity.abs() >= tolerance.velocity || position.outOfRange) { 110 | return new BouncingScrollSimulation( 111 | spring: spring, 112 | position: position.pixels, 113 | velocity: velocity * 114 | 0.91, // TODO(abarth): We should move this constant closer to the drag end. 115 | leadingExtent: position.minScrollExtent, 116 | trailingExtent: position.maxScrollExtent, 117 | tolerance: tolerance, 118 | ); 119 | } 120 | return null; 121 | } 122 | 123 | // The ballistic simulation here decelerates more slowly than the one for 124 | // ClampingScrollPhysics so we require a more deliberate input gesture 125 | // to trigger a fling. 126 | @override 127 | double get minFlingVelocity => 2.5 * 2.0; 128 | 129 | // Methodology: 130 | // 1- Use https://github.com/flutter/scroll_overlay to test with Flutter and 131 | // platform scroll views superimposed. 132 | // 2- Record incoming speed and make rapid flings in the test app. 133 | // 3- If the scrollables stopped overlapping at any moment, adjust the desired 134 | // output value of this function at that input speed. 135 | // 4- Feed new input/output set into a power curve fitter. Change function 136 | // and repeat from 2. 137 | // 5- Repeat from 2 with medium and slow flings. 138 | /// Momentum build-up function that mimics iOS's scroll speed increase with repeated flings. 139 | /// 140 | /// The velocity of the last fling is not an important factor. Existing speed 141 | /// and (related) time since last fling are factors for the velocity transfer 142 | /// calculations. 143 | @override 144 | double carriedMomentum(double existingVelocity) { 145 | return existingVelocity.sign * 146 | math.min(0.000816 * math.pow(existingVelocity.abs(), 1.967).toDouble(), 147 | 40000.0); 148 | } 149 | 150 | // Eyeballed from observation to counter the effect of an unintended scroll 151 | // from the natural motion of lifting the finger after a scroll. 152 | @override 153 | double get dragStartDistanceMotionThreshold => 3.5; 154 | } 155 | -------------------------------------------------------------------------------- /flutter_wanandroid/lib/widgets/pull_to_refresh/src/smart_refresher.dart: -------------------------------------------------------------------------------- 1 | /* 2 | Author: Jpeng 3 | Email: peng8350@gmail.com 4 | createTime:2018-05-01 11:39 5 | */ 6 | 7 | import 'package:flutter/material.dart'; 8 | import 'package:flutter/scheduler.dart'; 9 | import 'internals/default_constants.dart'; 10 | import 'package:flutter/widgets.dart'; 11 | import 'package:flutter/foundation.dart'; 12 | import '../src/internals/indicator_config.dart'; 13 | import '../src/internals/indicator_wrap.dart'; 14 | import '../src/internals/refresh_physics.dart'; 15 | 16 | 17 | 18 | enum WrapperType { Refresh, Loading } 19 | 20 | class RefreshStatus { 21 | static const int idle = 0; 22 | static const int canRefresh = 1; 23 | static const int refreshing = 2; 24 | static const int completed = 3; 25 | static const int failed = 4; 26 | static const int noMore = 5; 27 | } 28 | 29 | 30 | /* 31 | This is the most important component that provides drop-down refresh and up loading. 32 | */ 33 | class SmartRefresher extends StatefulWidget { 34 | //indicate your listView 35 | final Widget child; 36 | 37 | final IndicatorBuilder headerBuilder; 38 | final IndicatorBuilder footerBuilder; 39 | // configure your header and footer 40 | final Config headerConfig, footerConfig; 41 | // This bool will affect whether or not to have the function of drop-up load. 42 | final bool enablePullUp; 43 | //This bool will affect whether or not to have the function of drop-down refresh. 44 | final bool enablePullDown; 45 | // if open OverScroll if you use RefreshIndicator and LoadFooter 46 | final bool enableOverScroll; 47 | // upper and downer callback when you drag out of the distance 48 | final OnRefresh onRefresh; 49 | // This method will callback when the indicator changes from edge to edge. 50 | final OnOffsetChange onOffsetChange; 51 | //controll inner state 52 | final RefreshController controller; 53 | 54 | SmartRefresher({ 55 | Key key, 56 | @required this.child, 57 | this.headerBuilder, 58 | this.footerBuilder, 59 | RefreshController controller, 60 | this.headerConfig: const RefreshConfig(), 61 | this.footerConfig: const LoadConfig(), 62 | this.enableOverScroll:default_enableOverScroll, 63 | this.enablePullDown: default_enablePullDown, 64 | this.enablePullUp: default_enablePullUp, 65 | this.onRefresh, 66 | this.onOffsetChange, 67 | }) : assert(child != null), 68 | controller = controller ?? new RefreshController(),super(key: key); 69 | 70 | 71 | @override 72 | _SmartRefresherState createState() => new _SmartRefresherState(); 73 | } 74 | 75 | class _SmartRefresherState extends State { 76 | // listen the listen offset or on... 77 | ScrollController _scrollController; 78 | // the bool will check the user if dragging on the screen. 79 | bool _isDragging = false; 80 | // key to get height header of footer 81 | final GlobalKey _headerKey = new GlobalKey(), _footerKey = new GlobalKey(); 82 | // the height must be equals your headerBuilder 83 | double _headerHeight = 0.0, _footerHeight = 0.0; 84 | 85 | ValueNotifier offsetLis = new ValueNotifier(0.0); 86 | 87 | ValueNotifier topModeLis = new ValueNotifier(0); 88 | 89 | ValueNotifier bottomModeLis =new ValueNotifier(0); 90 | 91 | //handle the scrollStartEvent 92 | bool _handleScrollStart(ScrollStartNotification notification) { 93 | // This is used to interupt useless callback when the pull up load rolls back. 94 | if ((notification.metrics.outOfRange)) { 95 | return false; 96 | } 97 | GestureProcessor topWrap = _headerKey.currentState as GestureProcessor; 98 | GestureProcessor bottomWrap = _footerKey.currentState as GestureProcessor; 99 | if (widget.enablePullUp) bottomWrap.onDragStart(notification); 100 | if (widget.enablePullDown) topWrap.onDragStart(notification); 101 | return false; 102 | } 103 | 104 | //handle the scrollMoveEvent 105 | bool _handleScrollMoving(ScrollUpdateNotification notification) { 106 | bool down = _isPullDown(notification); 107 | if (down) { 108 | if (widget.onOffsetChange != null) 109 | widget.onOffsetChange(notification.metrics.extentBefore == 0, 110 | notification.metrics.minScrollExtent - notification.metrics.pixels); 111 | } else { 112 | if (widget.onOffsetChange != null) 113 | widget.onOffsetChange(notification.metrics.extentAfter == 0, 114 | notification.metrics.pixels - notification.metrics.maxScrollExtent); 115 | } 116 | if (_measure(notification) != -1.0) offsetLis.value = _measure(notification); 117 | GestureProcessor topWrap = _headerKey.currentState as GestureProcessor; 118 | GestureProcessor bottomWrap = _footerKey.currentState as GestureProcessor; 119 | if (widget.enablePullUp) bottomWrap.onDragMove(notification); 120 | if (widget.enablePullDown) topWrap.onDragMove(notification); 121 | return false; 122 | } 123 | 124 | //handle the scrollEndEvent 125 | bool _handleScrollEnd(ScrollNotification notification) { 126 | GestureProcessor topWrap = _headerKey.currentState as GestureProcessor; 127 | GestureProcessor bottomWrap = _footerKey.currentState as GestureProcessor; 128 | if (widget.enablePullUp) bottomWrap.onDragEnd(notification); 129 | if (widget.enablePullDown) topWrap.onDragEnd(notification); 130 | return false; 131 | } 132 | 133 | bool _dispatchScrollEvent(ScrollNotification notification) { 134 | // when is scroll in the ScrollInside,nothing to do 135 | if ((!_isPullUp(notification) && !_isPullDown(notification))) return false; 136 | if (notification is ScrollStartNotification) { 137 | return _handleScrollStart(notification); 138 | } 139 | if (notification is ScrollUpdateNotification) { 140 | //if dragDetails is null,This represents the user's finger out of the screen 141 | if (notification.dragDetails == null) { 142 | return _handleScrollEnd(notification); 143 | } else if (notification.dragDetails != null) { 144 | return _handleScrollMoving(notification); 145 | } 146 | } 147 | if (notification is ScrollEndNotification) { 148 | print("end"); 149 | _handleScrollEnd(notification); 150 | } 151 | 152 | return false; 153 | } 154 | 155 | 156 | //check user is pulling up 157 | bool _isPullUp(ScrollNotification noti) { 158 | return noti.metrics.pixels < 0; 159 | } 160 | 161 | //check user is pulling down 162 | bool _isPullDown(ScrollNotification noti) { 163 | return noti.metrics.pixels > 0; 164 | } 165 | 166 | double _measure(ScrollNotification notification) { 167 | if (notification.metrics.minScrollExtent - notification.metrics.pixels > 168 | 0) { 169 | return (notification.metrics.minScrollExtent - 170 | notification.metrics.pixels) / 171 | widget.headerConfig.triggerDistance; 172 | } else if (notification.metrics.pixels - 173 | notification.metrics.maxScrollExtent > 174 | 0) { 175 | return (notification.metrics.pixels - 176 | notification.metrics.maxScrollExtent) / 177 | widget.footerConfig.triggerDistance; 178 | } 179 | return -1.0; 180 | } 181 | 182 | void _init() { 183 | _scrollController = new ScrollController(); 184 | widget.controller.scrollController =_scrollController; 185 | SchedulerBinding.instance.addPostFrameCallback((_) { 186 | _onAfterBuild(); 187 | }); 188 | widget.controller._headerMode = topModeLis; 189 | widget.controller._footerMode = bottomModeLis; 190 | } 191 | 192 | _didChangeMode(bool up, ValueNotifier mode) { 193 | switch (mode.value) { 194 | case RefreshStatus.refreshing: 195 | if (widget.onRefresh != null) { 196 | widget.onRefresh(up); 197 | } 198 | if(up&&widget.headerConfig is RefreshConfig){ 199 | RefreshConfig config = widget.headerConfig as RefreshConfig; 200 | _scrollController.jumpTo(_scrollController.offset+config.visibleRange); 201 | } 202 | break; 203 | } 204 | } 205 | 206 | void _onAfterBuild() { 207 | if(widget.headerConfig is LoadConfig){ 208 | if((widget.headerConfig as LoadConfig).bottomWhenBuild){ 209 | _scrollController.jumpTo(-(_scrollController.position.pixels-_scrollController.position.maxScrollExtent)); 210 | } 211 | } 212 | 213 | topModeLis.addListener(() { 214 | _didChangeMode(true, topModeLis); 215 | }); 216 | bottomModeLis.addListener(() { 217 | _didChangeMode(false, bottomModeLis); 218 | }); 219 | setState(() { 220 | if (widget.enablePullDown) 221 | _headerHeight = _headerKey.currentContext.size.height; 222 | if (widget.enablePullUp) { 223 | _footerHeight = _footerKey.currentContext.size.height; 224 | } 225 | }); 226 | } 227 | 228 | @override 229 | void dispose() { 230 | // TODO: implement dispose 231 | _scrollController.dispose(); 232 | super.dispose(); 233 | } 234 | 235 | @override 236 | void initState() { 237 | // TODO: implement initState 238 | super.initState(); 239 | _init(); 240 | } 241 | 242 | Widget _buildWrapperByConfig(Config config, bool up) { 243 | if (config is LoadConfig) { 244 | return new LoadWrapper( 245 | key: up ? _headerKey : _footerKey, 246 | modeListener: up ? topModeLis : bottomModeLis, 247 | up: up, 248 | autoLoad: config.autoLoad, 249 | triggerDistance: config.triggerDistance, 250 | builder: up 251 | ? widget.headerBuilder 252 | : widget.footerBuilder, 253 | ); 254 | } else if (config is RefreshConfig) { 255 | return new RefreshWrapper( 256 | key: up ? _headerKey : _footerKey, 257 | modeLis: up ? topModeLis : bottomModeLis, 258 | up: up, 259 | completeDuration: config.completeDuration, 260 | triggerDistance: config.triggerDistance, 261 | visibleRange: config.visibleRange, 262 | builder: up 263 | ? widget.headerBuilder 264 | : widget.footerBuilder, 265 | ); 266 | } 267 | return new Container(); 268 | } 269 | 270 | @override 271 | void didUpdateWidget(SmartRefresher oldWidget) { 272 | // TODO: implement didUpdateWidget 273 | widget.controller._headerMode = topModeLis; 274 | widget.controller._footerMode = bottomModeLis; 275 | widget.controller.scrollController =_scrollController; 276 | super.didUpdateWidget(oldWidget); 277 | } 278 | 279 | 280 | @override 281 | Widget build(BuildContext context) { 282 | return new LayoutBuilder(builder: (context, cons) { 283 | return new Stack( 284 | children: [ 285 | new Positioned( 286 | top: !widget.enablePullDown||widget.headerConfig is LoadConfig ? 0.0 : -_headerHeight, 287 | bottom: !widget.enablePullUp||widget.footerConfig is LoadConfig ? 0.0 : -_footerHeight, 288 | left: 0.0, 289 | right: 0.0, 290 | child: new NotificationListener( 291 | child: new SingleChildScrollView( 292 | controller: _scrollController, 293 | physics: new RefreshScrollPhysics(enableOverScroll:widget.enableOverScroll ), 294 | child: new Column( 295 | children: [ 296 | widget.headerBuilder != null && widget.enablePullDown 297 | ? _buildWrapperByConfig(widget.headerConfig, true) 298 | : new Container(), 299 | new ConstrainedBox( 300 | constraints: new BoxConstraints( 301 | maxHeight: double.MAX_FINITE-1.0, 302 | minHeight: cons.biggest.height), 303 | child: widget.child, 304 | ), 305 | widget.footerBuilder != null && widget.enablePullUp 306 | ? _buildWrapperByConfig(widget.footerConfig, false) 307 | : new Container() 308 | ], 309 | )), 310 | onNotification: _dispatchScrollEvent, 311 | )), 312 | ], 313 | ); 314 | }); 315 | } 316 | } 317 | 318 | 319 | abstract class Indicator extends StatefulWidget { 320 | 321 | final int mode; 322 | 323 | const Indicator({Key key,this.mode}):super(key:key); 324 | } 325 | 326 | class RefreshController{ 327 | 328 | ValueNotifier _headerMode ; 329 | ValueNotifier _footerMode; 330 | ScrollController scrollController; 331 | 332 | void requestRefresh(bool up){ 333 | if(up) { 334 | if (_headerMode.value == RefreshStatus.idle) 335 | _headerMode.value = RefreshStatus.refreshing; 336 | } 337 | else { 338 | if (_footerMode.value == RefreshStatus.idle) { 339 | _footerMode.value = RefreshStatus.refreshing; 340 | } 341 | } 342 | } 343 | 344 | void scrollTo(double offset){ 345 | scrollController.jumpTo(offset); 346 | } 347 | 348 | void sendBack(bool up,int mode){ 349 | if(up){ 350 | _headerMode.value = mode; 351 | } 352 | else{ 353 | _footerMode.value = mode; 354 | } 355 | } 356 | 357 | int get headerMode => _headerMode.value; 358 | 359 | int get footerMode => _footerMode.value; 360 | 361 | isRefresh(bool up){ 362 | if(up){ 363 | return _headerMode.value==RefreshStatus 364 | .refreshing; 365 | } 366 | else{ 367 | return _footerMode.value==RefreshStatus 368 | .refreshing; 369 | } 370 | } 371 | 372 | 373 | } -------------------------------------------------------------------------------- /flutter_wanandroid/lib/widgets/xbanner.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | 4 | /// 5 | ///XBanner 中的点击事件 6 | typedef void PageClick(int i); 7 | 8 | /// Banner widget 9 | class XBanner extends StatefulWidget { 10 | final List _pages; 11 | final PageClick pageClick; 12 | final Duration bannerDuration; 13 | final Duration bannerAnimationDuration; 14 | 15 | /// [_pages] 为 Banner 中需要展示的页面集合。 16 | /// [pageClick] 为 Banner 中页面的点击事件回调。 17 | /// [bannerDuration] Banner 中页面切换的时间间隔,默认为 2 秒。 18 | /// [bannerAnimationDuration] Banner 中每次页面切换的动画时间,默认为 1 秒。 19 | XBanner(this._pages, 20 | {this.pageClick, 21 | this.bannerDuration = const Duration(seconds: 2), 22 | this.bannerAnimationDuration = const Duration(milliseconds: 1000)}); 23 | 24 | @override 25 | State createState() { 26 | return new XBannerState(); 27 | } 28 | } 29 | 30 | class XBannerState extends State with SingleTickerProviderStateMixin { 31 | PageController _pageController = new PageController(); 32 | Timer _timer; 33 | int _currentPage = 0; 34 | bool reverse = false; 35 | GlobalKey<_XBannerTipState> _xBannerTipStateKey = new GlobalKey(); 36 | 37 | @override 38 | void initState() { 39 | super.initState(); 40 | _timer = new Timer.periodic(widget.bannerDuration, (timmer) { 41 | _pageController.animateToPage(_currentPage, 42 | duration: widget.bannerAnimationDuration, curve: Curves.linear); 43 | if (!reverse) { 44 | _currentPage += 1; 45 | if (_currentPage == widget._pages.length) { 46 | _currentPage -= 1; 47 | reverse = true; 48 | } 49 | } else { 50 | _currentPage -= 1; 51 | if (_currentPage < 0) { 52 | _currentPage += 1; 53 | reverse = false; 54 | } 55 | } 56 | }); 57 | } 58 | 59 | @override 60 | void dispose() { 61 | super.dispose(); 62 | _timer.cancel(); 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | List pageWithClick = []; 68 | for (var i = 0; i < widget._pages.length; i++) { 69 | pageWithClick.add(new InkWell( 70 | child: widget._pages[i], 71 | onTap: () { 72 | if (widget.pageClick != null) { 73 | widget.pageClick(i); 74 | } 75 | }, 76 | )); 77 | } 78 | 79 | ///两层,PageView+XBannerTip 80 | return new Stack( 81 | alignment: Alignment.topLeft, 82 | children: [ 83 | new NotificationListener( 84 | onNotification: (ScrollNotification scrollNotification) { 85 | // if (scrollNotification is ScrollUpdateNotification) { 86 | // print("offset : ${_pageController.offset}"); 87 | // print("position : ${_pageController.position.pixels}"); 88 | // print("page : ${_pageController.page}"); 89 | // } 90 | return false; 91 | }, 92 | child: new PageView( 93 | controller: _pageController, 94 | children: pageWithClick, 95 | onPageChanged: (index) { 96 | _currentPage = index; 97 | 98 | ///回调方法 99 | _xBannerTipStateKey.currentState.changeTipIndex(index); 100 | }, 101 | )), 102 | new Align( 103 | child: new _XBannerTip( 104 | widget._pages.length, 105 | key: _xBannerTipStateKey, 106 | ), 107 | alignment: Alignment.bottomCenter, 108 | ) 109 | ], 110 | ); 111 | } 112 | } 113 | 114 | ///banner提示控件 115 | class _XBannerTip extends StatefulWidget { 116 | final int _count; 117 | 118 | _XBannerTip(this._count, {Key key}) : super(key: key); 119 | 120 | @override 121 | State createState() { 122 | return new _XBannerTipState(); 123 | } 124 | } 125 | 126 | class _XBannerTipState extends State<_XBannerTip> { 127 | int _index = 0; 128 | 129 | changeTipIndex(int index) { 130 | setState(() { 131 | _index = index; 132 | }); 133 | } 134 | 135 | @override 136 | Widget build(BuildContext context) { 137 | List childs = []; 138 | for (int i = 0; i < widget._count; i++) { 139 | childs.add(new SizedBox( 140 | width: 7.0, 141 | height: 7.0, 142 | child: new DecoratedBox( 143 | decoration: new BoxDecoration( 144 | shape: BoxShape.circle, 145 | color: i == _index ? Colors.black : Colors.grey, 146 | )), 147 | // child: new Container( 148 | // color: i == _index ? Colors.black : Colors.grey, 149 | // ), 150 | )); 151 | } 152 | 153 | return new SizedBox( 154 | width: widget._count * 15.0, 155 | height: 15.0, 156 | child: new Row( 157 | children: childs, 158 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 159 | ), 160 | ); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /flutter_wanandroid/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | after_layout: 5 | dependency: "direct main" 6 | description: 7 | name: after_layout 8 | url: "https://pub.flutter-io.cn" 9 | source: hosted 10 | version: "1.0.2" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.flutter-io.cn" 16 | source: hosted 17 | version: "0.31.2-alpha.2" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.flutter-io.cn" 23 | source: hosted 24 | version: "1.4.3" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.flutter-io.cn" 30 | source: hosted 31 | version: "2.0.7" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.flutter-io.cn" 37 | source: hosted 38 | version: "1.0.3" 39 | cached_network_image: 40 | dependency: "direct main" 41 | description: 42 | name: cached_network_image 43 | url: "https://pub.flutter-io.cn" 44 | source: hosted 45 | version: "0.4.1+1" 46 | charcode: 47 | dependency: transitive 48 | description: 49 | name: charcode 50 | url: "https://pub.flutter-io.cn" 51 | source: hosted 52 | version: "1.1.1" 53 | collection: 54 | dependency: transitive 55 | description: 56 | name: collection 57 | url: "https://pub.flutter-io.cn" 58 | source: hosted 59 | version: "1.14.6" 60 | convert: 61 | dependency: transitive 62 | description: 63 | name: convert 64 | url: "https://pub.flutter-io.cn" 65 | source: hosted 66 | version: "2.0.1" 67 | cookie_jar: 68 | dependency: transitive 69 | description: 70 | name: cookie_jar 71 | url: "https://pub.flutter-io.cn" 72 | source: hosted 73 | version: "0.0.3" 74 | crypto: 75 | dependency: transitive 76 | description: 77 | name: crypto 78 | url: "https://pub.flutter-io.cn" 79 | source: hosted 80 | version: "2.0.3" 81 | csslib: 82 | dependency: transitive 83 | description: 84 | name: csslib 85 | url: "https://pub.flutter-io.cn" 86 | source: hosted 87 | version: "0.14.4" 88 | cupertino_icons: 89 | dependency: "direct main" 90 | description: 91 | name: cupertino_icons 92 | url: "https://pub.flutter-io.cn" 93 | source: hosted 94 | version: "0.1.2" 95 | dio: 96 | dependency: "direct main" 97 | description: 98 | name: dio 99 | url: "https://pub.flutter-io.cn" 100 | source: hosted 101 | version: "0.0.12" 102 | flutter: 103 | dependency: "direct main" 104 | description: flutter 105 | source: sdk 106 | version: "0.0.0" 107 | flutter_cache_manager: 108 | dependency: transitive 109 | description: 110 | name: flutter_cache_manager 111 | url: "https://pub.flutter-io.cn" 112 | source: hosted 113 | version: "0.1.1" 114 | flutter_test: 115 | dependency: "direct dev" 116 | description: flutter 117 | source: sdk 118 | version: "0.0.0" 119 | flutter_webview_plugin: 120 | dependency: "direct main" 121 | description: 122 | name: flutter_webview_plugin 123 | url: "https://pub.flutter-io.cn" 124 | source: hosted 125 | version: "0.1.5" 126 | front_end: 127 | dependency: transitive 128 | description: 129 | name: front_end 130 | url: "https://pub.flutter-io.cn" 131 | source: hosted 132 | version: "0.1.0-alpha.12" 133 | glob: 134 | dependency: transitive 135 | description: 136 | name: glob 137 | url: "https://pub.flutter-io.cn" 138 | source: hosted 139 | version: "1.1.5" 140 | html: 141 | dependency: transitive 142 | description: 143 | name: html 144 | url: "https://pub.flutter-io.cn" 145 | source: hosted 146 | version: "0.13.3" 147 | http: 148 | dependency: transitive 149 | description: 150 | name: http 151 | url: "https://pub.flutter-io.cn" 152 | source: hosted 153 | version: "0.11.3+16" 154 | http_multi_server: 155 | dependency: transitive 156 | description: 157 | name: http_multi_server 158 | url: "https://pub.flutter-io.cn" 159 | source: hosted 160 | version: "2.0.4" 161 | http_parser: 162 | dependency: transitive 163 | description: 164 | name: http_parser 165 | url: "https://pub.flutter-io.cn" 166 | source: hosted 167 | version: "3.1.2" 168 | io: 169 | dependency: transitive 170 | description: 171 | name: io 172 | url: "https://pub.flutter-io.cn" 173 | source: hosted 174 | version: "0.3.2+1" 175 | js: 176 | dependency: transitive 177 | description: 178 | name: js 179 | url: "https://pub.flutter-io.cn" 180 | source: hosted 181 | version: "0.6.1" 182 | kernel: 183 | dependency: transitive 184 | description: 185 | name: kernel 186 | url: "https://pub.flutter-io.cn" 187 | source: hosted 188 | version: "0.3.0-alpha.12" 189 | logging: 190 | dependency: transitive 191 | description: 192 | name: logging 193 | url: "https://pub.flutter-io.cn" 194 | source: hosted 195 | version: "0.11.3+1" 196 | matcher: 197 | dependency: transitive 198 | description: 199 | name: matcher 200 | url: "https://pub.flutter-io.cn" 201 | source: hosted 202 | version: "0.12.2+1" 203 | meta: 204 | dependency: transitive 205 | description: 206 | name: meta 207 | url: "https://pub.flutter-io.cn" 208 | source: hosted 209 | version: "1.1.5" 210 | mime: 211 | dependency: transitive 212 | description: 213 | name: mime 214 | url: "https://pub.flutter-io.cn" 215 | source: hosted 216 | version: "0.9.6" 217 | multi_server_socket: 218 | dependency: transitive 219 | description: 220 | name: multi_server_socket 221 | url: "https://pub.flutter-io.cn" 222 | source: hosted 223 | version: "1.0.1" 224 | node_preamble: 225 | dependency: transitive 226 | description: 227 | name: node_preamble 228 | url: "https://pub.flutter-io.cn" 229 | source: hosted 230 | version: "1.4.1" 231 | package_config: 232 | dependency: transitive 233 | description: 234 | name: package_config 235 | url: "https://pub.flutter-io.cn" 236 | source: hosted 237 | version: "1.0.3" 238 | package_resolver: 239 | dependency: transitive 240 | description: 241 | name: package_resolver 242 | url: "https://pub.flutter-io.cn" 243 | source: hosted 244 | version: "1.0.2" 245 | path: 246 | dependency: transitive 247 | description: 248 | name: path 249 | url: "https://pub.flutter-io.cn" 250 | source: hosted 251 | version: "1.5.1" 252 | path_provider: 253 | dependency: transitive 254 | description: 255 | name: path_provider 256 | url: "https://pub.flutter-io.cn" 257 | source: hosted 258 | version: "0.4.0" 259 | plugin: 260 | dependency: transitive 261 | description: 262 | name: plugin 263 | url: "https://pub.flutter-io.cn" 264 | source: hosted 265 | version: "0.2.0+2" 266 | pool: 267 | dependency: transitive 268 | description: 269 | name: pool 270 | url: "https://pub.flutter-io.cn" 271 | source: hosted 272 | version: "1.3.4" 273 | pub_semver: 274 | dependency: transitive 275 | description: 276 | name: pub_semver 277 | url: "https://pub.flutter-io.cn" 278 | source: hosted 279 | version: "1.4.1" 280 | quiver: 281 | dependency: transitive 282 | description: 283 | name: quiver 284 | url: "https://pub.flutter-io.cn" 285 | source: hosted 286 | version: "0.29.0+1" 287 | rect_getter: 288 | dependency: "direct main" 289 | description: 290 | name: rect_getter 291 | url: "https://pub.flutter-io.cn" 292 | source: hosted 293 | version: "0.0.1" 294 | shared_preferences: 295 | dependency: "direct main" 296 | description: 297 | name: shared_preferences 298 | url: "https://pub.flutter-io.cn" 299 | source: hosted 300 | version: "0.4.1" 301 | shelf: 302 | dependency: transitive 303 | description: 304 | name: shelf 305 | url: "https://pub.flutter-io.cn" 306 | source: hosted 307 | version: "0.7.3" 308 | shelf_packages_handler: 309 | dependency: transitive 310 | description: 311 | name: shelf_packages_handler 312 | url: "https://pub.flutter-io.cn" 313 | source: hosted 314 | version: "1.0.3" 315 | shelf_static: 316 | dependency: transitive 317 | description: 318 | name: shelf_static 319 | url: "https://pub.flutter-io.cn" 320 | source: hosted 321 | version: "0.2.7" 322 | shelf_web_socket: 323 | dependency: transitive 324 | description: 325 | name: shelf_web_socket 326 | url: "https://pub.flutter-io.cn" 327 | source: hosted 328 | version: "0.2.2" 329 | sky_engine: 330 | dependency: transitive 331 | description: flutter 332 | source: sdk 333 | version: "0.0.99" 334 | source_map_stack_trace: 335 | dependency: transitive 336 | description: 337 | name: source_map_stack_trace 338 | url: "https://pub.flutter-io.cn" 339 | source: hosted 340 | version: "1.1.4" 341 | source_maps: 342 | dependency: transitive 343 | description: 344 | name: source_maps 345 | url: "https://pub.flutter-io.cn" 346 | source: hosted 347 | version: "0.10.5" 348 | source_span: 349 | dependency: transitive 350 | description: 351 | name: source_span 352 | url: "https://pub.flutter-io.cn" 353 | source: hosted 354 | version: "1.4.0" 355 | stack_trace: 356 | dependency: "direct main" 357 | description: 358 | name: stack_trace 359 | url: "https://pub.flutter-io.cn" 360 | source: hosted 361 | version: "1.9.2" 362 | stream_channel: 363 | dependency: transitive 364 | description: 365 | name: stream_channel 366 | url: "https://pub.flutter-io.cn" 367 | source: hosted 368 | version: "1.6.6" 369 | string_scanner: 370 | dependency: transitive 371 | description: 372 | name: string_scanner 373 | url: "https://pub.flutter-io.cn" 374 | source: hosted 375 | version: "1.0.2" 376 | synchronized: 377 | dependency: transitive 378 | description: 379 | name: synchronized 380 | url: "https://pub.flutter-io.cn" 381 | source: hosted 382 | version: "1.4.0" 383 | term_glyph: 384 | dependency: transitive 385 | description: 386 | name: term_glyph 387 | url: "https://pub.flutter-io.cn" 388 | source: hosted 389 | version: "1.0.0" 390 | test: 391 | dependency: transitive 392 | description: 393 | name: test 394 | url: "https://pub.flutter-io.cn" 395 | source: hosted 396 | version: "0.12.37" 397 | typed_data: 398 | dependency: transitive 399 | description: 400 | name: typed_data 401 | url: "https://pub.flutter-io.cn" 402 | source: hosted 403 | version: "1.1.5" 404 | url_launcher: 405 | dependency: "direct main" 406 | description: 407 | name: url_launcher 408 | url: "https://pub.flutter-io.cn" 409 | source: hosted 410 | version: "3.0.1" 411 | utf: 412 | dependency: transitive 413 | description: 414 | name: utf 415 | url: "https://pub.flutter-io.cn" 416 | source: hosted 417 | version: "0.9.0+4" 418 | uuid: 419 | dependency: transitive 420 | description: 421 | name: uuid 422 | url: "https://pub.flutter-io.cn" 423 | source: hosted 424 | version: "0.5.3" 425 | vector_math: 426 | dependency: transitive 427 | description: 428 | name: vector_math 429 | url: "https://pub.flutter-io.cn" 430 | source: hosted 431 | version: "2.0.6" 432 | watcher: 433 | dependency: transitive 434 | description: 435 | name: watcher 436 | url: "https://pub.flutter-io.cn" 437 | source: hosted 438 | version: "0.9.7+7" 439 | web_socket_channel: 440 | dependency: transitive 441 | description: 442 | name: web_socket_channel 443 | url: "https://pub.flutter-io.cn" 444 | source: hosted 445 | version: "1.0.7" 446 | yaml: 447 | dependency: transitive 448 | description: 449 | name: yaml 450 | url: "https://pub.flutter-io.cn" 451 | source: hosted 452 | version: "2.1.13" 453 | sdks: 454 | dart: ">=2.0.0-dev.52.0 <=2.0.0-dev.58.0.flutter-f981f09760" 455 | flutter: ">=0.1.4 <2.0.0" 456 | -------------------------------------------------------------------------------- /flutter_wanandroid/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_wanandroid 2 | description: A new Flutter project for WanAndroid. 3 | 4 | dependencies: 5 | flutter: 6 | sdk: flutter 7 | 8 | # The following adds the Cupertino Icons font to your application. 9 | # Use with the CupertinoIcons class for iOS style icons. 10 | cupertino_icons: ^0.1.0 11 | cached_network_image: ^0.4.1 12 | shared_preferences: ^0.4.1 13 | stack_trace: ^1.9.2 14 | dio: ^0.0.12 15 | flutter_webview_plugin: ^0.1.5 16 | url_launcher: ^3.0.1 17 | # pull_to_refresh: ^1.1.3 18 | rect_getter: ^0.0.1 19 | after_layout: ^1.0.2 20 | 21 | dev_dependencies: 22 | flutter_test: 23 | sdk: flutter 24 | 25 | 26 | # For information on the generic Dart part of this file, see the 27 | # following page: https://www.dartlang.org/tools/pub/pubspec 28 | 29 | # The following section is specific to Flutter. 30 | flutter: 31 | 32 | # The following line ensures that the Material Icons font is 33 | # included with your application, so that you can use the icons in 34 | # the material Icons class. 35 | uses-material-design: true 36 | 37 | 38 | # To add assets to your application, add an assets section, like this: 39 | assets: 40 | - assets/logo.png 41 | - assets/drawer_header_bg.jpg 42 | 43 | # An image asset can refer to one or more resolution-specific "variants", see 44 | # https://flutter.io/assets-and-images/#resolution-aware. 45 | 46 | # For details regarding adding assets from package dependencies, see 47 | # https://flutter.io/assets-and-images/#from-packages 48 | 49 | # To add custom fonts to your application, add a fonts section here, 50 | # in this "flutter" section. Each entry in this list should have a 51 | # "family" key with the font family name, and a "fonts" key with a 52 | # list giving the asset and other descriptors for the font. For 53 | # example: 54 | # fonts: 55 | # - family: Schyler 56 | # fonts: 57 | # - asset: fonts/Schyler-Regular.ttf 58 | # - asset: fonts/Schyler-Italic.ttf 59 | # style: italic 60 | # - family: Trajan Pro 61 | # fonts: 62 | # - asset: fonts/TrajanPro.ttf 63 | # - asset: fonts/TrajanPro_Bold.ttf 64 | # weight: 700 65 | # 66 | # For details regarding fonts from package dependencies, 67 | # see https://flutter.io/custom-fonts/#from-packages 68 | -------------------------------------------------------------------------------- /flutter_wanandroid/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter 3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to 4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties 5 | // are correct. 6 | 7 | 8 | 9 | void main() { 10 | } 11 | -------------------------------------------------------------------------------- /raw/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/.DS_Store -------------------------------------------------------------------------------- /raw/Screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_1.png -------------------------------------------------------------------------------- /raw/Screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_2.png -------------------------------------------------------------------------------- /raw/Screenshot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_3.png -------------------------------------------------------------------------------- /raw/Screenshot_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_4.png -------------------------------------------------------------------------------- /raw/Screenshot_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_5.png -------------------------------------------------------------------------------- /raw/Screenshot_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/Screenshot_6.png -------------------------------------------------------------------------------- /raw/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/app-release.apk -------------------------------------------------------------------------------- /raw/screenrecord.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeyYe/flutter_wanandroid/21e2ab79a1c6dc2abf555fe414a2f7d33ad5989c/raw/screenrecord.gif --------------------------------------------------------------------------------