├── .gitattributes ├── .gitignore ├── .metadata ├── README.md ├── android ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── nullno │ │ │ │ └── flutter │ │ │ │ └── flutter_vmusic │ │ │ │ └── MainActivity.java │ │ └── res │ │ │ ├── drawable │ │ │ ├── ic_launcher.png │ │ │ ├── launch_background.xml │ │ │ └── launch_image.jpg │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── image │ ├── disc-plus.png │ ├── disc.png │ ├── disc_back.png │ ├── disc_light-plus.png │ ├── disc_light.png │ ├── needle-plus.png │ ├── needle.png │ └── play_btn_3x.png ├── com.nullno.flutter.flutter_dxmusic.apk ├── ios ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ └── contents.xcworkspacedata └── Runner │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── main.m ├── lib ├── components │ ├── exitApp.dart │ ├── find.dart │ ├── my.dart │ ├── playPanel.dart │ └── video.dart ├── conf │ ├── api.dart │ ├── appsate.dart │ ├── platform.dart │ └── router.dart ├── main.dart ├── pages │ ├── home_page.dart │ ├── landing_page.dart │ ├── player_page.dart │ ├── search_page.dart │ ├── song_menu_page.dart │ ├── song_mlist_page.dart │ ├── template_blank.dart │ └── video_detail_page.dart └── utils │ ├── FixedSizeText.dart │ ├── custom.dart │ ├── store.dart │ ├── tool.dart │ └── video_player.dart ├── pubspec.lock ├── pubspec.yaml └── test └── widget_test.dart /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | *.apk 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | .dart_tool/ 27 | .flutter-plugins 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | /build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /.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: 20e59316b8b8474554b38493b8ca888794b0234a 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter-vmusic 2 | 3 | flutter 制造的一款音乐播放器; 4 | 5 | 6 | ### 数据来源 7 | https://binaryify.github.io/NeteaseCloudMusicApi/#/?id=neteasecloudmusicapi 8 | 9 | ### api接口 10 | https://musicapi.nullno.com/ 11 | 12 | ### 界面 13 | ![nullno-img](https://source.nullno.com/images/Screenshot_20191011-164737.jpg) 14 | https://source.nullno.com/images/Screenshot_20191011-164737.jpg 15 | 16 | ![nullno-img](https://source.nullno.com/images/Screenshot_20191022-133435.jpg) 17 | 18 | ##功能 19 | 20 | 1、歌单热歌榜 21 | 22 | 2、视频播放 23 | 24 | 3、音乐播放 25 | 26 | 4、音乐下载(dev..) 27 | 28 | ##安卓下载地址 29 | 30 | https://nullno.github.io/flutter-vmusic/com.nullno.flutter.flutter_dxmusic.apk 31 | 32 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.nullno.flutter.flutter_vmusic" 37 | minSdkVersion 16 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'androidx.test:runner:1.1.1' 60 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 61 | } 62 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 17 | 24 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/nullno/flutter/flutter_vmusic/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.nullno.flutter.flutter_vmusic; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | 11 | super.onCreate(savedInstanceState); 12 | 13 | 14 | GeneratedPluginRegistrant.registerWith(this); 15 | } 16 | } -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/android/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/android/app/src/main/res/drawable/launch_image.jpg -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #EDE7DF 4 | #00000000 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.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 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /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.10.2-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/image/disc-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/assets/image/disc-plus.png -------------------------------------------------------------------------------- /assets/image/disc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/assets/image/disc.png -------------------------------------------------------------------------------- /assets/image/disc_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/assets/image/disc_back.png -------------------------------------------------------------------------------- /assets/image/disc_light-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/assets/image/disc_light-plus.png -------------------------------------------------------------------------------- /assets/image/disc_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/assets/image/disc_light.png -------------------------------------------------------------------------------- /assets/image/needle-plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/assets/image/needle-plus.png -------------------------------------------------------------------------------- /assets/image/needle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/assets/image/needle.png -------------------------------------------------------------------------------- /assets/image/play_btn_3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/assets/image/play_btn_3x.png -------------------------------------------------------------------------------- /com.nullno.flutter.flutter_dxmusic.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/com.nullno.flutter.flutter_dxmusic.apk -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nullno/flutter-vmusic/c67eb26cb4e321cab3e3907d5a455a4ece493683/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_vmusic 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/components/exitApp.dart: -------------------------------------------------------------------------------- 1 | /* 2 | 退出应用确认框 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/cupertino.dart'; 6 | 7 | Future exitApp(context) { 8 | return showDialog( 9 | context: context, 10 | barrierDismissible: true, // user must tap button! 11 | builder: (BuildContext context){ 12 | return CupertinoAlertDialog( 13 | title: Text('是否退出应用?'), 14 | actions:[ 15 | CupertinoDialogAction( 16 | child: Text('取消'), 17 | onPressed: () => Navigator.pop(context, false), 18 | ), 19 | CupertinoDialogAction( 20 | child: Text('退出'), 21 | onPressed: () => Navigator.pop(context, true), 22 | ), 23 | ], 24 | ); 25 | }, 26 | ); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /lib/components/find.dart: -------------------------------------------------------------------------------- 1 | /* 2 | 推荐模块 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_vmusic/conf/router.dart'; 6 | 7 | import 'package:cached_network_image/cached_network_image.dart'; 8 | import 'dart:async'; 9 | 10 | //import 'package:flutter/painting.dart'; 11 | import 'package:flutter_vmusic/conf/api.dart'; 12 | import 'package:flutter_vmusic/utils/tool.dart'; 13 | import 'package:flutter_vmusic/utils/FixedSizeText.dart'; 14 | 15 | import 'package:loading/loading.dart'; 16 | import 'package:loading/indicator/line_scale_indicator.dart'; 17 | 18 | 19 | 20 | class Find extends StatefulWidget{ 21 | 22 | @override 23 | 24 | State createState() => new _Find(); 25 | 26 | } 27 | 28 | class _Find extends State with SingleTickerProviderStateMixin{ 29 | 30 | 31 | 32 | 33 | // banner数据 34 | List adList = []; 35 | // 热歌榜数据 36 | List songRanks = []; 37 | //歌单 38 | List songLists = []; 39 | //加载状态 40 | int loadState = 0; //0加载中 1加载成功 2加载失败 41 | 42 | TabController AdController;//tab控制器 43 | int _AdcurrentIndex = 0; //选中下标 44 | 45 | //下拉刷新 46 | final GlobalKey _refreshIndicatorKey = new GlobalKey(); 47 | 48 | @override 49 | void initState(){ 50 | super.initState(); 51 | _flashData(); 52 | } 53 | //广告图初始化controller并添加监听 54 | void _adController(){ 55 | AdController = TabController(initialIndex:0,length:adList.length, vsync: this); 56 | AdController.addListener((){ 57 | if (AdController.index.toDouble() == AdController.animation.value) { 58 | //赋值 并更新数据 59 | if(mounted) { 60 | this.setState(() { 61 | _AdcurrentIndex = AdController.index; 62 | }); 63 | } 64 | } 65 | }); 66 | } 67 | 68 | // 刷新获取数据 69 | Future _flashData(){ 70 | final Completer completer = new Completer(); 71 | 72 | getData((status){ 73 | if(mounted) { 74 | setState(() { 75 | loadState = status; 76 | completer.complete(null); 77 | if (adList.length > 0) { 78 | _adController(); 79 | } 80 | }); 81 | } 82 | }); 83 | 84 | /* // 启动一下 [Timer] 在3秒后,在list里面添加一条数据,关完成这个刷新 85 | new Timer(Duration(seconds: 2), () { 86 | // 添加数据,更新界面 87 | // 完成刷新 88 | completer.complete(null); 89 | }); 90 | */ 91 | 92 | return completer.future; 93 | } 94 | //获取数据 95 | void getData(complete) async{ 96 | var status = 0; 97 | 98 | await homeGet((res){ 99 | status = 1; 100 | adList = res[0]['banners']; 101 | songLists= res[1]['result']; 102 | },(err){ 103 | status = 2; 104 | print(err); 105 | }); 106 | complete(status); 107 | } 108 | 109 | 110 | @override 111 | Widget build(BuildContext context) { 112 | 113 | Widget _loader(BuildContext context, String url) { 114 | return new Center( 115 | widthFactor:12.0, 116 | child: CircularProgressIndicator( 117 | backgroundColor:Colors.white, 118 | ), 119 | ); 120 | } 121 | 122 | Widget _error(BuildContext context, String url, Object error) { 123 | // print(error); 124 | return new Center( 125 | child: Icon(Icons.error,color: Colors.white), 126 | ); 127 | } 128 | 129 | Widget _loaderImgBlank(BuildContext context, String url) { 130 | return new Center( 131 | widthFactor:12.0, 132 | child:Icon(Icons.image,size:158,color: Colors.grey,) 133 | ); 134 | } 135 | 136 | //广告图 137 | Widget slideBanner = adList.length>0?TabBarView( 138 | controller: AdController, 139 | children: adList.map((item){ 140 | return Container( 141 | margin:EdgeInsets.fromLTRB(15.0, 0, 15.0, 0), 142 | child: ClipRRect( 143 | borderRadius: BorderRadius.circular(5), 144 | child:Container( 145 | color:Colors.white30, 146 | child: new CachedNetworkImage( 147 | placeholder: _loader, 148 | errorWidget: _error, 149 | imageUrl: item['imageUrl'],//item['imageUrl'], 150 | fit: BoxFit.cover, 151 | ) 152 | ), 153 | ) 154 | ); 155 | }).toList() 156 | ):Container(); 157 | 158 | //热歌榜 159 | Widget songRank = Column( 160 | crossAxisAlignment: CrossAxisAlignment.stretch, 161 | children: [ 162 | FixedSizeText('热歌榜',textAlign:TextAlign.left,style:TextStyle(fontSize:16.0, fontWeight:FontWeight.bold)), 163 | GridView.count( 164 | primary: false, 165 | padding: EdgeInsets.fromLTRB(0.0,10.0,0.0,0.0), 166 | crossAxisSpacing: 10, 167 | mainAxisSpacing: 10, 168 | childAspectRatio: 1, 169 | crossAxisCount: 6, 170 | shrinkWrap: true, 171 | children: [ 172 | Column( 173 | children: [ 174 | ClipRRect( 175 | borderRadius: BorderRadius.circular(50), 176 | child: Stack( 177 | children: [ 178 | InkWell( 179 | onTap: (){ 180 | Router.fadeNavigator(context,"/songmenulist",{'id':4395559,'from':'/find'},(res){}); 181 | }, 182 | child: new CachedNetworkImage( 183 | imageUrl:'https://p1.music.126.net/N2whh2Prf0l8QHmCpShrcQ==/19140298416347251.jpg',//item['playlist']['coverImgUrl'], 184 | fit: BoxFit.cover, 185 | ), 186 | ) 187 | /* Positioned( 188 | left:20.0, 189 | bottom:3.0, 190 | child:Row( 191 | children: [ 192 | Icon(Icons.play_arrow,color:Colors.white,size:10.0,), 193 | FixedSizeText(tranNumber(item['playlist']['playCount']),style:TextStyle(color:Colors.white,fontSize:10.0)) 194 | ], 195 | ), 196 | )*/ 197 | ], 198 | ) 199 | ), 200 | /* FixedSizeText(item['playlist']['name'], 201 | maxLines:1, 202 | overflow: TextOverflow.ellipsis, 203 | style:TextStyle(fontSize:13.0,height:1.5))*/ 204 | ], 205 | ), 206 | Column( 207 | children: [ 208 | ClipRRect( 209 | borderRadius: BorderRadius.circular(50), 210 | child: Stack( 211 | children: [ 212 | InkWell( 213 | onTap: (){ 214 | Router.fadeNavigator(context,"/songmenulist",{'id':2617766278,'from':'/find'},(res){}); 215 | }, 216 | child: new CachedNetworkImage( 217 | imageUrl:'https://p1.music.126.net/XbjRDARP1xv5a-40ZDOy6A==/109951163785427934.jpg',//item['playlist']['coverImgUrl'], 218 | fit: BoxFit.cover, 219 | ), 220 | ) 221 | ], 222 | ) 223 | ), 224 | ], 225 | ), 226 | Column( 227 | children: [ 228 | ClipRRect( 229 | borderRadius: BorderRadius.circular(50), 230 | child: Stack( 231 | children: [ 232 | InkWell( 233 | onTap: (){ 234 | Router.fadeNavigator(context,"/songmenulist",{'id':3779629,'from':'/find'},(res){}); 235 | }, 236 | child: new CachedNetworkImage( 237 | imageUrl:'http://p1.music.126.net/N2HO5xfYEqyQ8q6oxCw8IQ==/18713687906568048.jpg',//item['playlist']['coverImgUrl'], 238 | fit: BoxFit.cover, 239 | ), 240 | ) 241 | ], 242 | ) 243 | ), 244 | ], 245 | ), 246 | Column( 247 | children: [ 248 | ClipRRect( 249 | borderRadius: BorderRadius.circular(50), 250 | child: Stack( 251 | children: [ 252 | InkWell( 253 | onTap: (){ 254 | Router.fadeNavigator(context,"/songmenulist",{'id':3778678,'from':'/find'},(res){}); 255 | }, 256 | child: new CachedNetworkImage( 257 | imageUrl:'http://p2.music.126.net/GhhuF6Ep5Tq9IEvLsyCN7w==/18708190348409091.jpg',//item['playlist']['coverImgUrl'], 258 | fit: BoxFit.cover, 259 | ), 260 | ) 261 | ], 262 | ) 263 | ), 264 | ], 265 | ), 266 | Column( 267 | children: [ 268 | ClipRRect( 269 | borderRadius: BorderRadius.circular(50), 270 | child: Stack( 271 | children: [ 272 | InkWell( 273 | onTap: (){ 274 | Router.fadeNavigator(context,"/songmenulist",{'id':2884035,'from':'/find'},(res){}); 275 | }, 276 | child: new CachedNetworkImage( 277 | imageUrl:'http://p2.music.126.net/sBzD11nforcuh1jdLSgX7g==/18740076185638788.jpg',//item['playlist']['coverImgUrl'], 278 | fit: BoxFit.cover, 279 | ), 280 | ) 281 | ], 282 | ) 283 | ), 284 | ], 285 | ), 286 | Column( 287 | children: [ 288 | ClipRRect( 289 | borderRadius: BorderRadius.circular(50), 290 | child: Stack( 291 | children: [ 292 | InkWell( 293 | onTap: (){ 294 | Router.fadeNavigator(context,"/songmenulist",{'id':2250011882,'from':'/find'},(res){}); 295 | }, 296 | child: new CachedNetworkImage( 297 | imageUrl:'http://p1.music.126.net/oUxnXXvM33OUHxxukYnUjQ==/109951164174523461.jpg',//item['playlist']['coverImgUrl'], 298 | fit: BoxFit.cover, 299 | ), 300 | ) 301 | ], 302 | ) 303 | ), 304 | ], 305 | ), 306 | ], 307 | 308 | 309 | ), 310 | 311 | ], 312 | ); 313 | 314 | //歌单 315 | Widget songList = Column( 316 | crossAxisAlignment: CrossAxisAlignment.stretch, 317 | children: [ 318 | Row( 319 | mainAxisAlignment:MainAxisAlignment.spaceBetween, 320 | children: [ 321 | FixedSizeText('推荐歌单',textAlign:TextAlign.left,style:TextStyle(fontSize:16.0, fontWeight:FontWeight.bold)), 322 | ClipRRect( 323 | borderRadius: BorderRadius.circular(10), 324 | child: Material( 325 | color:Colors.white, 326 | child: InkWell( 327 | onTap: (){ 328 | Router.fadeNavigator(context,"/songmenu",{'from':'/find'},(res){}); 329 | }, 330 | highlightColor:Colors.grey, 331 | 332 | child:Icon(Icons.more_horiz), 333 | ), 334 | ) 335 | ), 336 | 337 | ], 338 | ), 339 | 340 | GridView.count( 341 | primary: false, 342 | padding: EdgeInsets.fromLTRB(0.0,10.0,0.0,0.0), 343 | crossAxisSpacing: 10, 344 | mainAxisSpacing: 10, 345 | childAspectRatio: 0.75, 346 | crossAxisCount: 2, 347 | shrinkWrap: true, 348 | children: songLists.map((item){ 349 | return Column( 350 | crossAxisAlignment: CrossAxisAlignment.start, 351 | children: [ 352 | InkWell( 353 | onTap: (){ 354 | Router.fadeNavigator(context,"/songmenulist",{'id':item['id'],'from':'/find'},(res){}); 355 | }, 356 | child:ClipRRect( 357 | borderRadius: BorderRadius.circular(5), 358 | child: Stack( 359 | children: [ 360 | new CachedNetworkImage( 361 | placeholder: _loaderImgBlank, 362 | errorWidget: _error, 363 | imageUrl:item['picUrl'],//item['picUrl'], 364 | fit: BoxFit.cover, 365 | ), 366 | Positioned( 367 | top:3.0, 368 | right:3.0, 369 | child:Row( 370 | children: [ 371 | Icon(Icons.play_circle_outline,color:Colors.white,size:15.0,), 372 | FixedSizeText(tranNumber(item['playCount']),style:TextStyle(color:Colors.white,fontSize:14.0)) 373 | ], 374 | ), 375 | ) 376 | ], 377 | ) 378 | ) , 379 | ), 380 | 381 | Container( 382 | padding:EdgeInsets.fromLTRB(0.0, 3.0, 0.0, 0.0), 383 | child:FixedSizeText(item['name'], 384 | maxLines:2, 385 | overflow: TextOverflow.ellipsis, 386 | style:TextStyle(fontSize:13.0,height:1.2)), 387 | ) 388 | 389 | ], 390 | ); 391 | }).toList() 392 | ), 393 | 394 | ], 395 | ); 396 | 397 | return Material( 398 | // 255, 240,62,57 399 | child:loadState!=1?Center(child:loadState==0?Loading(indicator: LineScaleIndicator(), size: 50.0):Icon(Icons.cloud_off,size:40.0,)):RefreshIndicator( 400 | key: _refreshIndicatorKey, 401 | onRefresh: _flashData, // onRefresh 参数是一个 Future 的回调 402 | child: ListView( 403 | primary: true, 404 | physics: BouncingScrollPhysics(), 405 | padding:const EdgeInsets.fromLTRB(0.0,0.0,0.0,60.0), 406 | children: [ 407 | Container( 408 | height: 150.0, 409 | margin: EdgeInsets.fromLTRB(0.0, 0.0, 0, 0.0), 410 | padding:EdgeInsets.fromLTRB(0, 8.0, 0.0, 8.0), 411 | color: Colors.white, 412 | child: slideBanner, 413 | ), 414 | Container( 415 | color:Colors.white, 416 | margin:EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), 417 | padding:EdgeInsets.fromLTRB(15.0, 8.0, 15.0, 8.0), 418 | child: songRank, 419 | ), 420 | Container( 421 | color:Colors.white, 422 | margin:EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), 423 | padding:EdgeInsets.fromLTRB(15.0, 8.0, 15.0, 8.0), 424 | child: songList, 425 | ), 426 | FixedSizeText('~我也是有底线的呦~',textAlign:TextAlign.center,style:TextStyle(height:2.0,fontSize:12.0),) 427 | 428 | 429 | ], 430 | ), 431 | ) 432 | 433 | 434 | ); 435 | 436 | } 437 | 438 | @override 439 | void dispose() { 440 | 441 | AdController.dispose(); 442 | super.dispose(); 443 | } 444 | 445 | } -------------------------------------------------------------------------------- /lib/components/my.dart: -------------------------------------------------------------------------------- 1 | /* 2 | 我的模块 3 | */ 4 | import 'package:flutter/material.dart'; 5 | 6 | class MyCenter extends StatefulWidget{ 7 | 8 | @override 9 | 10 | State createState() => new _MyCenter(); 11 | 12 | } 13 | 14 | class _MyCenter extends State{ 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | // TODO: implement build 19 | return Text('我的模块'); 20 | } 21 | } -------------------------------------------------------------------------------- /lib/components/playPanel.dart: -------------------------------------------------------------------------------- 1 | /* 2 | 播放面板 3 | */ 4 | import 'dart:async'; 5 | import 'package:flutter/material.dart'; 6 | import 'package:audioplayers/audioplayers.dart'; 7 | import 'package:flutter_vmusic/utils/FixedSizeText.dart'; 8 | 9 | import 'package:flutter_vmusic/conf/appsate.dart'; 10 | import 'package:cached_network_image/cached_network_image.dart'; 11 | 12 | import 'package:flutter_vmusic/conf/router.dart'; 13 | import 'package:flutter_vmusic/conf/platform.dart'; 14 | 15 | class PlayPanel extends StatefulWidget{ 16 | final Map params; 17 | PlayPanel({ 18 | Key key, 19 | this.params, 20 | }) : super(key: key); 21 | @override 22 | _PlayPanel createState() => _PlayPanel(); 23 | } 24 | 25 | class _PlayPanel extends State{ 26 | 27 | AudioPlayer audioPlayer = AppState.player['audioPlayer'] ; 28 | 29 | void initState() { 30 | super.initState(); 31 | 32 | } 33 | 34 | //播放歌曲 35 | void playSong(url) async{ 36 | print(url); 37 | int result = await audioPlayer.play(url.replaceAll('http:', 'https:')); 38 | if(result==1) { 39 | setState(() { 40 | AppState.player['playStatus']=true; 41 | 42 | }); 43 | } 44 | } 45 | //暂停播放 46 | void pauseSong() async{ 47 | int result = await audioPlayer.pause(); 48 | if(result==1) { 49 | setState(() { 50 | AppState.player['playStatus']=false; 51 | }); 52 | } 53 | } 54 | 55 | @override 56 | Widget build(BuildContext context) { 57 | return RawMaterialButton( 58 | onPressed: (){ 59 | Router.fadeNavigator(context,"/playerpage",{'id':AppState.player['id'],'from':'/panel'},(res){ 60 | SYS.systemUI(Colors.transparent,Colors.black,Brightness.dark); 61 | }); 62 | }, 63 | splashColor:Color(0xff898B8B), 64 | child:Container( 65 | width: double.infinity, 66 | height: 50.0, 67 | padding:EdgeInsets.all(5.0), 68 | decoration: BoxDecoration(color:Color.fromRGBO(255, 255, 255, 0.95)), 69 | child: new Row( 70 | mainAxisAlignment: MainAxisAlignment.spaceAround, 71 | crossAxisAlignment:CrossAxisAlignment.center, 72 | children: [ 73 | Padding( 74 | padding:EdgeInsets.fromLTRB(4.0,0,5.0,0), 75 | child:ClipRRect( 76 | borderRadius: BorderRadius.circular(50), 77 | child:Container( 78 | height:40.0, 79 | width:40.0, 80 | color:Colors.grey, 81 | child: new CachedNetworkImage( 82 | imageUrl:AppState.player['face'],//item['picUrl'], 83 | ), 84 | ) 85 | ) ,), 86 | 87 | Expanded( 88 | child: Column( 89 | crossAxisAlignment: CrossAxisAlignment.start, 90 | mainAxisAlignment: MainAxisAlignment.center, 91 | children: [ 92 | FixedSizeText(AppState.player['name'], overflow: TextOverflow.ellipsis,style:TextStyle(fontSize:13.0),), 93 | FixedSizeText(AppState.player['singer'],style:TextStyle(color:Colors.black45,fontSize:11.0)), 94 | ], 95 | ) 96 | ), 97 | IconButton( 98 | padding:EdgeInsets.all(0.0), 99 | splashColor:Color(0xff898B8B), 100 | onPressed: (){ 101 | if(!AppState.player['playStatus']){ 102 | playSong(AppState.player['url']); 103 | }else{ 104 | pauseSong(); 105 | } 106 | 107 | }, 108 | icon: Icon( AppState.player['playStatus']==false?Icons.play_circle_outline:Icons.pause_circle_outline,color: Colors.black,size:40.0) 109 | ), 110 | IconButton( 111 | padding:EdgeInsets.all(0.0), 112 | splashColor:Color(0xff898B8B), 113 | onPressed: (){ 114 | 115 | }, 116 | icon: Icon(Icons.playlist_play,color: Colors.black,size:40.0), 117 | ) 118 | 119 | ], 120 | ), 121 | ), 122 | 123 | ); 124 | } 125 | } -------------------------------------------------------------------------------- /lib/components/video.dart: -------------------------------------------------------------------------------- 1 | /* 2 | 视频模块 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:cached_network_image/cached_network_image.dart'; 6 | 7 | import 'package:flutter_vmusic/utils/tool.dart'; 8 | import 'package:flutter_vmusic/utils/video_player.dart'; 9 | import 'package:flutter_vmusic/utils/FixedSizeText.dart'; 10 | 11 | import 'package:loading/loading.dart'; 12 | import 'package:loading/indicator/line_scale_pulse_out_indicator.dart'; 13 | 14 | import 'dart:async'; 15 | import 'package:flutter_vmusic/conf/api.dart'; 16 | 17 | import 'package:flutter_vmusic/conf/router.dart'; 18 | import 'package:flutter_vmusic/conf/platform.dart'; 19 | 20 | class VideoList extends StatefulWidget{ 21 | 22 | @override 23 | 24 | State createState() => new _VideoList(); 25 | 26 | } 27 | 28 | class _VideoList extends State{ 29 | 30 | 31 | // 推荐MV数据 32 | List topList = []; 33 | // 全部MV数据 34 | List allList = []; 35 | //加载状态 36 | int loadState = 0; //0加载中 1加载成功 2加载失败 37 | //下拉刷新 38 | final GlobalKey _refreshIndicatorKey = new GlobalKey(); 39 | 40 | Map loadMore={ 41 | "Text":"----", 42 | "Page":0, 43 | "hasMore":true, 44 | "isScrollBottom":false, 45 | }; 46 | 47 | //初始化滚动监听器,加载更多使用 48 | ScrollController _scrollController = new ScrollController(); 49 | 50 | @override 51 | void initState(){ 52 | super.initState(); 53 | if(mounted) { 54 | addloadMore(); 55 | // 数据刷新 56 | _flashData(); 57 | } 58 | } 59 | 60 | // 全部mv监听加载更多 61 | addloadMore(){ 62 | _scrollController.addListener(() { 63 | var maxScroll = _scrollController.position.maxScrollExtent.toStringAsFixed(0); 64 | var pixel = _scrollController.position.pixels.toStringAsFixed(0); 65 | 66 | if (maxScroll == pixel && loadMore["hasMore"]&&!loadMore["isScrollBottom"]) { 67 | 68 | setState(() { 69 | loadMore["Text"] = "正在加载中..."; 70 | loadMore["isScrollBottom"]=true; 71 | getAllMV({"area":'全部',"offset":loadMore['Page']*10},(res){ 72 | loadMore['hasMore']=res['hasMore']; 73 | loadMore['Page']=loadMore['Page']+1; 74 | loadMore["isScrollBottom"]=false; 75 | res['data'].forEach((aitem)=>{ 76 | aitem['vurl']=null, 77 | allList.add(aitem) 78 | }); 79 | },(err){ 80 | print(err); 81 | }); 82 | }); 83 | 84 | } else if(!loadMore['hasMore']) { 85 | setState(() { 86 | loadMore["isScrollBottom"]=true; 87 | loadMore["Text"] = "~我也是有底线的呦~"; 88 | }); 89 | } 90 | }); 91 | } 92 | 93 | // 下拉刷新数据 94 | Future _flashData() async{ 95 | final Completer completer = new Completer(); 96 | getData((status){ 97 | setState(() { 98 | loadState=status; 99 | completer.complete(null); 100 | }); 101 | 102 | }); 103 | 104 | 105 | return completer.future; 106 | } 107 | //获取数据 108 | void getData(complete) async{ 109 | var status = 0; 110 | 111 | //获取推荐 112 | await getPersonalizedMV((res){ 113 | status = 1; 114 | topList=res['result']; 115 | },(err){ 116 | status = 2; 117 | print(err); 118 | }); 119 | 120 | // 获取全部 121 | await getAllMV({"area":'全部',"offset":0},(res){ 122 | status = 1; 123 | loadMore['hasMore']=res['hasMore']; 124 | loadMore['Page']=1; 125 | loadMore["isScrollBottom"]=false; 126 | allList.clear(); 127 | res['data'].forEach((aitem)=>{ 128 | aitem['vurl']=null, 129 | allList.add(aitem) 130 | 131 | }); 132 | },(err){ 133 | status = 2; 134 | print(err); 135 | }); 136 | 137 | complete(status); 138 | } 139 | 140 | //获取播放链接 141 | void midPlayer(mid){ 142 | var result = allList.singleWhere((aitem)=>(aitem['id']==mid),orElse: ()=>(0)); 143 | 144 | setState(() { 145 | result['vurl']=1; 146 | }); 147 | getMVDetail(mid,(res){ 148 | if(res['code']==200){ 149 | setState(() { 150 | allList.forEach((aitem)=>{ 151 | aitem['vurl']=null 152 | }); 153 | result['vurl']= videoUrl(res['data']['brs']); 154 | 155 | }); 156 | } 157 | 158 | },(err)=>{}); 159 | 160 | } 161 | 162 | 163 | @override 164 | Widget build(BuildContext context) { 165 | 166 | Widget _error(BuildContext context, String url, Object error) { 167 | // print(error); 168 | return new Center( 169 | child: Icon(Icons.error,color: Colors.white), 170 | ); 171 | } 172 | 173 | Widget _loaderImg(BuildContext context, String url) { 174 | return new Center( 175 | child:Icon(Icons.image,size:88.0,color:Colors.grey,), 176 | ); 177 | } 178 | 179 | //推荐 180 | Widget topMV = Column( 181 | crossAxisAlignment: CrossAxisAlignment.stretch, 182 | children: [ 183 | FixedSizeText('精选MV',textAlign:TextAlign.left,style:TextStyle(fontSize:16.0, fontWeight:FontWeight.bold)), 184 | GridView.count( 185 | primary: false, 186 | padding: EdgeInsets.fromLTRB(0.0,10.0,0.0,0.0), 187 | crossAxisSpacing: 10, 188 | mainAxisSpacing: 10, 189 | childAspectRatio: 1.25, 190 | crossAxisCount: 2, 191 | shrinkWrap: true, 192 | children: topList.map((item){ 193 | return InkWell( 194 | onTap:()=>{ 195 | Router.fadeNavigator(context,"/videopage",{'vid':item['id'],'type':0, 'from':'/video'},(res){ 196 | SYS.systemUI(Colors.transparent,Colors.black,Brightness.dark); 197 | }) 198 | }, 199 | child: Column( 200 | crossAxisAlignment: CrossAxisAlignment.start, 201 | children: [ 202 | ClipRRect( 203 | borderRadius: BorderRadius.circular(5), 204 | child: Stack( 205 | children: [ 206 | new CachedNetworkImage( 207 | placeholder: _loaderImg, 208 | errorWidget: _error, 209 | imageUrl:item['picUrl'],//item['playlist']['coverImgUrl'], 210 | fit: BoxFit.cover, 211 | ), 212 | Positioned( 213 | right:3.0, 214 | top:3.0, 215 | child:Row( 216 | children: [ 217 | Icon(Icons.play_arrow,color:Colors.white,size:10.0,), 218 | FixedSizeText(tranNumber(item['playCount']),style:TextStyle(color:Colors.white,fontSize:10.0)) 219 | ], 220 | ), 221 | ) 222 | ], 223 | ) 224 | ), 225 | Container( 226 | padding:EdgeInsets.fromLTRB(0.0, 3.0, 0.0, 0.0), 227 | child: FixedSizeText(item['name'], 228 | maxLines:1, 229 | overflow: TextOverflow.ellipsis, 230 | style:TextStyle(fontSize:13.0)), 231 | ), 232 | 233 | FixedSizeText(item['artistName'], 234 | maxLines:1, 235 | overflow: TextOverflow.ellipsis, 236 | style:TextStyle(fontSize:11.0,color:Colors.grey)) 237 | ], 238 | ) 239 | ); 240 | 241 | }).toList() 242 | ), 243 | 244 | ], 245 | ); 246 | 247 | //视频片段 248 | Widget mvList(List mvdata){ 249 | 250 | return ListView.builder( 251 | itemCount: mvdata.length, 252 | shrinkWrap: true, 253 | primary:false, 254 | padding:EdgeInsets.all(0.0), 255 | itemBuilder: (context, i) => Container( 256 | color:Colors.white, 257 | width:double.infinity, 258 | padding:EdgeInsets.fromLTRB(15.0, 8.0, 15.0, 8.0), 259 | margin:EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 10.0), 260 | child: Column( 261 | crossAxisAlignment: CrossAxisAlignment.start, 262 | children: [ 263 | ClipRRect( 264 | borderRadius: BorderRadius.circular(5), 265 | child: new Stack( 266 | alignment:Alignment.center, 267 | children: [ 268 | Container( 269 | width:double.infinity, 270 | color:Colors.black, 271 | height:185.0, 272 | child:mvdata[i]['vurl']==null||mvdata[i]['vurl']==1?new CachedNetworkImage( 273 | imageUrl:mvdata[i]['cover']!=null?mvdata[i]['cover']:"http://p1.music.126.net/l6zOREIvWGNk3L67EkffRw==/1401877337913197.jpg",//item['picUrl'], 274 | fit: BoxFit.cover, 275 | ):SimpleViewPlayer(mvdata[i]['vurl'], isFullScreen: false), 276 | ), 277 | mvdata[i]['vurl']==null||mvdata[i]['vurl']==1?Positioned( 278 | bottom:3.0, 279 | left:3.0, 280 | child:Row( 281 | children: [ 282 | Icon(Icons.play_arrow,color:Colors.white,size:13.0,), 283 | FixedSizeText(tranNumber(mvdata[i]['playCount']),style:TextStyle(color:Colors.white,fontSize:12.0)) 284 | ], 285 | ), 286 | ):Container(), 287 | mvdata[i]['vurl']==null||mvdata[i]['vurl']==1?Positioned( 288 | bottom:3.0, 289 | right:3.0, 290 | child:Row( 291 | children: [ 292 | Icon(Icons.graphic_eq,color:Colors.white,size:13.0,), 293 | FixedSizeText(formatDuration(mvdata[i]['duration']),style:TextStyle(color:Colors.white,fontSize:12.0)) 294 | ], 295 | ), 296 | ):Container(), 297 | mvdata[i]['vurl']==1?CircularProgressIndicator(backgroundColor:Colors.redAccent):mvdata[i]['vurl']==null?Positioned( 298 | child:InkWell( 299 | onTap:(){ 300 | midPlayer(mvdata[i]['id']); 301 | }, 302 | child:Icon(Icons.play_arrow,color:Colors.white70,size:45.0,), 303 | ), 304 | ):Container() 305 | ], 306 | ) 307 | ), 308 | Material( 309 | color:Colors.white, 310 | child:InkWell( 311 | highlightColor:Colors.transparent, 312 | onTap:(){ 313 | setState(() { 314 | mvdata[i]['vurl']=null; 315 | }); 316 | Router.fadeNavigator(context,"/videopage",{'vid':mvdata[i]['id'],'type':0, 'from':'/video'},(res){ 317 | SYS.systemUI(Colors.transparent,Colors.black,Brightness.dark); 318 | }); 319 | }, 320 | child:Padding( 321 | padding:EdgeInsets.only(top:10.0,bottom:10.0), 322 | child: Row( 323 | crossAxisAlignment: CrossAxisAlignment.center, 324 | mainAxisAlignment:MainAxisAlignment.spaceBetween, 325 | children: [ 326 | Expanded( 327 | flex:7, 328 | child:Container( 329 | margin:EdgeInsets.fromLTRB(0.0,5.0,0.0,5.0), 330 | width:Adapt.screenW(), 331 | child:FixedSizeText(mvdata[i]['name']+'---'+mvdata[i]['artistName'], 332 | maxLines:2, 333 | overflow: TextOverflow.ellipsis, 334 | style:TextStyle(fontSize:14.0,height:1.2))), 335 | ), 336 | Icon(Icons.send,color:Colors.grey,size:15.0,) 337 | ], 338 | ), 339 | ), 340 | 341 | ) 342 | ) 343 | 344 | ], 345 | ), 346 | ) 347 | ); 348 | 349 | } 350 | 351 | //全部 352 | Widget allMV = Column( 353 | crossAxisAlignment: CrossAxisAlignment.stretch, 354 | children: [ 355 | Container( 356 | color:Colors.white, 357 | padding:EdgeInsets.fromLTRB(15.0, 10.0, 15.0, 2.0), 358 | child: Row( 359 | mainAxisAlignment:MainAxisAlignment.spaceBetween, 360 | children: [ 361 | FixedSizeText('全部',textAlign:TextAlign.left,style:TextStyle(fontSize:16.0, fontWeight:FontWeight.bold)), 362 | 363 | ], 364 | ), 365 | ), 366 | mvList(allList) 367 | 368 | ], 369 | ); 370 | 371 | return Material( 372 | 373 | child:loadState!=1?Center(child:loadState==0?Loading(indicator: LineScalePulseOutIndicator(), size: 50.0):Icon(Icons.cloud_off,size:40.0,)):RefreshIndicator( 374 | key: _refreshIndicatorKey, 375 | onRefresh: _flashData, // onRefresh 参数是一个 Future 的回调 376 | child: ListView( 377 | controller: _scrollController, 378 | // primary: true, 379 | physics: const BouncingScrollPhysics(), 380 | padding:const EdgeInsets.fromLTRB(0.0,0.0,0.0,60.0), 381 | children: [ 382 | Container( 383 | color:Colors.white, 384 | // margin:EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), 385 | padding:EdgeInsets.fromLTRB(15.0, 8.0, 15.0, 8.0), 386 | child: topMV, 387 | ), 388 | Container( 389 | margin:EdgeInsets.fromLTRB(0.0, 10.0, 0.0, 0.0), 390 | padding:EdgeInsets.fromLTRB(0.0, 8.0, 0.0, 8.0), 391 | child: allMV, 392 | ), 393 | Visibility( 394 | child: Row( 395 | mainAxisAlignment: MainAxisAlignment.center, 396 | children: [ 397 | loadMore["hasMore"]?Container( 398 | width:15.0, 399 | height:15.0, 400 | margin:EdgeInsets.all(5.0), 401 | child:Loading(indicator: LineScalePulseOutIndicator(), size: 100.0), 402 | ):Container(), 403 | FixedSizeText(loadMore["Text"],textAlign:TextAlign.center,style:TextStyle(height:1,fontSize:12.0)) 404 | ], 405 | ), 406 | visible: loadMore["isScrollBottom"], 407 | ) 408 | ], 409 | ), 410 | ) 411 | 412 | 413 | ); 414 | } 415 | 416 | @override 417 | void dispose() { 418 | _scrollController.dispose(); 419 | super.dispose(); 420 | } 421 | } -------------------------------------------------------------------------------- /lib/conf/api.dart: -------------------------------------------------------------------------------- 1 | /*数据接口*/ 2 | import 'package:dio/dio.dart'; 3 | import 'dart:convert'; 4 | import 'dart:async'; 5 | 6 | 7 | 8 | // Set default configs 9 | // or new Dio with a BaseOptions instance. 10 | BaseOptions options = new BaseOptions( 11 | baseUrl: "https://musicapi.nullno.com", 12 | connectTimeout: 5000, 13 | receiveTimeout: 3000, 14 | ); 15 | 16 | Dio dio = new Dio(options); // with default Options 17 | 18 | 19 | 20 | 21 | //搜索 22 | void search(parameters,resolve,[reject]) async { 23 | try { 24 | Response response = await dio.get("/search",queryParameters:{"keywords":parameters['keywords'],"limit": 20,"offset":parameters['offset'],"type":parameters["type"]}); 25 | resolve(jsonDecode(response.toString())); 26 | } catch (e) { 27 | reject(e); 28 | } 29 | } 30 | //搜索建议 31 | void searchSuggest(parameters,resolve,[reject]) async { 32 | try { 33 | Response response = await dio.get("/search/suggest",queryParameters:{"keywords":parameters['keywords'],"type":"mobile"}); 34 | resolve(jsonDecode(response.toString())); 35 | } catch (e) { 36 | reject(e); 37 | } 38 | } 39 | 40 | //热搜榜 41 | void searchHot(resolve,[reject]) async { 42 | try { 43 | Response response = await dio.get("/search/hot/detail",); 44 | resolve(jsonDecode(response.toString())); 45 | } catch (e) { 46 | reject(e); 47 | } 48 | } 49 | 50 | 51 | //歌曲排行榜 52 | void getRank(resolve,[reject]) async { 53 | try { 54 | List response = await Future.wait([ 55 | dio.get("/playlist/detail",queryParameters:{"id":"4395559"}), 56 | dio.get("/playlist/detail",queryParameters:{"id":"2617766278"}), 57 | dio.get("/playlist/detail",queryParameters:{"id":"3779629"}), 58 | dio.get("/playlist/detail",queryParameters:{"id":"3778678"}), 59 | // dio.get("/playlist/detail",queryParameters:{"id":"1978921795"}), 60 | // dio.get("/playlist/detail",queryParameters:{"id":"19723756"}), 61 | dio.get("/playlist/detail",queryParameters:{"id":"2884035"}), 62 | dio.get("/playlist/detail",queryParameters:{"id":"2250011882"}) 63 | ]); 64 | resolve(jsonDecode(response.toString())); 65 | } catch (e) { 66 | reject(e); 67 | } 68 | } 69 | 70 | 71 | //banner 72 | void getBanner(resolve,reject) async { 73 | try { 74 | Response response = await dio.get("/banner",queryParameters:{"types": "1",}); 75 | resolve(jsonDecode(response.toString())); 76 | } catch (e) { 77 | reject(e); 78 | } 79 | } 80 | 81 | //获取推荐歌单 82 | void getPersonalizedSongList(resolve,[reject]) async { 83 | try { 84 | Response response = await dio.get("/personalized",queryParameters:{"limit": 20}); 85 | resolve(jsonDecode(response.toString())); 86 | } catch (e) { 87 | reject(e); 88 | } 89 | } 90 | void homeGet(resolve,[reject]) async{ 91 | try{ 92 | List response = await Future.wait([ 93 | dio.get("/banner",queryParameters:{"types": "1",}), 94 | dio.get("/personalized",queryParameters:{"limit": 20}) 95 | ]); 96 | resolve(jsonDecode(response.toString())); 97 | }catch(e){ 98 | 99 | } 100 | 101 | } 102 | 103 | 104 | //精品歌单(歌单广场) 105 | void getHighqualitySongList(parameters,resolve,[reject]) async { 106 | try { 107 | Response response = await dio.get("/top/playlist/highquality",queryParameters:{"limit": 8,"before":parameters['before'],"cat":parameters["cat"]}); 108 | resolve(jsonDecode(response.toString())); 109 | } catch (e) { 110 | reject(e); 111 | } 112 | } 113 | 114 | //获取歌单详情 115 | void getSongMenuDetail(parameters,resolve,[reject]) async { 116 | try { 117 | Response response = await dio.get("/playlist/detail",queryParameters:{"id":parameters["id"]}); 118 | resolve(jsonDecode(response.toString())); 119 | } catch (e) { 120 | reject(e); 121 | } 122 | } 123 | 124 | //获取推荐mv 125 | void getPersonalizedMV(resolve,[reject]) async { 126 | try { 127 | Response response = await dio.get("/personalized/mv"); 128 | resolve(jsonDecode(response.toString())); 129 | } catch (e) { 130 | reject(e); 131 | } 132 | } 133 | //获取全部mv 134 | void getAllMV(parameters,resolve,[reject]) async { 135 | try { 136 | Response response = await dio.get("/mv/all",queryParameters:{"area":parameters['area'],"limit": 8,"offset":parameters['offset']}); 137 | resolve(jsonDecode(response.toString())); 138 | } catch (e) { 139 | reject(e); 140 | } 141 | } 142 | 143 | 144 | //获取mv视频播放数据 145 | void getMVDetail(mvId,resolve,[reject]) async { 146 | try { 147 | Response response = await dio.get("/mv/detail",queryParameters:{"mvid":mvId}); 148 | resolve(jsonDecode(response.toString())); 149 | } catch (e) { 150 | reject(e); 151 | } 152 | } 153 | 154 | //获取视频详情 155 | void getVideoDetail(vid,resolve,[reject]) async { 156 | try { 157 | Response response = await dio.get("/video/detail",queryParameters:{"id":vid}); 158 | resolve(jsonDecode(response.toString())); 159 | } catch (e) { 160 | reject(e); 161 | } 162 | } 163 | 164 | //获取视频播放数据 165 | 166 | void getVideoUrl(vid,resolve,[reject]) async { 167 | try { 168 | Response response = await dio.get("/video/url",queryParameters:{"id":vid}); 169 | resolve(jsonDecode(response.toString())); 170 | } catch (e) { 171 | reject(e); 172 | } 173 | } 174 | 175 | //获取mv评论 176 | void getMvComment(vid,resolve,[reject]) async { 177 | 178 | try { 179 | Response response = await dio.get("/comment/mv",queryParameters:{"id":vid}); 180 | resolve(jsonDecode(response.toString())); 181 | } catch (e) { 182 | reject(e); 183 | } 184 | } 185 | 186 | //获取视频评论 187 | void getVideoComment(parameters,resolve,[reject]) async { 188 | 189 | try { 190 | var path = parameters['type']==0?"/comment/mv":"/comment/video"; 191 | Response response = await dio.get(path,queryParameters:{"id":parameters['vid'],"limit":20,"offset":parameters['offset'],"before":parameters['before']}); 192 | resolve(jsonDecode(response.toString())); 193 | } catch (e) { 194 | reject(e); 195 | } 196 | } 197 | 198 | //获取歌曲详情 199 | void getSongDetail(ids,resolve,[reject]) async { 200 | try { 201 | Response response = await dio.get("/song/detail",queryParameters:{"ids":ids}); 202 | resolve(jsonDecode(response.toString())); 203 | } catch (e) { 204 | reject(e); 205 | } 206 | } 207 | 208 | //获取歌曲播放链接 209 | void getSongUrl(id,resolve,[reject]) async { 210 | try { 211 | Response response = await dio.get("/song/url",queryParameters:{"id":id}); 212 | resolve(jsonDecode(response.toString())); 213 | } catch (e) { 214 | reject(e); 215 | } 216 | } -------------------------------------------------------------------------------- /lib/conf/appsate.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:audioplayers/audioplayers.dart'; 3 | /// 应用程序状态 4 | 5 | class AppState{ 6 | 7 | //音乐控制 8 | static Map player ={ 9 | "id":0, 10 | "url":'未知', 11 | "face":'https://p1.music.126.net/N2whh2Prf0l8QHmCpShrcQ==/19140298416347251.jpg', 12 | "name":'未知', 13 | "singer":'未知', 14 | "duration":null, 15 | "position":null, 16 | "loop":true, 17 | "playStatus":false, 18 | "audioPlayer":new AudioPlayer(), 19 | "play":(url,[callback]) async{ 20 | int result = await AppState.player['audioPlayer'].play(url.replaceAll('http:', 'https:')); 21 | if(result==1) { 22 | if(callback)callback(); 23 | } 24 | }, 25 | "pause":([callback]) async{ 26 | int result = await AppState.player['audioPlayer'].pause(); 27 | if(result==1) { 28 | if(callback)callback(); 29 | } 30 | } 31 | }; 32 | 33 | 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /lib/conf/platform.dart: -------------------------------------------------------------------------------- 1 | /* 2 | 系统平台设置 3 | */ 4 | import 'package:flutter/services.dart'; 5 | import 'dart:io'; 6 | 7 | class SYS { 8 | static systemUI(statusBarColor,navColor,brightcolor){ 9 | if (Platform.isAndroid) { 10 | // 以下两行 设置android状态栏为透明的沉浸。写在组件渲染之后,是为了在渲染后进行set赋值,覆盖状态栏,写在渲染之前MaterialApp组件会覆盖掉这个值。 11 | SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top,SystemUiOverlay.bottom]); 12 | SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor:statusBarColor, 13 | systemNavigationBarIconBrightness: Brightness.dark, 14 | systemNavigationBarColor:statusBarColor, 15 | statusBarIconBrightness:brightcolor, 16 | // statusBarBrightness:brightcolor, 17 | ); 18 | SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); 19 | } 20 | } 21 | static hideBar(){ 22 | SystemChrome.setEnabledSystemUIOverlays([]); 23 | } 24 | static hideTopBar(){ 25 | SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.bottom]); 26 | } 27 | static showBar(){ 28 | SystemChrome.setEnabledSystemUIOverlays([SystemUiOverlay.top, SystemUiOverlay.bottom]); 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /lib/conf/router.dart: -------------------------------------------------------------------------------- 1 | /* 2 | 路由信息配置 3 | */ 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_vmusic/utils/custom.dart'; 6 | 7 | import 'package:flutter_vmusic/pages/landing_page.dart'; 8 | import 'package:flutter_vmusic/pages/home_page.dart'; 9 | 10 | import 'package:flutter_vmusic/pages/song_menu_page.dart'; 11 | import 'package:flutter_vmusic/pages/song_mlist_page.dart'; 12 | import 'package:flutter_vmusic/pages/search_page.dart'; 13 | import 'package:flutter_vmusic/pages/video_detail_page.dart'; 14 | import 'package:flutter_vmusic/pages/player_page.dart'; 15 | 16 | 17 | class Router{ 18 | //初始化路由 19 | static String initialRoute ='/launch'; 20 | //命名路由(静态路由) 21 | static Map routes={ 22 | '/launch':(BuildContext context) => LandingPage(), 23 | '/home':(BuildContext context) => HomePage(), 24 | }; 25 | //自定义路由跳转 26 | 27 | static fadeNavigator(BuildContext context,String routeName,Map params,pop){ 28 | Widget pageWidget; 29 | switch(routeName){ 30 | case'/launch': 31 | pageWidget=LandingPage(); 32 | break; 33 | case'/home': 34 | pageWidget=HomePage(params:params); 35 | break; 36 | case'/songmenu': 37 | pageWidget=SongMenu(params:params); 38 | break; 39 | case'/songmenulist': 40 | pageWidget=SongMenuList(params:params); 41 | break; 42 | case'/searchpage': 43 | pageWidget=SearchPage(params:params); 44 | break; 45 | case'/videopage': 46 | pageWidget=VideoPage(params:params); 47 | break; 48 | case'/playerpage': 49 | pageWidget=PlayerPage(params:params); 50 | 51 | 52 | } 53 | if(pageWidget!=null){ 54 | if(params['from']=='/launch'){ 55 | Navigator.pushAndRemoveUntil(context, FadeRoute(page: pageWidget),(route) => route == null).then((Object result) { 56 | pop(result); 57 | }); 58 | }else{ 59 | Navigator.push(context, MaterialPageRoute(builder: (context) { 60 | return pageWidget; 61 | })).then((Object result) { 62 | pop(result); 63 | }); 64 | } 65 | }else{ 66 | Navigator.of(context).pushNamed(routeName).then((Object result) { 67 | pop(result); 68 | }); 69 | } 70 | 71 | // Navigator.push(context, EnterExitRoute(exitPage: LandingPage(), enterPage: HomePage())); 72 | } 73 | 74 | static goHome(BuildContext context,Map params,pop){ 75 | 76 | Navigator.pushAndRemoveUntil(context, EnterExitRoute(exitPage:LandingPage(),enterPage:HomePage(params:params)),(route) => route == null).then((Object result) { 77 | pop(result); 78 | }); 79 | 80 | } 81 | 82 | 83 | } 84 | 85 | 86 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_vmusic/conf/router.dart'; 3 | import 'package:flutter_vmusic/pages/landing_page.dart'; 4 | 5 | 6 | 7 | void main(){ 8 | runApp(MyApp()); 9 | } 10 | 11 | 12 | class MyApp extends StatelessWidget { 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | return MaterialApp( 17 | debugShowCheckedModeBanner: false, 18 | title:'vmusic', 19 | theme: new ThemeData( 20 | primaryColor: Colors.white, 21 | accentColor: Colors.black45, 22 | accentColorBrightness: Brightness.light, 23 | ), 24 | home: LandingPage(), 25 | color:Colors.white, 26 | routes: Router.routes, 27 | initialRoute: Router.initialRoute, 28 | ); 29 | } 30 | } -------------------------------------------------------------------------------- /lib/pages/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | //import 'dart:ui'; 3 | import 'package:flutter_vmusic/components/playPanel.dart'; 4 | import 'package:flutter_vmusic/components/exitApp.dart'; 5 | //三大模块 6 | import 'package:flutter_vmusic/components/find.dart'; 7 | //import 'package:flutter_vmusic/components/my.dart'; 8 | import 'package:flutter_vmusic/components/video.dart'; 9 | 10 | import 'package:flutter_vmusic/conf/platform.dart'; 11 | import 'package:flutter_vmusic/conf/router.dart'; 12 | 13 | 14 | class HomePage extends StatefulWidget{ 15 | final Map params; 16 | HomePage({ 17 | Key key, 18 | this.params, 19 | }) : super(key: key); 20 | @override 21 | _HomePage createState() => _HomePage(); 22 | } 23 | 24 | class _HomePage extends State with SingleTickerProviderStateMixin{ 25 | 26 | //主页退出app确认 27 | Future _onWillPop()=>exitApp(context); 28 | //自定义打开Drawer 29 | final GlobalKey _scaffoldKey = GlobalKey(); 30 | //滑动切配置(自定义TabBar和TabBarView联动) 31 | TabController controller;//tab控制器 32 | int _currentIndex = 0; //选中下标 33 | 34 | 35 | 36 | List tabList = [{'title':'发现'},{'title':'视频'}];//tab集合 37 | 38 | @override 39 | void initState() { 40 | super.initState(); 41 | 42 | SYS.systemUI(Colors.transparent,Colors.black,Brightness.light); 43 | if(mounted) { 44 | //初始化controller并添加监听 45 | controller = TabController(initialIndex: 0, length: tabList.length, vsync: this); 46 | controller.addListener(() { 47 | if (controller.index.toDouble() == controller.animation.value) { 48 | //赋值 并更新数据 49 | this.setState(() { 50 | _currentIndex = controller.index; 51 | }); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | @override 58 | Widget build(BuildContext context) { 59 | //播放面板 60 | final mPlayPanel = PlayPanel(); 61 | 62 | //侧栏 63 | Widget mDrawer = new Drawer(//New added 64 | child: Center( 65 | child:Text('暂无历史播放内容'), 66 | ),//New added 67 | ); 68 | 69 | Widget getModule(int i) { 70 | Widget mainBlock; 71 | switch(i){ 72 | // case 0: 73 | // mainBlock = MyCenter(); 74 | // break; 75 | case 0: 76 | mainBlock = Find(); 77 | break; 78 | case 1: 79 | mainBlock = VideoList(); 80 | break; 81 | } 82 | 83 | return mainBlock; 84 | 85 | } 86 | 87 | //顶部导航 88 | Widget appNav = PreferredSize( 89 | preferredSize: Size.fromHeight(40.0), 90 | child:AppBar( 91 | backgroundColor:Colors.white, 92 | elevation: 0, 93 | brightness: Brightness.light, 94 | bottom:PreferredSize( 95 | child:Container( 96 | width: double.infinity, 97 | height:40.0, 98 | child:Material( 99 | color:Colors.white, 100 | child:Row( 101 | crossAxisAlignment: CrossAxisAlignment.start, 102 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 103 | children: [ 104 | Expanded( 105 | flex: 1, 106 | child:IconButton( 107 | onPressed: (){ 108 | _scaffoldKey.currentState.openDrawer(); 109 | }, 110 | icon:Icon(Icons.history,color: Colors.black,size:25.0), 111 | ), 112 | ), 113 | Expanded( 114 | flex: 5, 115 | child:Container( 116 | alignment:Alignment.center, 117 | child: new TabBar( 118 | controller: controller,//控制器 119 | labelColor: Colors.black, //选中的颜色 120 | labelStyle: TextStyle(fontSize: 15.0,fontWeight:FontWeight.bold), //选中的样式 121 | unselectedLabelColor: Colors.black, //未选中的颜色 122 | unselectedLabelStyle: TextStyle(fontSize: 15), //未选中的样式 123 | indicatorColor: Colors.redAccent, //下划线颜色 124 | isScrollable: true, //是否可滑动 125 | //tab标签 126 | tabs: tabList.map((item) { 127 | return new Tab( 128 | text: item['title'], 129 | ); 130 | }).toList(), 131 | //点击事件 132 | onTap: (int i) { 133 | print(i); 134 | }, 135 | ), 136 | ), 137 | ), 138 | Expanded( 139 | flex: 1, 140 | child:IconButton( 141 | onPressed: (){ 142 | Router.fadeNavigator(context,"/searchpage",{'seachParam':{},'from':'/find'},(res){}); 143 | 144 | }, 145 | color:Colors.redAccent, 146 | icon:Icon(Icons.search,color: Colors.black,size:25.0), 147 | ) 148 | ) 149 | ], 150 | ), 151 | ), 152 | 153 | ), preferredSize: null , 154 | ) , 155 | ) 156 | ); 157 | 158 | //滑动容区 159 | Widget slideView = TabBarView( 160 | controller: controller, 161 | children: tabList.asMap().keys.map((index) { 162 | return getModule(index); 163 | }).toList(), 164 | ); 165 | 166 | //主内容区 167 | Widget homeWarp = new Stack( 168 | children: [ 169 | DefaultTabController( 170 | length:tabList.length, 171 | child:Scaffold( 172 | appBar: appNav, 173 | body:slideView 174 | ) 175 | ), 176 | Align( 177 | alignment: Alignment.bottomCenter, 178 | child:mPlayPanel 179 | ), 180 | ], 181 | ); 182 | 183 | return WillPopScope( 184 | onWillPop: _onWillPop, 185 | child:Scaffold( 186 | key: _scaffoldKey, 187 | drawer:mDrawer,//New added 188 | body:homeWarp 189 | ) 190 | ); 191 | 192 | } 193 | 194 | @override 195 | void dispose() { 196 | controller.dispose(); 197 | super.dispose(); 198 | } 199 | } 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /lib/pages/landing_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_vmusic/conf/router.dart'; 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | 5 | import 'package:flutter_vmusic/conf/platform.dart'; 6 | 7 | class LandingPage extends StatefulWidget { 8 | 9 | @override 10 | State createState() => new _LandingPage(); 11 | } 12 | class _LandingPage extends State with SingleTickerProviderStateMixin{ 13 | 14 | AnimationController controllerTest; 15 | CurvedAnimation curve; 16 | 17 | 18 | @override 19 | void initState() { 20 | //初始化,当当前widget被插入到树中时调用 21 | super.initState(); 22 | // 全屏 23 | SYS.hideBar(); 24 | controllerTest = new AnimationController( 25 | duration: const Duration(milliseconds: 2000), 26 | vsync: this); 27 | curve = new CurvedAnimation(parent: controllerTest, curve: Curves.easeIn); 28 | controllerTest.forward(); 29 | } 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Material( 34 | child:FadeTransition( 35 | opacity: new Tween(begin: 1.0, end: 1.0).animate(curve), 36 | child: Stack( 37 | alignment: FractionalOffset(0.5, 0.9), 38 | children: [ 39 | Positioned( 40 | child:Container( 41 | color:Colors.black, 42 | width: double.infinity, 43 | height: double.infinity, 44 | child:new CachedNetworkImage( 45 | imageUrl: 'https://source.nullno.com/images/sp3.jpg', 46 | fit: BoxFit.cover, 47 | ), 48 | ) 49 | ), 50 | Positioned( 51 | child:Container( 52 | color:Colors.black54, 53 | width: double.infinity, 54 | height: double.infinity, 55 | child: Column( 56 | mainAxisAlignment:MainAxisAlignment.center, 57 | children: [ 58 | SlideTransition( 59 | position: new Tween( 60 | begin: Offset(0.0, -0.5), 61 | end: Offset(0.0, 0.0), 62 | ).animate(curve), 63 | child:Text('DX MUSIC',style:TextStyle(color:Colors.white,fontSize:50.0,fontWeight:FontWeight.bold),), 64 | ), 65 | SizeTransition( 66 | axis: Axis.horizontal, //控制宽度或者高度缩放 67 | sizeFactor: new Tween(begin: 0.1, end: 1.0).animate(curve), 68 | child: Container( 69 | width:300, 70 | height:3, 71 | margin:EdgeInsets.all(10.0), 72 | decoration: new BoxDecoration( 73 | border: Border.all(color: Colors.white, width: 2), 74 | borderRadius: BorderRadius.circular(10.0) 75 | ), 76 | ), 77 | ), 78 | SlideTransition( 79 | position: new Tween( 80 | begin: Offset(0.0, 0.5), 81 | end: Offset(0.0, 0.0), 82 | ).animate(curve), 83 | child: Text('Thanks NeteaseCloudMusicApi',style:TextStyle(color:Colors.white,fontSize:20.0),) 84 | ) 85 | ], 86 | ) 87 | ) 88 | 89 | ), 90 | Positioned( 91 | child:RaisedButton( 92 | onPressed: () { 93 | // Navigator.pushNamedAndRemoveUntil( context,"/home", (router) => router == null); 94 | //Navigator.of(context).pushNamed("/home"); 95 | //Router.goHome(context,{'des':'我是首页进来的555','from':'/launch'},(res){}); 96 | Router.fadeNavigator(context,"/home",{'des':'我是首页进来的555','from':'/launch'},(res){}); 97 | 98 | }, 99 | color:Color(0xff00CD81), 100 | splashColor:Color(0xff221535), 101 | elevation: 0.0, 102 | highlightElevation: 0.0, 103 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(22.0))), 104 | padding:EdgeInsets.fromLTRB(50,10,50,10), 105 | child:Text('开始听歌',style:TextStyle(color:Colors.white, fontSize:16),textAlign: TextAlign.center) 106 | ), 107 | ), 108 | 109 | ], 110 | ) , 111 | ), 112 | ); 113 | } 114 | 115 | @override 116 | void dispose() { 117 | controllerTest.dispose(); 118 | super.dispose(); 119 | } 120 | } -------------------------------------------------------------------------------- /lib/pages/player_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | *搜索页 3 | */ 4 | 5 | 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_vmusic/utils/FixedSizeText.dart'; 8 | import 'dart:ui'; 9 | import 'package:flutter_vmusic/utils/tool.dart'; 10 | 11 | import 'package:flutter_vmusic/conf/api.dart'; 12 | 13 | import 'package:flutter_vmusic/conf/platform.dart'; 14 | import 'package:audioplayers/audioplayers.dart'; 15 | import 'package:flutter_vmusic/conf/appsate.dart'; 16 | 17 | class PlayerPage extends StatefulWidget{ 18 | final Map params; 19 | PlayerPage({ 20 | Key key, 21 | this.params, 22 | }) : super(key: key); 23 | @override 24 | _PlayerPage createState() => _PlayerPage(); 25 | } 26 | 27 | class _PlayerPage extends State with SingleTickerProviderStateMixin{ 28 | 29 | // //音乐控制 30 | AudioPlayer audioPlayer = AppState.player['audioPlayer'] ; 31 | // bool playStatus=false; 32 | // String playUrl =''; 33 | double playSliderVal=0.0; 34 | // Duration songDuration; 35 | // Duration songPosition; 36 | // bool loop = true; 37 | //旋转动画 38 | AnimationController _myController; 39 | Animation rotateDisc; 40 | //歌单详情 41 | Map songDetail = new Map(); 42 | 43 | //加载状态 44 | int loadState = 0; //0 加载中 1加载完成 2 失败 45 | 46 | 47 | @override 48 | void initState() { 49 | super.initState(); 50 | SYS.hideBar(); 51 | discAni(); 52 | getData(); 53 | 54 | 55 | 56 | audioPlayer.onDurationChanged.listen((Duration d) { 57 | 58 | // setState(() => songDuration = d); 59 | AppState.player['duration'] = d; 60 | }); 61 | 62 | audioPlayer.onAudioPositionChanged.listen((Duration d) { 63 | if(mounted) { 64 | setState(() { 65 | // songPosition = d; 66 | AppState.player['position'] = d; 67 | playSliderVal = 68 | AppState.player['position'].inSeconds.floor().toDouble(); 69 | }); 70 | } 71 | }); 72 | 73 | audioPlayer.onPlayerCompletion.listen((event) { 74 | if(mounted) { 75 | setState(() { 76 | if (AppState.player['loop']) { 77 | setState(() { 78 | playSliderVal = 0.0; 79 | }); 80 | playSong(AppState.player['url']); 81 | } else { 82 | _myController.stop(); 83 | } 84 | AppState.player['playStatus'] = !AppState.player['playStatus']; 85 | }); 86 | } 87 | }); 88 | 89 | 90 | 91 | } 92 | 93 | //歌曲数据 94 | void getData(){ 95 | //歌曲详情 96 | getSongDetail(widget.params['id'],(res){ 97 | setState(() { 98 | songDetail = res['songs'][0]; 99 | AppState.player['id']=widget.params['id']; 100 | AppState.player['face']=songDetail['al']['picUrl']; 101 | AppState.player['name']=songDetail['name']; 102 | AppState.player['singer']=songDetail['ar'][0]['name']; 103 | }); 104 | 105 | },(err){ 106 | loadState=2; 107 | }); 108 | //歌曲链接 109 | getSongUrl(widget.params['id'],(res){ 110 | setState(() { 111 | if(res['data'].length>0){ 112 | setState(() { 113 | AppState.player['url']=res['data'][0]['url']; 114 | playSong(AppState.player['url']); 115 | 116 | }); 117 | } 118 | }); 119 | },(err){ 120 | loadState=2; 121 | }); 122 | } 123 | 124 | //磁盘动画 125 | void discAni(){ 126 | //动画控制器 127 | _myController = new AnimationController(duration: const Duration(seconds: 10), vsync: this); 128 | rotateDisc = new Tween(begin: 0.0, end: 1.0).animate(_myController); 129 | rotateDisc.addStatusListener((status) { 130 | if (status == AnimationStatus.completed) { 131 | //动画从 controller.forward() 正向执行 结束时会回调此方法 132 | // print("status is completed"); 133 | //重置起点 134 | _myController.reset(); 135 | //开启 136 | _myController.forward(); 137 | } else if (status == AnimationStatus.dismissed) { 138 | //动画从 controller.reverse() 反向执行 结束时会回调此方法 139 | // print("status is dismissed"); 140 | } else if (status == AnimationStatus.forward) { 141 | // print("status is forward"); 142 | //执行 controller.forward() 会回调此状态 143 | } else if (status == AnimationStatus.reverse) { 144 | //执行 controller.reverse() 会回调此状态 145 | // print("status is reverse"); 146 | } 147 | }); 148 | } 149 | 150 | //播放歌曲 151 | void playSong(url) async{ 152 | print(url); 153 | int result = await audioPlayer.play(url.replaceAll('http:', 'https:')); 154 | if(result==1) { 155 | setState(() { 156 | AppState.player['playStatus']=true; 157 | _myController.forward(); 158 | }); 159 | } 160 | } 161 | //暂停播放 162 | void pauseSong() async{ 163 | int result = await audioPlayer.pause(); 164 | if(result==1) { 165 | setState(() { 166 | AppState.player['playStatus']=false; 167 | _myController.stop(); 168 | }); 169 | } 170 | } 171 | 172 | @override 173 | Widget build(BuildContext context) { 174 | //顶部导航 175 | Widget appNav = Offstage( 176 | child:SafeArea(child: Container( 177 | color: Colors.transparent, 178 | width: double.infinity, 179 | height: 50.0, 180 | child: Material( 181 | color: Colors.transparent, 182 | child: Row( 183 | crossAxisAlignment: CrossAxisAlignment.center, 184 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 185 | children: [ 186 | Expanded( 187 | flex: 1, 188 | child:Visibility( 189 | child:IconButton( 190 | padding:EdgeInsets.all(0.0), 191 | onPressed: () { 192 | Navigator.pop(context); 193 | }, 194 | icon: Icon(Icons.arrow_back, color: Colors.white70, 195 | size: 30.0), 196 | ), 197 | visible:true, 198 | ) 199 | ), 200 | Expanded( 201 | flex: 5, 202 | child:Column( 203 | mainAxisAlignment: MainAxisAlignment.center, 204 | children: [ 205 | FixedSizeText(songDetail.isNotEmpty?songDetail['name']:'loading...',maxLines:1,overflow:TextOverflow.ellipsis,textAlign:TextAlign.center, style:TextStyle(color:Colors.white),), 206 | FixedSizeText(songDetail.isNotEmpty?songDetail['ar'][0]['name']:'loading...',maxLines:1,overflow:TextOverflow.ellipsis,textAlign:TextAlign.center, style:TextStyle(fontSize:12.0, color:Colors.grey),) 207 | ], 208 | ) 209 | ), 210 | Expanded( 211 | flex: 1, 212 | child:Visibility( 213 | child:IconButton( 214 | onPressed: (){ 215 | 216 | }, 217 | 218 | color: Colors.redAccent, 219 | icon: Icon(Icons.more_vert, color: Colors.white70, size: 25.0), 220 | 221 | ), 222 | visible:true, 223 | ) 224 | ) 225 | ], 226 | ), 227 | ), 228 | 229 | )), 230 | offstage:false, 231 | ); 232 | //播放主界面 233 | Widget playView= Container( 234 | decoration:new BoxDecoration( 235 | image: new DecorationImage( 236 | image:NetworkImage(songDetail.isNotEmpty?songDetail['al']['picUrl']:'https://p4.music.126.net/PeIyrKa1NL2BUAzw8kcnpQ==/109951164144255354.jpg?param=200y200'), 237 | fit:BoxFit.fill, 238 | ) 239 | 240 | ), 241 | child: Stack( 242 | children: [ 243 | BackdropFilter( 244 | filter: ImageFilter.blur(sigmaX: 100.0, sigmaY: 100.0), 245 | child: new Container( 246 | color:Colors.black.withOpacity(0.5), 247 | width: 500, 248 | ), 249 | ), 250 | Container( 251 | decoration: BoxDecoration( 252 | gradient: LinearGradient( 253 | begin: Alignment.topCenter, 254 | end: Alignment.bottomCenter, 255 | colors: [ 256 | Colors.black45, 257 | Colors.transparent, 258 | Colors.black45 259 | ], 260 | ), 261 | ), 262 | child:Center( 263 | child: Container( 264 | height:380, 265 | child:Stack( 266 | alignment:Alignment.topCenter, 267 | children: [ 268 | RotationTransition( 269 | turns: rotateDisc, 270 | child: 271 | Center( 272 | child: Container( 273 | height:266,width:266.0, 274 | decoration:new BoxDecoration( 275 | image: new DecorationImage( 276 | image:AssetImage('assets/image/disc.png'), 277 | fit:BoxFit.fill, 278 | ) 279 | ), 280 | child:Padding( 281 | padding:EdgeInsets.all(50.0), 282 | child: ClipRRect( 283 | borderRadius: BorderRadius.circular(100.0), 284 | child:Image.network(songDetail.isNotEmpty?songDetail['al']['picUrl']:'') 285 | ) 286 | ), 287 | 288 | ), 289 | ), 290 | ), 291 | Transform.rotate( 292 | angle:AppState.player['playStatus']?0:-0.5, 293 | alignment:Alignment.topLeft, 294 | child:Container( 295 | height:100, 296 | child: Image.asset('assets/image/needle.png'), 297 | ) , 298 | ), 299 | ], 300 | ) , 301 | ), 302 | ), 303 | 304 | 305 | ), 306 | ]) 307 | ); 308 | //播放控制 309 | Widget playControl = Container( 310 | height:150.0, 311 | // color:Colors.black, 312 | padding:EdgeInsets.all(15.0), 313 | child: Column( 314 | mainAxisAlignment:MainAxisAlignment.end, 315 | children: [ 316 | Row(), 317 | Padding(padding: EdgeInsets.only( 318 | top:10,bottom:10,), 319 | child: Row( 320 | mainAxisAlignment:MainAxisAlignment.spaceBetween, 321 | children: [ 322 | Expanded( 323 | flex:1, 324 | child: FixedSizeText(tranDuration(AppState.player['position']),maxLines:1,textAlign:TextAlign.center, style:TextStyle(color:Colors.white54,fontSize:12.0),), 325 | ), 326 | Expanded( 327 | flex:5, 328 | child:SliderTheme( 329 | data: SliderTheme.of(context).copyWith( 330 | //已拖动的颜色 331 | activeTrackColor: Colors.red, 332 | //未拖动的颜色 333 | inactiveTrackColor: Colors.white24, 334 | thumbShape: RoundSliderThumbShape(//可继承SliderComponentShape自定义形状 335 | enabledThumbRadius: 5, //滑块大小 336 | ), 337 | //滑块中心的颜色 338 | thumbColor: Colors.red, 339 | //滑块边缘的颜色 340 | overlayColor: Colors.white30, 341 | ), 342 | child:new Slider( 343 | onChanged: (newValue) { 344 | 345 | 346 | if (AppState.player['duration'] != null) { 347 | setState(() { 348 | playSliderVal = newValue; 349 | }); 350 | int seconds = newValue.ceil(); 351 | audioPlayer.seek(new Duration(seconds: seconds)); 352 | } 353 | }, 354 | value:AppState.player['duration'] != null?(playSliderVal>AppState.player['duration'].inSeconds.ceil()?AppState.player['duration'].inSeconds.ceil():playSliderVal):0.0, 355 | min: 0.0, 356 | max: AppState.player['duration'] != null?AppState.player['duration'].inSeconds.ceil().toDouble():0.0, 357 | ), 358 | ), 359 | 360 | 361 | ), 362 | Expanded( 363 | flex:1, 364 | child: FixedSizeText( tranDuration(AppState.player['duration']),maxLines:1,textAlign:TextAlign.center, style:TextStyle(color:Colors.white54,fontSize:12.0),), 365 | ), 366 | ], 367 | ) 368 | ), 369 | Row( 370 | mainAxisAlignment:MainAxisAlignment.spaceBetween, 371 | children: [ 372 | IconButton( 373 | padding:EdgeInsets.all(0.0), 374 | onPressed: () { 375 | setState(() { 376 | AppState.player['loop']=!AppState.player['loop']; 377 | }); 378 | }, 379 | icon: Icon(AppState.player['loop']?Icons.repeat_one:Icons.repeat, color: Colors.white24, 380 | size: 30.0), 381 | ), 382 | IconButton( 383 | padding:EdgeInsets.all(0.0), 384 | onPressed: () { 385 | 386 | }, 387 | icon: Icon(Icons.skip_previous, color: Colors.white54, 388 | size: 30.0), 389 | ), 390 | IconButton( 391 | padding:EdgeInsets.all(0.0), 392 | onPressed: () { 393 | if(!AppState.player['playStatus']){ 394 | playSong(AppState.player['url']); 395 | }else{ 396 | pauseSong(); 397 | } 398 | }, 399 | icon: Icon(AppState.player['playStatus']?Icons.pause_circle_outline:Icons.play_circle_outline, color: Colors.white70, 400 | size: 50.0), 401 | ), 402 | IconButton( 403 | padding:EdgeInsets.all(0.0), 404 | onPressed: () { 405 | 406 | }, 407 | icon: Icon(Icons.skip_next, color: Colors.white54, 408 | size: 30.0), 409 | ), 410 | IconButton( 411 | padding:EdgeInsets.all(0.0), 412 | onPressed: () { 413 | 414 | }, 415 | 416 | icon: Icon(Icons.playlist_play, color: Colors.white24, 417 | size: 30.0), 418 | ), 419 | ], 420 | ), 421 | ], 422 | ), 423 | ); 424 | 425 | //主内容区 426 | Widget mainWarp= new Stack( 427 | alignment:Alignment.topCenter, 428 | children: [ 429 | playView, 430 | appNav, 431 | Align( 432 | alignment: Alignment.bottomCenter, 433 | child: playControl, 434 | ) 435 | ] 436 | 437 | ); 438 | 439 | //主内容区 440 | return Material(color:Colors.white, child:mainWarp); 441 | } 442 | @override 443 | void dispose() { 444 | _myController.dispose(); 445 | super.dispose(); 446 | 447 | } 448 | 449 | } 450 | -------------------------------------------------------------------------------- /lib/pages/song_menu_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_vmusic/conf/router.dart'; 3 | import 'package:flutter_vmusic/utils/tool.dart'; 4 | import 'package:flutter_vmusic/utils/FixedSizeText.dart'; 5 | import 'package:cached_network_image/cached_network_image.dart'; 6 | import 'dart:async'; 7 | import 'package:flutter_vmusic/conf/api.dart'; 8 | 9 | import 'package:loading/loading.dart'; 10 | import 'package:loading/indicator/line_scale_pulse_out_indicator.dart'; 11 | 12 | 13 | 14 | class SongMenu extends StatefulWidget{ 15 | final Map params; 16 | SongMenu({ 17 | Key key, 18 | this.params, 19 | }) : super(key: key); 20 | @override 21 | _SongMenu createState() => _SongMenu(); 22 | } 23 | 24 | class _SongMenu extends State with SingleTickerProviderStateMixin{ 25 | 26 | 27 | //滑动切配置(自定义TabBar和TabBarView联动) 28 | TabController controller;//tab控制器 29 | int _currentIndex = 0; //选中下标 30 | 31 | List tabList = [{'title':'全部'},{'title':'华语'},{'title':'古风'},{'title':'欧美'},{'title':'流行'}];//tab集合 32 | //歌单 33 | List songLists = []; 34 | //加载状态 35 | int loadState = 0; //0加载中 1加载成功 2加载失败 36 | //下拉刷新 37 | final GlobalKey _refreshIndicatorKeySong = new GlobalKey(); 38 | 39 | Map loadMore={ 40 | "Text":"正在加载中", 41 | "Page":0, 42 | "type":"全部", 43 | "hasMore":true, 44 | "isScrollBottom":true, 45 | }; 46 | 47 | 48 | 49 | //初始化滚动监听器,加载更多使用 50 | ScrollController _scrollController = new ScrollController(); 51 | @override 52 | void initState() { 53 | super.initState(); 54 | addloadMore(); 55 | _flashData(); 56 | //初始化controller并添加监听 57 | controller = TabController(initialIndex:0,length: tabList.length, vsync: this); 58 | controller.addListener((){ 59 | if (controller.index.toDouble() == controller.animation.value) { 60 | //赋值 并更新数据 61 | this.setState(() { 62 | _currentIndex = controller.index; 63 | }); 64 | } 65 | }); 66 | } 67 | 68 | // 全部mv监听加载更多 69 | addloadMore(){ 70 | _scrollController.addListener(() { 71 | var maxScroll = _scrollController.position.maxScrollExtent.toStringAsFixed(0); 72 | var pixel = _scrollController.position.pixels.toStringAsFixed(0); 73 | 74 | if (maxScroll == pixel && loadMore["hasMore"]&&!loadMore["isScrollBottom"]) { 75 | 76 | setState(() { 77 | loadMore["Text"] = "正在加载中..."; 78 | loadMore["isScrollBottom"]=true; 79 | getHighqualitySongList({"cat":loadMore['type'],"before":loadMore['Page']},(res){ 80 | loadMore['hasMore']=res['more']; 81 | loadMore['Page']=res['lasttime']; 82 | loadMore["isScrollBottom"]=false; 83 | res['playlists'].forEach((aitem)=>{ 84 | songLists.add(aitem) 85 | }); 86 | },(err){ 87 | print(err); 88 | }); 89 | }); 90 | 91 | } else if(!loadMore['hasMore']) { 92 | setState(() { 93 | loadMore["isScrollBottom"]=true; 94 | loadMore["Text"] = "~我也是有底线的呦~"; 95 | }); 96 | } 97 | }); 98 | } 99 | 100 | // 刷新获取数据 101 | Future _flashData(){ 102 | final Completer completer = new Completer(); 103 | 104 | getData((status){ 105 | setState(() { 106 | loadState=status; 107 | completer.complete(null); 108 | }); 109 | }); 110 | 111 | 112 | return completer.future; 113 | } 114 | 115 | //获取数据 116 | void getData(complete) async{ 117 | var status = 0; 118 | 119 | // 获取推荐歌单 120 | await getHighqualitySongList({"cat":loadMore['type'],"before":0},(res){ 121 | status = 1; 122 | loadMore['hasMore']=res['more']; 123 | loadMore['Page']=res['lasttime']; 124 | loadMore["isScrollBottom"]=false; 125 | songLists.clear(); 126 | songLists=res['playlists']; 127 | 128 | },(err){ 129 | status = 2; 130 | print(err); 131 | }); 132 | 133 | complete(status); 134 | } 135 | 136 | 137 | @override 138 | Widget build(BuildContext context) { 139 | 140 | Widget _loaderImg(BuildContext context, String url) { 141 | return new Center( 142 | widthFactor:12.0, 143 | child:Loading(indicator: LineScalePulseOutIndicator(), size: 50.0), 144 | ); 145 | } 146 | Widget _loaderImgBlank(BuildContext context, String url) { 147 | return new Center( 148 | child:Icon(Icons.image,size:158,color: Colors.grey,) 149 | ); 150 | } 151 | 152 | //主内容区 153 | return Material( 154 | color:Colors.white, 155 | child: RefreshIndicator( 156 | key: _refreshIndicatorKeySong, 157 | onRefresh: _flashData, 158 | child: CustomScrollView( 159 | controller: _scrollController, 160 | physics: const AlwaysScrollableScrollPhysics(), 161 | slivers: [ 162 | SliverAppBar( 163 | floating: true, pinned: false, snap: true, 164 | expandedHeight: 200.0, 165 | backgroundColor:Colors.black, 166 | brightness: Brightness.dark, 167 | title: FixedSizeText('歌单广场',style:TextStyle(fontSize:16.0,color:Colors.white)) , 168 | iconTheme: IconThemeData(color: Colors.white), 169 | flexibleSpace: FlexibleSpaceBar( 170 | centerTitle: true, 171 | background: new CachedNetworkImage( 172 | placeholder: _loaderImg, 173 | imageUrl:songLists.length>0?songLists[0]['coverImgUrl']:"",//item['picUrl'], 174 | fit: BoxFit.cover, 175 | ), 176 | ), 177 | bottom:PreferredSize( 178 | child: Container( 179 | decoration: BoxDecoration( 180 | gradient: LinearGradient( 181 | begin: Alignment.topCenter, 182 | end: Alignment.bottomCenter, 183 | colors: [ 184 | Colors.transparent, 185 | Colors.black, 186 | ], 187 | ), 188 | ), 189 | child: TabBar( 190 | controller: controller, 191 | labelColor: Colors.red, 192 | unselectedLabelColor: Colors.white, 193 | indicatorColor: Colors.redAccent, // 194 | tabs:tabList.map((item) { 195 | return new Tab(text: item['title']); 196 | }).toList(), 197 | //点击事件 198 | onTap: (int i) { 199 | setState(() { 200 | loadMore['type'] = tabList[i]['title']; 201 | _flashData(); 202 | }); 203 | }, 204 | ),), 205 | ), 206 | 207 | ), 208 | SliverPadding( 209 | padding: const EdgeInsets.fromLTRB(15.0,15.0,15.0,0.0), 210 | sliver:SliverGrid( 211 | gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount( 212 | crossAxisCount: 2, //Grid按两列显示 213 | mainAxisSpacing: 10.0, 214 | crossAxisSpacing: 10.0, 215 | childAspectRatio: 0.75, 216 | ), 217 | delegate: SliverChildListDelegate( 218 | songLists.map((item){ 219 | return Column( 220 | crossAxisAlignment: CrossAxisAlignment.start, 221 | children: [ 222 | InkWell( 223 | onTap: (){ 224 | Router.fadeNavigator(context,"/songmenulist",{'id':item['id'],'from':'/songmenu'},(res){}); 225 | }, 226 | child: ClipRRect( 227 | borderRadius: BorderRadius.circular(5), 228 | child: Stack( 229 | children: [ 230 | new CachedNetworkImage( 231 | placeholder: _loaderImgBlank, 232 | imageUrl:item['coverImgUrl'],//item['picUrl'], 233 | fit: BoxFit.cover, 234 | ), 235 | Positioned( 236 | top:3.0, 237 | right:3.0, 238 | child:Row( 239 | children: [ 240 | Icon(Icons.play_circle_outline,color:Colors.white,size:15.0,), 241 | FixedSizeText(tranNumber(item['playCount']),style:TextStyle(color:Colors.white,fontSize:14.0)) 242 | ], 243 | ), 244 | ) 245 | ], 246 | ) 247 | ), 248 | ), 249 | Container( 250 | padding:EdgeInsets.fromLTRB(0.0, 3.0, 0.0, 0.0), 251 | child:FixedSizeText(item['name'], 252 | maxLines:2, 253 | overflow: TextOverflow.ellipsis, 254 | style:TextStyle(fontSize:13.0,height:1.2)), 255 | ) 256 | ], 257 | ); 258 | }).toList(), 259 | ), 260 | ) 261 | ), 262 | SliverPadding( 263 | padding: const EdgeInsets.fromLTRB(0.0,10.0,0.0,50.0), 264 | sliver:SliverToBoxAdapter( 265 | child: Visibility(child:Row( 266 | mainAxisAlignment: MainAxisAlignment.center, 267 | children: [ 268 | loadMore["hasMore"]?Container( 269 | width:15.0, 270 | height:15.0, 271 | margin:EdgeInsets.all(5.0), 272 | child:Loading(indicator: LineScalePulseOutIndicator(), size: 100.0), 273 | ):Container(), 274 | FixedSizeText(loadMore["Text"],textAlign:TextAlign.center,style:TextStyle(height:1,fontSize:12.0)) 275 | ], 276 | ), 277 | visible:loadMore["isScrollBottom"], 278 | ) 279 | ), 280 | ), 281 | 282 | ] 283 | ), 284 | ), 285 | ); 286 | } 287 | @override 288 | void dispose() { 289 | 290 | _scrollController.dispose(); 291 | super.dispose(); 292 | } 293 | 294 | } 295 | 296 | 297 | 298 | 299 | 300 | /* 301 | Sliver头 302 | SliverPersistentHeader( 303 | pinned: true, 304 | delegate: _SliverAppBarDelegate( 305 | TabBar( 306 | controller: controller, 307 | labelColor: Colors.red, 308 | unselectedLabelColor: Colors.grey, 309 | indicatorColor: Colors.black, // 310 | tabs:tabList.map((item) { 311 | return new Tab(text: item['title']); 312 | }).toList(), 313 | //点击事件 314 | onTap: (int i) { 315 | setState(() { 316 | loadMore['type'] = tabList[i]['title']; 317 | _flashData(); 318 | }); 319 | print(i); 320 | }, 321 | ) 322 | ) 323 | ), 324 | 325 | class _SliverAppBarDelegate extends SliverPersistentHeaderDelegate { 326 | _SliverAppBarDelegate(this._tabBar); 327 | 328 | final TabBar _tabBar; 329 | 330 | @override 331 | double get minExtent => _tabBar.preferredSize.height; 332 | 333 | @override 334 | double get maxExtent => _tabBar.preferredSize.height; 335 | 336 | @override 337 | Widget build( 338 | BuildContext context, double shrinkOffset, bool overlapsContent) { 339 | return Container( 340 | child: _tabBar, 341 | color:Colors.white, 342 | padding:EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0), 343 | ); 344 | } 345 | 346 | @override 347 | bool shouldRebuild(_SliverAppBarDelegate oldDelegate) { 348 | return false; 349 | } 350 | } 351 | */ -------------------------------------------------------------------------------- /lib/pages/song_mlist_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | *歌单详情 3 | */ 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_vmusic/utils/tool.dart'; 7 | import 'package:flutter_vmusic/utils/FixedSizeText.dart'; 8 | import 'package:cached_network_image/cached_network_image.dart'; 9 | import 'dart:async'; 10 | import 'dart:ui'; 11 | 12 | import 'package:flutter_vmusic/conf/api.dart'; 13 | 14 | import 'package:loading/loading.dart'; 15 | import 'package:loading/indicator/line_scale_pulse_out_indicator.dart'; 16 | 17 | import 'package:flutter_vmusic/components/playPanel.dart'; 18 | 19 | import 'package:flutter_vmusic/conf/router.dart'; 20 | import 'package:flutter_vmusic/conf/platform.dart'; 21 | 22 | class SongMenuList extends StatefulWidget{ 23 | final Map params; 24 | SongMenuList({ 25 | Key key, 26 | this.params, 27 | }) : super(key: key); 28 | @override 29 | _SongMenuList createState() => _SongMenuList(); 30 | } 31 | 32 | class _SongMenuList extends State with SingleTickerProviderStateMixin{ 33 | 34 | //顶部标题 35 | int topTitleStatus = 0; 36 | //歌单详情 37 | Map songDetail = new Map(); 38 | List songLists = []; 39 | //加载状态 40 | int loadState = 0; //0加载中 1加载成功 2加载失败 41 | //下拉刷新 42 | final GlobalKey _refreshIndicatorKeySong = new GlobalKey(); 43 | //初始化滚动监听器,加载更多使用 44 | ScrollController _scrollController = new ScrollController(); 45 | 46 | 47 | 48 | @override 49 | void initState() { 50 | super.initState(); 51 | changTitle(); 52 | 53 | _flashData(); 54 | 55 | } 56 | // 滑动切换标题 57 | changTitle(){ 58 | _scrollController.addListener(() { 59 | var pixel = _scrollController.position.pixels; 60 | if (pixel >200) { 61 | if(mounted) { 62 | setState(() { 63 | topTitleStatus = 1; 64 | }); 65 | } 66 | } else if(topTitleStatus!=0){ 67 | setState(() { 68 | topTitleStatus=0; 69 | }); 70 | } 71 | }); 72 | } 73 | 74 | // 刷新获取数据 75 | Future _flashData(){ 76 | final Completer completer = new Completer(); 77 | 78 | getData((status){ 79 | if(mounted) { 80 | setState(() { 81 | loadState = status; 82 | completer.complete(null); 83 | }); 84 | } 85 | }); 86 | 87 | 88 | return completer.future; 89 | } 90 | 91 | //获取数据 92 | void getData(complete) async{ 93 | var status = 0; 94 | 95 | // 获取推荐歌单 96 | await getSongMenuDetail({"id":widget.params['id']},(res){ 97 | 98 | songDetail=res['playlist']; 99 | songLists=res['playlist']['tracks']; 100 | 101 | },(err){ 102 | status = 2; 103 | print(err); 104 | }); 105 | 106 | complete(status); 107 | } 108 | 109 | 110 | @override 111 | Widget build(BuildContext context) { 112 | 113 | Widget _loaderImg(BuildContext context, String url) { 114 | return new Center( 115 | widthFactor:12.0, 116 | child:Loading(indicator: LineScalePulseOutIndicator(), size: 50.0), 117 | ); 118 | } 119 | 120 | //播放面板 121 | final mPlayPanel = PlayPanel(); 122 | //顶部导航 123 | Widget appNav =PreferredSize( 124 | preferredSize: Size.fromHeight(40.0), 125 | child:AppBar( 126 | backgroundColor:topTitleStatus==1?Colors.white:Colors.black, 127 | elevation: 0, 128 | brightness:topTitleStatus==1?Brightness.light:Brightness.dark, 129 | bottom:PreferredSize( 130 | child:Container( 131 | color:topTitleStatus==1?Colors.white:Colors.black, 132 | width: double.infinity, 133 | height:40.0, 134 | child:Material( 135 | color:Colors.transparent, 136 | child:Row( 137 | crossAxisAlignment: CrossAxisAlignment.start, 138 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 139 | children: [ 140 | Expanded( 141 | flex: 1, 142 | child:IconButton( 143 | onPressed: (){ 144 | Navigator.pop(context); 145 | }, 146 | icon:Icon(Icons.arrow_back,color: topTitleStatus==0?Colors.white:Colors.black,size:25.0), 147 | ), 148 | ), 149 | Expanded( 150 | flex: 5, 151 | child:Container( 152 | alignment:Alignment.centerLeft, 153 | child:Column( 154 | crossAxisAlignment: CrossAxisAlignment.stretch, 155 | mainAxisAlignment: MainAxisAlignment.center, 156 | children: [ 157 | FixedSizeText(topTitleStatus==1?songDetail['name']:"歌单",maxLines:1,overflow:TextOverflow.ellipsis, style:TextStyle(color:topTitleStatus==0?Colors.white:Colors.black),), 158 | FixedSizeText( songDetail.isEmpty?"Loading...":songDetail['description'],maxLines:1,overflow:TextOverflow.ellipsis, style:TextStyle(color:Colors.grey,fontSize:10.0),), 159 | ], 160 | ), 161 | 162 | ), 163 | ), 164 | Expanded( 165 | flex: 1, 166 | child: Visibility( 167 | child:IconButton( 168 | onPressed: (){ 169 | setState(() { 170 | 171 | }); 172 | }, 173 | color:Colors.redAccent, 174 | icon:Icon(Icons.more_vert,color: topTitleStatus==0?Colors.white:Colors.black,size:25.0), 175 | 176 | ), 177 | visible: false, 178 | 179 | ), 180 | 181 | ) 182 | ], 183 | ), 184 | ), 185 | 186 | 187 | ) 188 | ), 189 | ) 190 | ); 191 | 192 | //歌单信息 193 | Widget headInfo= Container( 194 | height:250.0, 195 | color:Colors.black, 196 | child:Stack( 197 | children: [ 198 | new CachedNetworkImage( 199 | placeholder: _loaderImg, 200 | imageUrl: songDetail.isEmpty?"":songDetail['coverImgUrl'],//item['picUrl'], 201 | fit: BoxFit.cover, 202 | width:double.infinity, 203 | ), 204 | BackdropFilter( 205 | filter: ImageFilter.blur(sigmaX: 100.0, sigmaY: 100.0), 206 | child: new Container( 207 | color:Colors.white.withOpacity(0.0), 208 | width: 500, 209 | ), 210 | ), 211 | Container( 212 | decoration: BoxDecoration( 213 | gradient: LinearGradient( 214 | begin: Alignment.topCenter, 215 | end: Alignment.bottomCenter, 216 | colors: [ 217 | Colors.black, 218 | Colors.transparent, 219 | ], 220 | ), 221 | ), 222 | ), 223 | songDetail.isEmpty?new Center( 224 | widthFactor:12.0, 225 | child:Loading(indicator: LineScalePulseOutIndicator(), size: 50.0), 226 | ):Column( 227 | children: [ 228 | Container( 229 | padding:EdgeInsets.all(15.0), 230 | height:180.0, 231 | child: Row( 232 | crossAxisAlignment:CrossAxisAlignment.start, 233 | mainAxisAlignment:MainAxisAlignment.spaceBetween, 234 | children: [ 235 | Expanded( 236 | flex: 2, 237 | child: ClipRRect( 238 | borderRadius: BorderRadius.circular(5), 239 | child: 240 | Container( 241 | child:Stack( 242 | children: [ 243 | new CachedNetworkImage( 244 | imageUrl:songDetail['coverImgUrl'],//item['picUrl'], 245 | fit: BoxFit.cover, 246 | width:double.infinity, 247 | ), 248 | Positioned( 249 | top:3.0, 250 | right:3.0, 251 | child:Row( 252 | mainAxisAlignment: MainAxisAlignment.center, 253 | children: [ 254 | Icon(Icons.play_arrow,color:Colors.white,size:12.0,), 255 | FixedSizeText(tranNumber(songDetail['playCount']),style:TextStyle(color:Colors.white,fontSize:12.0,shadows: [ 256 | Shadow(color: Colors.black,blurRadius:1.0, offset: Offset(1, 1)) 257 | ],)) 258 | ], 259 | ), 260 | ) 261 | ], 262 | ), 263 | ), 264 | ), 265 | ), 266 | Expanded( 267 | flex: 3, 268 | child:Container( 269 | padding:EdgeInsets.fromLTRB(15.0, 0.0, 0.0, 0.0), 270 | 271 | constraints:BoxConstraints.expand( 272 | height:130.0 273 | ), 274 | child: Column( 275 | crossAxisAlignment: CrossAxisAlignment.start, 276 | mainAxisAlignment:MainAxisAlignment.spaceBetween, 277 | children: [ 278 | FixedSizeText(songDetail['name'],maxLines:2,overflow:TextOverflow.ellipsis, style:TextStyle(fontSize:20.0,color:Colors.white,shadows: [ 279 | Shadow(color: Colors.black,blurRadius:5.0, offset: Offset(2, 5)) 280 | ],)), 281 | FixedSizeText(songDetail['description'],maxLines:3,overflow:TextOverflow.ellipsis, style:TextStyle(fontSize:10.0,color:Colors.white60)), 282 | Container( 283 | margin:EdgeInsets.fromLTRB(0, 0.0,0.0, 0.0), 284 | child: Row( 285 | children: [ 286 | ClipRRect( 287 | borderRadius: BorderRadius.circular(30), 288 | child: Container( 289 | width:30.0, 290 | height:30.0, 291 | child: new CachedNetworkImage( 292 | imageUrl: songDetail.isEmpty?"":songDetail['creator']['avatarUrl'],//item['picUrl'], 293 | fit: BoxFit.cover, 294 | ), 295 | ), 296 | ), 297 | Container( 298 | margin:EdgeInsets.all(5), 299 | child:FixedSizeText(songDetail.isEmpty?"":songDetail['creator']['nickname'],maxLines:1,overflow:TextOverflow.ellipsis, style:TextStyle(fontSize:12.0,color:Colors.white)), 300 | ), 301 | 302 | ], 303 | ) 304 | ) , 305 | ], 306 | ) , 307 | ), 308 | ), 309 | 310 | ], 311 | 312 | ), 313 | ), 314 | Container( 315 | padding:EdgeInsets.fromLTRB(30.0,0.0,30.0,0.0), 316 | child: Row( 317 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 318 | children: [ 319 | InkWell( 320 | onTap: (){}, 321 | child:Column( 322 | children: [ 323 | Icon(Icons.insert_comment,color:Colors.white60,size:30.0), 324 | FixedSizeText(tranNumber(songDetail['commentCount']),style:TextStyle(fontSize:12.0,color:Colors.white60)) 325 | ], 326 | ), 327 | ), 328 | InkWell( 329 | onTap: (){}, 330 | child:Column( 331 | children: [ 332 | Icon(Icons.share,color:Colors.white60,size:30.0), 333 | FixedSizeText(tranNumber(songDetail['shareCount']),style:TextStyle(fontSize:12.0,color:Colors.white60)) 334 | ], 335 | ), 336 | ), 337 | InkWell( 338 | onTap: (){}, 339 | child:Column( 340 | children: [ 341 | Icon(Icons.cloud_download,color:Colors.white60,size:30.0), 342 | FixedSizeText("下载",style:TextStyle(fontSize:11.0, color:Colors.white60)) 343 | ], 344 | ), 345 | ), 346 | InkWell( 347 | onTap: (){}, 348 | child:Column( 349 | children: [ 350 | Icon(Icons.check_circle_outline,color:Colors.white60,size:30.0), 351 | FixedSizeText("多选",style:TextStyle(fontSize:11.0, color:Colors.white60)) 352 | ], 353 | ), 354 | ), 355 | 356 | ], 357 | ), 358 | ) 359 | ], 360 | ), 361 | 362 | ], 363 | ), 364 | 365 | 366 | ); 367 | //歌曲片段 368 | Widget songListView(List songdata){ 369 | 370 | return ListView.builder( 371 | itemCount: songdata.length, 372 | shrinkWrap: true, 373 | primary:false, 374 | padding:EdgeInsets.all(0.0), 375 | itemBuilder: (context, i) => Material( 376 | color:Colors.transparent, 377 | child: InkWell( 378 | onTap: (){ 379 | Router.fadeNavigator(context,"/playerpage",{'id':songdata[i]['id'],'from':'/songmenulist'},(res){ 380 | SYS.systemUI(Colors.transparent,Colors.black,Brightness.dark); 381 | }); 382 | }, 383 | 384 | child: Container( 385 | margin:EdgeInsets.fromLTRB(0.0,15.0,0.0,15.0), 386 | padding:EdgeInsets.fromLTRB(15.0,0.0,15.0,0.0) , 387 | child: Row( 388 | crossAxisAlignment:CrossAxisAlignment.start, 389 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 390 | children: [ 391 | Expanded( 392 | flex: 1, 393 | child: FixedSizeText((i+1).toString(),textAlign:TextAlign.left,style:TextStyle(color:Colors.grey,fontSize:20.0)), 394 | ), 395 | Expanded( 396 | flex: 6, 397 | child:Column( 398 | crossAxisAlignment:CrossAxisAlignment.start, 399 | children: [ 400 | FixedSizeText(songdata[i]['name'],maxLines:1,overflow:TextOverflow.ellipsis, style:TextStyle(color:Colors.black,fontSize:13.0)), 401 | FixedSizeText(songdata[i]['ar'][0]['name'],maxLines:1,overflow:TextOverflow.ellipsis,style:TextStyle(color:Colors.grey,fontSize:12.0)), 402 | ], 403 | ) , 404 | ), 405 | Expanded( 406 | flex: 1, 407 | child:Visibility( 408 | child: Align( 409 | alignment:Alignment.topRight, 410 | child: InkWell(onTap: (){ 411 | Router.fadeNavigator(context,"/videopage",{'vid':songdata[i]['mv'],'type':0, 'from':'/video'},(res){ 412 | SYS.systemUI(Colors.transparent,Colors.black,Brightness.dark); 413 | }); 414 | },child:Icon(Icons.music_video,color:Colors.redAccent,)), 415 | ), 416 | visible:songdata[i]['mv']>0, 417 | ) 418 | ), 419 | Expanded( 420 | flex: 1, 421 | child:Align( 422 | alignment:Alignment.topRight, 423 | child: InkWell(onTap: (){}, child:Icon(Icons.more_vert,color:Colors.grey,)), 424 | ) 425 | ), 426 | ], 427 | 428 | ), 429 | ), 430 | ), 431 | ) 432 | 433 | ); 434 | 435 | } 436 | //歌曲列表 437 | Widget songs=Container( 438 | color:Colors.transparent, 439 | child: ClipRRect( 440 | borderRadius: BorderRadius.circular(20), 441 | child: Container( 442 | padding: EdgeInsets.fromLTRB(0.0,15.0,0.0,50.0), 443 | constraints: BoxConstraints( 444 | minHeight: 450, 445 | ), 446 | color:Colors.white, 447 | child:songDetail.isEmpty?Row( 448 | mainAxisAlignment: MainAxisAlignment.center, 449 | children: [ 450 | Container( 451 | width:15.0, 452 | height:15.0, 453 | margin:EdgeInsets.all(5.0), 454 | child:Loading(indicator: LineScalePulseOutIndicator(), size: 100.0), 455 | ), 456 | FixedSizeText("读取歌单中...",textAlign:TextAlign.center,style:TextStyle(height:1,fontSize:12.0)) 457 | ], 458 | ): Column( 459 | crossAxisAlignment:CrossAxisAlignment.start, 460 | children: [ 461 | Center( 462 | child:SizedBox( 463 | height:35.0, 464 | child:FlatButton.icon(onPressed: (){}, shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(22.0))),highlightColor:Colors.transparent, splashColor:Colors.black12, icon: Icon(Icons.play_circle_outline,color:Colors.black,size:35.0,), label:FixedSizeText("播放全部(${songDetail['tracks'].length}首)", style:TextStyle(color:Colors.black,fontSize:14.0),)) , 465 | ) , 466 | ), 467 | Divider( 468 | height:28.0, 469 | ), 470 | songListView(songLists), 471 | Center( 472 | child:FixedSizeText('~没有了呦~',textAlign:TextAlign.center,style:TextStyle(color:Colors.grey,fontSize:12.0),) 473 | ) 474 | 475 | ], 476 | ) 477 | ), 478 | ), 479 | ); 480 | 481 | 482 | //主内容区 483 | Widget mainWarp=RefreshIndicator( 484 | key: _refreshIndicatorKeySong, 485 | onRefresh: _flashData, 486 | child: new Stack( 487 | children: [ 488 | ListView( 489 | controller: _scrollController, 490 | physics: const AlwaysScrollableScrollPhysics(), 491 | children: [ 492 | headInfo, 493 | songs 494 | ], 495 | ), 496 | Align( 497 | alignment: Alignment.bottomCenter, 498 | child:mPlayPanel 499 | ), 500 | ] 501 | 502 | ) 503 | ); 504 | 505 | 506 | //主内容区 507 | return Material( 508 | color:Colors.white, 509 | child: 510 | Scaffold( 511 | appBar:appNav , 512 | body:mainWarp 513 | ), 514 | 515 | 516 | ); 517 | } 518 | @override 519 | void dispose() { 520 | 521 | _scrollController.dispose(); 522 | super.dispose(); 523 | } 524 | 525 | } 526 | -------------------------------------------------------------------------------- /lib/pages/template_blank.dart: -------------------------------------------------------------------------------- 1 | /* 2 | *搜索页 3 | */ 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_vmusic/conf/router.dart'; 7 | import 'package:flutter_vmusic/utils/FixedSizeText.dart'; 8 | import 'package:cached_network_image/cached_network_image.dart'; 9 | import 'dart:async'; 10 | import 'dart:ui'; 11 | 12 | import 'package:flutter_vmusic/utils/tool.dart'; 13 | 14 | import 'package:flutter_vmusic/conf/api.dart'; 15 | 16 | import 'package:loading/loading.dart'; 17 | import 'package:loading/indicator/line_scale_pulse_out_indicator.dart'; 18 | import 'package:flutter_vmusic/conf/platform.dart'; 19 | 20 | 21 | class VideoPage extends StatefulWidget{ 22 | final Map params; 23 | VideoPage({ 24 | Key key, 25 | this.params, 26 | }) : super(key: key); 27 | @override 28 | _VideoPage createState() => _VideoPage(); 29 | } 30 | 31 | class _VideoPage extends State with SingleTickerProviderStateMixin{ 32 | 33 | 34 | //歌单详情 35 | Map videoDetail = new Map(); 36 | 37 | //加载状态 38 | int loadState = 0; //0加载中 1加载成功 2加载失败 39 | 40 | 41 | 42 | //初始化滚动监听器,加载更多使用 43 | ScrollController _scrollController = new ScrollController(); 44 | 45 | @override 46 | void initState() { 47 | super.initState(); 48 | 49 | _scrollController.addListener(() { 50 | 51 | }); 52 | 53 | 54 | 55 | 56 | } 57 | 58 | 59 | 60 | //获取数据 61 | void getData(complete) async{ 62 | var status = 0; 63 | //热搜榜详细列表 64 | // await searchHot((res){ 65 | // hotRankLists=res['data']; 66 | // },(err){ 67 | // status = 2; 68 | // print(err); 69 | // }); 70 | complete(status); 71 | } 72 | 73 | 74 | 75 | 76 | @override 77 | Widget build(BuildContext context) { 78 | Widget _loaderImg(BuildContext context, String url) { 79 | return new Center( 80 | widthFactor: 12.0, 81 | child: Loading(indicator: LineScalePulseOutIndicator(), size: 50.0), 82 | ); 83 | } 84 | 85 | //顶部导航 86 | Widget appNav = PreferredSize( 87 | preferredSize: Size.fromHeight(40.0), 88 | child: AppBar( 89 | backgroundColor: Colors.white, 90 | elevation: 0, 91 | brightness: Brightness.light, 92 | bottom: PreferredSize( 93 | child: Container( 94 | color: Colors.white, 95 | width: double.infinity, 96 | height: 40.0, 97 | child: Material( 98 | color: Colors.white, 99 | child: Row( 100 | crossAxisAlignment: CrossAxisAlignment.center, 101 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 102 | children: [ 103 | Expanded( 104 | flex: 1, 105 | child: IconButton( 106 | onPressed: () { 107 | Navigator.pop(context); 108 | }, 109 | icon: Icon(Icons.arrow_back, color: Colors.black, 110 | size: 25.0), 111 | ), 112 | ), 113 | Expanded( 114 | flex: 5, 115 | child: 116 | Text('视频详情') 117 | 118 | ), 119 | Expanded( 120 | flex: 1, 121 | child:Visibility( 122 | child:IconButton( 123 | onPressed: () => { 124 | 125 | }, 126 | color: Colors.redAccent, 127 | icon: Icon(Icons.more_vert, color: Colors.black, size: 25.0), 128 | 129 | ), 130 | visible:false, 131 | ) 132 | ) 133 | ], 134 | ), 135 | ), 136 | 137 | ) 138 | ), 139 | ) 140 | ); 141 | 142 | 143 | //主内容区 144 | Widget mainWarp= new Stack( 145 | alignment:Alignment.bottomCenter, 146 | children: [ 147 | 148 | ] 149 | 150 | ); 151 | 152 | 153 | //主内容区 154 | return Material( 155 | color:Colors.white, 156 | child:SafeArea( 157 | child:Scaffold( 158 | backgroundColor:Colors.white, 159 | appBar:appNav , 160 | body:Container(child:mainWarp) 161 | ), 162 | ) 163 | ); 164 | } 165 | @override 166 | void dispose() { 167 | super.dispose(); 168 | } 169 | 170 | } 171 | -------------------------------------------------------------------------------- /lib/pages/video_detail_page.dart: -------------------------------------------------------------------------------- 1 | /* 2 | *搜索页 3 | */ 4 | 5 | import 'package:flutter/material.dart'; 6 | import 'package:flutter_vmusic/utils/FixedSizeText.dart'; 7 | import 'package:cached_network_image/cached_network_image.dart'; 8 | 9 | import 'package:flutter_vmusic/utils/tool.dart'; 10 | 11 | import 'package:flutter_vmusic/conf/api.dart'; 12 | 13 | import 'package:flutter_vmusic/utils/video_player.dart'; 14 | 15 | import 'package:loading/loading.dart'; 16 | import 'package:loading/indicator/line_scale_pulse_out_indicator.dart'; 17 | import 'package:flutter_vmusic/conf/platform.dart'; 18 | 19 | 20 | 21 | class VideoPage extends StatefulWidget{ 22 | final Map params; 23 | VideoPage({ 24 | Key key, 25 | this.params, 26 | }) : super(key: key); 27 | @override 28 | _VideoPage createState() => _VideoPage(); 29 | } 30 | 31 | class _VideoPage extends State { 32 | 33 | 34 | //视频详情 35 | Map videoDetail = new Map(); 36 | 37 | //加载状态 38 | int loadState = 0; //0加载中 1加载成功 2加载失败 39 | 40 | bool barshow=false; //导航显示状态 41 | 42 | int titleLine= 1; 43 | 44 | // 精彩评论 45 | List HotCommentList = []; 46 | //全部评论 47 | List AllCommentList = []; 48 | int commentloadStatus = 1; //0资源不存在 1加载中 2加载完成 评论 49 | 50 | //加载更多 51 | Map loadMore={ 52 | "Text":"----", 53 | "Page":0, 54 | "before":0, 55 | "hasMore":true, 56 | "isScrollBottom":false, 57 | }; 58 | 59 | //初始化滚动监听器,加载更多使用 60 | ScrollController _scrollController = new ScrollController(); 61 | 62 | @override 63 | void initState() { 64 | super.initState(); 65 | SYS.hideTopBar(); 66 | addloadMore(); 67 | 68 | if(widget.params['type']==0){ 69 | getMVDetail(widget.params['vid'],(res){ 70 | setState(() { 71 | res['data']['vurl'] =videoUrl(res['data']['brs']); 72 | videoDetail=res['data']; 73 | }); 74 | },(err){ 75 | print('资源不存在'); 76 | setState(() { 77 | loadState=2; 78 | barshow=true; 79 | SYS.systemUI(Colors.transparent,Colors.black,Brightness.light); 80 | }); 81 | }); 82 | } 83 | if(widget.params['type']==1){ 84 | getVideoDetail(widget.params['vid'],(res){ 85 | setState(() { 86 | videoDetail=res['data']; 87 | }); 88 | getVideoUrl(widget.params['vid'],(resv){ 89 | setState(() { 90 | if(resv['urls'].length>0){ 91 | videoDetail['vurl'] = resv['urls'][0]['url'].replaceAll('http:', 'https:'); 92 | } 93 | }); 94 | }); 95 | },(err){ 96 | setState(() { 97 | loadState=2; 98 | barshow=true; 99 | SYS.systemUI(Colors.transparent,Colors.black,Brightness.light); 100 | }); 101 | 102 | }); 103 | } 104 | 105 | getComment(); 106 | } 107 | 108 | // 全部mv监听加载更多 109 | addloadMore(){ 110 | _scrollController.addListener(() { 111 | var maxScroll = _scrollController.position.maxScrollExtent.toStringAsFixed(0); 112 | var pixel = _scrollController.position.pixels.toStringAsFixed(0); 113 | 114 | if (maxScroll == pixel && loadMore["hasMore"]&&!loadMore["isScrollBottom"]) { 115 | 116 | getComment(scroll:true); 117 | 118 | } else if(!loadMore['hasMore']) { 119 | setState(() { 120 | loadMore["isScrollBottom"]=true; 121 | loadMore["Text"] = "已加载全部"; 122 | }); 123 | } 124 | }); 125 | } 126 | 127 | //获取评论 128 | getComment({bool scroll:false}){ 129 | if(scroll){ 130 | setState(() { 131 | loadMore["Text"] = "正在加载中..."; 132 | loadMore["isScrollBottom"]=true; 133 | }); 134 | 135 | } 136 | getVideoComment({'type':widget.params['type'],'vid':widget.params['vid'],'offset':loadMore['Page']*20,'before':loadMore['before']},(res){ 137 | 138 | setState(() { 139 | loadMore['hasMore']=res['more']; 140 | loadMore['Page']=loadMore['Page']+1; 141 | loadMore["isScrollBottom"]=false; 142 | if(!scroll){ 143 | HotCommentList = res['hotComments']; 144 | commentloadStatus=2; 145 | } 146 | res['comments'].forEach((aitem)=>{ 147 | AllCommentList.add(aitem) 148 | }); 149 | 150 | }); 151 | },(err){ 152 | print('资源不存在'); 153 | commentloadStatus=0; 154 | }); 155 | 156 | } 157 | 158 | 159 | @override 160 | Widget build(BuildContext context) { 161 | Widget _loaderImg(BuildContext context, String url) { 162 | return new Center( 163 | widthFactor: 12.0, 164 | child: Loading(indicator: LineScalePulseOutIndicator(), size: 50.0), 165 | ); 166 | } 167 | 168 | //顶部导航 169 | Widget appNav = Offstage( 170 | child:SafeArea(child: Container( 171 | color: Colors.transparent, 172 | width: double.infinity, 173 | height: 30.0, 174 | child: Material( 175 | color: Colors.transparent, 176 | child: Row( 177 | crossAxisAlignment: CrossAxisAlignment.center, 178 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 179 | children: [ 180 | Expanded( 181 | flex: 1, 182 | child: IconButton( 183 | padding:EdgeInsets.all(0.0), 184 | onPressed: () { 185 | Navigator.pop(context); 186 | }, 187 | icon: Icon(Icons.arrow_back, color: Colors.white, 188 | size: 25.0), 189 | ) 190 | ), 191 | Expanded( 192 | flex: 5, 193 | child: FixedSizeText(videoDetail.isNotEmpty?widget.params['type']==1?videoDetail['title']:videoDetail['name']:'loading...',maxLines:1,overflow:TextOverflow.ellipsis, style:TextStyle(color:Colors.white),) 194 | ), 195 | Expanded( 196 | flex: 1, 197 | child:Visibility( 198 | child:IconButton( 199 | onPressed: () => { 200 | 201 | }, 202 | color: Colors.redAccent, 203 | icon: Icon(Icons.more_vert, color: Colors.white, size: 25.0), 204 | 205 | ), 206 | visible:false, 207 | ) 208 | ) 209 | ], 210 | ), 211 | ), 212 | 213 | )), 214 | offstage:!barshow, 215 | ); 216 | 217 | //视频 218 | Widget videoPanel = Material( 219 | child: Container( 220 | width:Adapt.screenW(), 221 | color:Colors.black, 222 | height:200, 223 | child:videoDetail.isNotEmpty && videoDetail.containsKey("vurl") ?SimpleViewPlayer(videoDetail['vurl'], isFullScreen: false, 224 | callback:(){ 225 | setState(() { 226 | barshow=!barshow; 227 | !barshow? SYS.hideTopBar():SYS.systemUI(Colors.transparent,Colors.black,Brightness.light); 228 | }); 229 | }): 230 | Center( 231 | child:videoDetail.containsKey("coverUrl") || videoDetail.containsKey("cover")?Container( 232 | width:double.infinity, 233 | decoration:new BoxDecoration( 234 | image: new DecorationImage( 235 | image:NetworkImage(widget.params['type']==1?videoDetail['coverUrl']:videoDetail['cover']) 236 | ) 237 | 238 | ), 239 | 240 | ): Center( 241 | child:loadState==2?Text('资源不存在',style:TextStyle(color:Colors.white),):SizedBox( 242 | width:40.0, 243 | height:40.0, 244 | child: CircularProgressIndicator(backgroundColor:Colors.redAccent)), 245 | ) 246 | ) 247 | )); 248 | 249 | //视频信息 250 | Widget vInfo = videoDetail.isNotEmpty?Container( 251 | color:Colors.white, 252 | padding:EdgeInsets.fromLTRB(15.0,10.0,15.0,5.0), 253 | margin:EdgeInsets.only(bottom:10.0), 254 | child:Column( 255 | mainAxisAlignment:MainAxisAlignment.start, 256 | crossAxisAlignment:CrossAxisAlignment.start, 257 | children: [ 258 | Row( 259 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 260 | crossAxisAlignment:CrossAxisAlignment.center, 261 | children: [ 262 | Expanded( 263 | flex:12, 264 | child:Container( 265 | 266 | child:FixedSizeText(widget.params['type']==1?videoDetail['title']:videoDetail['name'] +' --'+videoDetail['artistName'],maxLines:3,overflow:TextOverflow.ellipsis, style:TextStyle(color:Colors.black,fontSize:15,fontWeight:FontWeight.bold)), 267 | ) 268 | 269 | ), 270 | Expanded( 271 | flex:1, 272 | child:SizedBox( 273 | height:30.0, 274 | child:IconButton(padding:EdgeInsets.all(0), icon: Icon(titleLine==1?Icons.arrow_drop_down:Icons.arrow_drop_up,color:Colors.grey,size:30,), onPressed: (){ 275 | setState((){ 276 | titleLine=titleLine==1?3:1; 277 | }); 278 | }), 279 | ), 280 | ) 281 | ], 282 | ), 283 | Padding( 284 | padding:EdgeInsets.only(top:5.0), 285 | child: FixedSizeText(tranNumber(videoDetail[widget.params['type']==1?'playTime':'playCount'])+'次观看',style:TextStyle(color:Colors.grey,fontSize:13)), 286 | ), 287 | Visibility(child:Padding( 288 | padding:EdgeInsets.only(top:5.0), 289 | child: FixedSizeText((widget.params['type']==1?tranTimestr(videoDetail['publishTime']):videoDetail['publishTime'])+'发布',style:TextStyle(color:Colors.grey,fontSize:12)), 290 | ), 291 | visible:titleLine==3, 292 | ), 293 | Visibility(child:Padding( 294 | padding:EdgeInsets.only(top:5.0), 295 | child: FixedSizeText(videoDetail['description']!=null&&videoDetail['description']!=''?videoDetail['description']:'',style:TextStyle(color:Colors.grey,fontSize:12)), 296 | ), 297 | visible:widget.params['type']==1 && titleLine==3, 298 | ), 299 | 300 | Container( 301 | padding:EdgeInsets.fromLTRB(30.0,10.0,30.0,10.0), 302 | child: Row( 303 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 304 | children: [ 305 | InkWell( 306 | onTap: (){}, 307 | child:Column( 308 | children: [ 309 | Icon(Icons.star_border,color:Colors.black,size:30.0), 310 | FixedSizeText(tranNumber(videoDetail[widget.params['type']==1?'praisedCount':'likeCount']),style:TextStyle(fontSize:12.0,color:Colors.black)) 311 | ], 312 | ), 313 | ), 314 | // InkWell( 315 | // onTap: (){}, 316 | // child:Column( 317 | // children: [ 318 | // Icon(Icons.add_box,color:Colors.black,size:30.0), 319 | // FixedSizeText(tranNumber(videoDetail['shareCount']),style:TextStyle(fontSize:12.0,color:Colors.black)) 320 | // ], 321 | // ), 322 | // ), 323 | InkWell( 324 | onTap: (){}, 325 | child:Column( 326 | children: [ 327 | Icon(Icons.insert_comment,color:Colors.black,size:30.0), 328 | FixedSizeText(tranNumber(videoDetail['commentCount']),style:TextStyle(fontSize:12.0, color:Colors.black)) 329 | ], 330 | ), 331 | ), 332 | InkWell( 333 | onTap: (){}, 334 | child:Column( 335 | children: [ 336 | Icon(Icons.share,color:Colors.black,size:30.0), 337 | FixedSizeText(tranNumber(videoDetail['shareCount']),style:TextStyle(fontSize:12.0, color:Colors.black)) 338 | ], 339 | ), 340 | ), 341 | 342 | ], 343 | ), 344 | ), 345 | Divider(), 346 | widget.params['type']==1? 347 | Row( 348 | mainAxisAlignment: MainAxisAlignment.start, 349 | crossAxisAlignment:CrossAxisAlignment.center, 350 | children: [ 351 | ClipRRect( 352 | borderRadius: BorderRadius.circular(40), 353 | child:Container( 354 | width:40, 355 | height:40, 356 | child:new CachedNetworkImage( 357 | imageUrl:videoDetail['avatarUrl'],//item['picUrl'], 358 | fit: BoxFit.cover, 359 | ), 360 | ) 361 | ), 362 | Padding( 363 | padding:EdgeInsets.only(left:5), 364 | child: FixedSizeText(videoDetail['creator']['nickname'],style:TextStyle(color:Colors.black,fontSize:13)), 365 | ) 366 | 367 | ] 368 | ):Container() 369 | ] 370 | ) 371 | ):Container(); 372 | 373 | //评论列表 374 | Widget commentList(List listData){ 375 | return ListView.builder( 376 | itemCount: listData.length, 377 | shrinkWrap: true, 378 | primary:false, 379 | padding:EdgeInsets.all(0.0), 380 | itemBuilder: (context, i) =>Material( 381 | color:Colors.transparent, 382 | child: InkWell( 383 | onTap: (){}, 384 | child:Container( 385 | padding:EdgeInsets.only(top:10.0,bottom:10.0), 386 | child: Row( 387 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 388 | crossAxisAlignment: CrossAxisAlignment.start, 389 | children: [ 390 | Container( 391 | margin:EdgeInsets.fromLTRB(15.0, 0.0, 10, 0), 392 | child:ClipRRect( 393 | borderRadius: BorderRadius.circular(40), 394 | child:Container( 395 | width:30, 396 | height:30, 397 | child:new CachedNetworkImage( 398 | imageUrl:listData[i]['user']['avatarUrl'],//item['picUrl'], 399 | fit: BoxFit.cover, 400 | ), 401 | ) 402 | ) 403 | ) , 404 | Expanded( 405 | flex:7, 406 | child: Container( 407 | padding:EdgeInsets.only(right:15.0), 408 | child: Column( 409 | crossAxisAlignment:CrossAxisAlignment.start, 410 | children: [ 411 | Row( 412 | mainAxisAlignment:MainAxisAlignment.spaceBetween, 413 | children: [ 414 | Column( 415 | crossAxisAlignment:CrossAxisAlignment.start, 416 | children: [ 417 | FixedSizeText(listData[i]['user']['nickname'],maxLines:1, style:TextStyle(color:Colors.grey,fontSize:12)), 418 | FixedSizeText(tranTimestr(listData[i]['time']),maxLines:1, style:TextStyle(color:Colors.grey,fontSize:9)), 419 | ], 420 | ), 421 | Row( 422 | children: [ 423 | FixedSizeText(listData[i]['likedCount'].toString(),maxLines:1, style:TextStyle(color:Colors.grey,fontSize:12)), 424 | Icon(Icons.star_border,size:15,) 425 | ], 426 | ) 427 | ], 428 | ), 429 | Padding( 430 | padding:EdgeInsets.only(top:5,bottom:5), 431 | child:FixedSizeText(listData[i]['content'],maxLines:20, style:TextStyle(color:Colors.black,fontSize:13,height:1.2)), 432 | ), 433 | Divider() 434 | 435 | ], 436 | ) , 437 | ) 438 | ), 439 | ], 440 | ), 441 | ) 442 | ), 443 | 444 | ) 445 | ); 446 | } 447 | 448 | 449 | //评论 450 | Widget commentPanel = Container( 451 | color:Colors.white, 452 | padding:EdgeInsets.only(top:15.0,bottom: 15.0), 453 | child: commentloadStatus==2 && AllCommentList.length>0? Column( 454 | mainAxisAlignment:MainAxisAlignment.start, 455 | crossAxisAlignment:CrossAxisAlignment.start, 456 | children: [ 457 | HotCommentList.length>0?Padding( 458 | padding:EdgeInsets.only(left:15.0), 459 | child:FixedSizeText('精彩评论',style:TextStyle(fontSize:15.0,fontWeight:FontWeight.bold,color:Colors.red)), 460 | ):Container(), 461 | HotCommentList.length>0?Padding( 462 | child: Divider(), 463 | padding:EdgeInsets.fromLTRB(15.0,5.0,15.0,0.0), 464 | ):Container(), 465 | HotCommentList.length>0?commentList(HotCommentList):Container(), 466 | HotCommentList.length>0?Container( 467 | color: Color(0xFFF2F2F2), 468 | height:10.0, 469 | ):Container(), 470 | AllCommentList.length>0?Container( 471 | padding:EdgeInsets.only(left:15.0), 472 | margin:EdgeInsets.only(top:10.0,), 473 | child:FixedSizeText('全部评论(${tranNumber(videoDetail['commentCount'])})',style:TextStyle(fontSize:15.0,fontWeight:FontWeight.bold,color:Colors.black)), 474 | ):Container(), 475 | AllCommentList.length>0?Padding( 476 | child: Divider(), 477 | padding:EdgeInsets.fromLTRB(15.0,5.0,15.0,5.0), 478 | ):Container(), 479 | AllCommentList.length>0?commentList(AllCommentList):Container(), 480 | AllCommentList.length>0? Visibility( 481 | child: Row( 482 | mainAxisAlignment: MainAxisAlignment.center, 483 | children: [ 484 | loadMore["hasMore"]?Container( 485 | width:15.0, 486 | height:15.0, 487 | margin:EdgeInsets.all(5.0), 488 | child:Loading(indicator: LineScalePulseOutIndicator(), size: 100.0), 489 | ):Container(), 490 | FixedSizeText(loadMore["Text"],textAlign:TextAlign.center,style:TextStyle(height:1,fontSize:12.0)) 491 | ], 492 | ), 493 | visible: loadMore["isScrollBottom"], 494 | ):Container() 495 | ], 496 | ):Container(child: Row( 497 | mainAxisAlignment: MainAxisAlignment.center, 498 | children: [ 499 | Visibility( 500 | child: Container( 501 | width:15.0, 502 | height:15.0, 503 | margin:EdgeInsets.all(5.0), 504 | child:Loading(indicator: LineScalePulseOutIndicator(), size: 100.0), 505 | ), 506 | visible:commentloadStatus==1 , 507 | ), 508 | FixedSizeText(commentloadStatus==1?"努力加载中...":'暂无评论',textAlign:TextAlign.center,style:TextStyle(height:1,fontSize:12.0)) 509 | ], 510 | ),) 511 | 512 | ); 513 | 514 | 515 | //滑动面板 516 | Widget viewPanel = Container( 517 | width:Adapt.screenW(), 518 | height:Adapt.screenH()-200, 519 | color: Color(0xFFF2F2F2), 520 | child:ListView( 521 | controller: _scrollController, 522 | padding:EdgeInsets.all(0.0), 523 | children: [ 524 | vInfo, 525 | commentPanel 526 | ], 527 | ) 528 | ); 529 | 530 | //主内容区 531 | Widget mainWarp= new Stack( 532 | alignment:Alignment.topCenter, 533 | children: [ 534 | videoPanel, 535 | appNav, 536 | Positioned( 537 | bottom:0, 538 | child: loadState!=2?viewPanel:Container(), 539 | ), 540 | ] 541 | 542 | ); 543 | 544 | 545 | //主内容区s 546 | return Material(child: mainWarp) ; 547 | } 548 | @override 549 | void dispose() { 550 | _scrollController.dispose(); 551 | super.dispose(); 552 | } 553 | 554 | } 555 | -------------------------------------------------------------------------------- /lib/utils/FixedSizeText.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FixedSizeText extends Text { 4 | const FixedSizeText(String data, { 5 | Key key, 6 | TextStyle style, 7 | StrutStyle strutStyle, 8 | TextAlign textAlign, 9 | TextDirection textDirection, 10 | Locale locale, 11 | bool softWrap, 12 | TextOverflow overflow, 13 | double textScaleFactor = 0.9, 14 | int maxLines, 15 | String semanticsLabel, 16 | }) : super(data, 17 | key:key, 18 | style:style, 19 | strutStyle:strutStyle, 20 | textAlign:textAlign, 21 | textDirection:textDirection, 22 | locale:locale, 23 | softWrap:softWrap, 24 | overflow:overflow, 25 | textScaleFactor:textScaleFactor, 26 | maxLines:maxLines, 27 | semanticsLabel:semanticsLabel); 28 | } -------------------------------------------------------------------------------- /lib/utils/custom.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | //路由渐变 5 | class FadeRoute extends PageRouteBuilder { 6 | final Widget page; 7 | FadeRoute({this.page}) : super( 8 | pageBuilder: ( 9 | BuildContext context, 10 | Animation animation, 11 | Animation secondaryAnimation, 12 | ) => 13 | page, 14 | transitionsBuilder: ( 15 | BuildContext context, 16 | Animation animation, 17 | Animation secondaryAnimation, 18 | Widget child, 19 | ) => 20 | FadeTransition( 21 | opacity: animation, 22 | child: child, 23 | ), 24 | ); 25 | } 26 | 27 | //入出路由动画 28 | class EnterExitRoute extends PageRouteBuilder { 29 | final Widget enterPage; 30 | final Widget exitPage; 31 | EnterExitRoute({this.exitPage, this.enterPage}) 32 | : super( 33 | pageBuilder: ( 34 | BuildContext context, 35 | Animation animation, 36 | Animation secondaryAnimation, 37 | ) => 38 | enterPage, 39 | transitionsBuilder: ( 40 | BuildContext context, 41 | Animation animation, 42 | Animation secondaryAnimation, 43 | Widget child, 44 | ) => 45 | Stack( 46 | children: [ 47 | FadeTransition( 48 | opacity: new Tween( 49 | begin:1.0, 50 | end: 0.0, 51 | ).animate(animation), 52 | child: exitPage, 53 | ), 54 | SlideTransition( 55 | position: new Tween( 56 | begin: const Offset(1.0, 0.0), 57 | end: Offset.zero, 58 | ).animate(animation), 59 | child: enterPage, 60 | ) 61 | ], 62 | ), 63 | ); 64 | } 65 | 66 | //自定义路由动画 67 | class CustomRoute extends PageRouteBuilder { 68 | final Widget widget; 69 | CustomRoute(this.widget) : super( 70 | transitionDuration: const Duration(seconds: 1), 71 | pageBuilder: (BuildContext context, Animation animation1, 72 | Animation animation2) { 73 | return widget; 74 | }, 75 | transitionsBuilder: (BuildContext context, 76 | Animation animation1, 77 | Animation animation2, 78 | Widget child) { 79 | //缩放 80 | // return ScaleTransition( 81 | // scale:Tween(begin:0.0,end:1.0).animate(CurvedAnimation( 82 | // parent:animation1, 83 | // curve: Curves.fastOutSlowIn 84 | // )), 85 | // child:child 86 | // ); 87 | //旋转+缩放路由动画 88 | // return RotationTransition( 89 | // turns:Tween(begin:0.0,end:1.0) 90 | // .animate(CurvedAnimation( 91 | // parent: animation1, 92 | // curve: Curves.fastOutSlowIn 93 | // )), 94 | // child:ScaleTransition( 95 | // scale:Tween(begin: 0.0,end:1.0) 96 | // .animate(CurvedAnimation( 97 | // parent: animation1, 98 | // curve:Curves.fastOutSlowIn 99 | // )), 100 | // child: child, 101 | // ) 102 | // ); 103 | //左右滑动路由动画 104 | // return SlideTransition( 105 | // position: Tween( 106 | // begin: Offset(-1.0, 0.0), end: Offset(0.0, 0.0)) 107 | // .animate(CurvedAnimation( 108 | // parent: animation1, curve: Curves.fastOutSlowIn)), 109 | // child: child, 110 | // ); 111 | 112 | return FadeTransition( 113 | opacity: Tween(begin:0.0,end :1.0).animate(CurvedAnimation( 114 | parent:animation1, 115 | curve:Curves.fastOutSlowIn 116 | )), 117 | child: child, 118 | ); 119 | }); 120 | } 121 | 122 | -------------------------------------------------------------------------------- /lib/utils/store.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | 4 | List warr=[]; 5 | 6 | 7 | void updateState(d,Widget w){ 8 | 9 | 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /lib/utils/tool.dart: -------------------------------------------------------------------------------- 1 | //工具方法 2 | import 'package:flutter/material.dart'; 3 | import 'dart:ui'; 4 | 5 | /* 6 | 数字转换 7 | int num 8 | */ 9 | 10 | tranNumber(int num){ 11 | String result ="0"; 12 | if(num<10000){ 13 | result = num.toDouble().toStringAsFixed(0); 14 | } 15 | if( num>=10000){ 16 | result = (num/10000).toStringAsFixed(0) +'万'; 17 | } 18 | if( num>=100000000){ 19 | result = (num/100000000).toStringAsFixed(0) +'亿'; 20 | } 21 | return result; 22 | } 23 | 24 | /* 25 | 视频链接https 分辨率url 26 | */ 27 | videoUrl(dynamic url){ 28 | String newUrl="https://source.nullno.com/blog/220050677efa8a64c018ef48289ea236a0c11531882000114a12be221fc.mp4"; 29 | if(url.containsKey("1080")){ 30 | newUrl = url['1080']; 31 | }else if(url.containsKey("720")){ 32 | newUrl = url['720']; 33 | 34 | }else if(url.containsKey("480")){ 35 | newUrl = url['480']; 36 | 37 | }else if(url.containsKey("240")){ 38 | newUrl = url['240']; 39 | } 40 | return newUrl.replaceAll('http:', 'https:'); 41 | 42 | } 43 | 44 | /* 45 | 视频毫秒数 46 | * */ 47 | formatDuration(int t) { 48 | if (t < 60000) { 49 | return ((t % 60000) / 1000).toStringAsFixed(0) + "s"; 50 | } else if ((t >= 60000) && (t < 3600000)) { 51 | String s=((t % 3600000) / 60000)>=10?((t % 3600000) / 60000).toStringAsFixed(0):"0"+((t % 3600000) / 60000).toStringAsFixed(0); 52 | String m=((t % 60000) / 1000)>=10?((t % 60000) / 1000).toStringAsFixed(0):"0"+((t % 3600000) / 60000).toStringAsFixed(0); 53 | return s+":" +m; 54 | } else { 55 | //小时用不上 56 | return(t / 3600000).toStringAsFixed(0) + ":" + ((t % 3600000) / 60000).toStringAsFixed(0) + 57 | ":" + ((t % 60000) / 1000).toStringAsFixed(0); 58 | } 59 | } 60 | 61 | /* 62 | * 时间戳转字符串 63 | * */ 64 | 65 | tranTimestr(int timestamp){ 66 | 67 | var date = new DateTime.fromMillisecondsSinceEpoch(timestamp); //时间戳为10位需*1000,时间戳为13位的话不需乘1000 68 | var Y = date.year.toString() + '-'; 69 | var M = (date.month < 10 ? '0' + (date.month).toString() : (date.month).toString()) + '-'; 70 | var D = date.day.toString() + ' '; 71 | var h = date.hour.toString() + ':'; 72 | var m = date.minute < 10 ?'0' +date.minute.toString()+ '':date.minute.toString()+ ''; 73 | var s = date.second < 10 ? '0' + date.second.toString() : date.second.toString(); 74 | 75 | return Y + M + D; 76 | } 77 | /* 78 | 79 | *音乐播放时间进度处理 80 | return string 81 | */ 82 | 83 | tranDuration(Duration d){ 84 | 85 | if(d==null){ 86 | return "00:00"; 87 | } 88 | String t=''; 89 | if(d.inHours>0){ 90 | t = d.toString().split('.')[0]; 91 | }else{ 92 | t = d.toString().split('.')[0].substring(2,7); 93 | } 94 | return t; 95 | } 96 | 97 | /* 98 | *搜索文字高亮 99 | */ 100 | 101 | keywordsHighlight(String keywords,String str){ 102 | 103 | List fg = str.split('/
/'); 104 | 105 | List newArr=[]; 106 | int index=0; 107 | fg.forEach((itemA){ 108 | String nitem = itemA.replaceAll(keywords,''+keywords+''); 109 | List sword = nitem.split(''); 110 | index++; 111 | sword.forEach((itemB) { 112 | if(itemB!=''){ 113 | if(index==1){ 114 | if (itemB == keywords && fg[0].contains(keywords)) { 115 | newArr.add(TextSpan(text: keywords, style: TextStyle(color: Colors.blueAccent, fontSize: 13.0)),); 116 | } else if (fg[0].contains(itemB)) { 117 | newArr.add(TextSpan(text: itemB, style: TextStyle(color: Colors.black, fontSize: 13.0))); 118 | } 119 | } 120 | if(index==2) { 121 | if (itemB == keywords && fg[1].contains(keywords)) { 122 | newArr.add(TextSpan(text: keywords, 123 | style: TextStyle( 124 | color: Colors.blueAccent, fontSize: 10.0, height: 1.5))); 125 | } else if (fg[1].contains(itemB)) { 126 | newArr.add(TextSpan(text: itemB, 127 | style: TextStyle( 128 | color: Colors.grey, fontSize: 10.0, height: 1.5))); 129 | } 130 | } 131 | } 132 | }); 133 | }); 134 | 135 | 136 | return RichText( 137 | text: TextSpan(style: TextStyle(fontSize: 13.0), 138 | children:newArr, 139 | ) 140 | ); 141 | 142 | } 143 | 144 | /* 145 | 146 | 屏幕相关 147 | */ 148 | class Adapt { 149 | static MediaQueryData mediaQuery = MediaQueryData.fromWindow(window); 150 | static double _width = mediaQuery.size.width; 151 | static double _height = mediaQuery.size.height; 152 | static double _topbarH = mediaQuery.padding.top; 153 | static double _botbarH = mediaQuery.padding.bottom; 154 | static double _pixelRatio = mediaQuery.devicePixelRatio; 155 | static var _ratio; 156 | static init(int number){ 157 | int uiwidth = number is int ? number : 750; 158 | _ratio = _width / uiwidth; 159 | } 160 | static px(number){ 161 | if(!(_ratio is double || _ratio is int)){Adapt.init(750);} 162 | return number * _ratio; 163 | } 164 | static onepx(){ 165 | return 1/_pixelRatio; 166 | } 167 | static screenW(){ 168 | return _width; 169 | } 170 | static screenH(){ 171 | return _height; 172 | } 173 | static padTopH(){ 174 | return _topbarH; 175 | } 176 | static padBotH(){ 177 | return _botbarH; 178 | } 179 | } -------------------------------------------------------------------------------- /lib/utils/video_player.dart: -------------------------------------------------------------------------------- 1 | library flutter_page_video; 2 | import 'package:flutter/material.dart'; 3 | import 'package:video_player/video_player.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:screen/screen.dart'; 6 | 7 | class SimpleViewPlayer extends StatefulWidget { 8 | final String source; 9 | bool isFullScreen; 10 | final callback; 11 | 12 | SimpleViewPlayer(this.source, {this.isFullScreen: false,this.callback}); 13 | 14 | @override 15 | _SimpleViewPlayerState createState() => _SimpleViewPlayerState(); 16 | } 17 | 18 | class _SimpleViewPlayerState extends State { 19 | VideoPlayerController controller; 20 | VoidCallback listener; 21 | bool hideBottom = true; 22 | 23 | @override 24 | void initState() { 25 | super.initState(); 26 | listener = () { 27 | if (!mounted) { 28 | return; 29 | } 30 | setState(() {}); 31 | }; 32 | controller = VideoPlayerController.network(widget.source); 33 | controller.initialize(); 34 | controller.setLooping(true); 35 | controller.addListener(listener); 36 | 37 | controller.play(); 38 | Screen.keepOn(true); 39 | if (widget.isFullScreen) { 40 | SystemChrome.setEnabledSystemUIOverlays([]); 41 | SystemChrome.setPreferredOrientations([ 42 | DeviceOrientation.landscapeLeft, 43 | DeviceOrientation.landscapeRight, 44 | ]); 45 | } 46 | } 47 | 48 | @override 49 | void dispose() { 50 | controller.dispose(); 51 | Screen.keepOn(false); 52 | if (widget.isFullScreen) { 53 | SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); 54 | SystemChrome.setPreferredOrientations([ 55 | DeviceOrientation.portraitUp, 56 | DeviceOrientation.portraitDown, 57 | DeviceOrientation.landscapeLeft, 58 | DeviceOrientation.landscapeRight, 59 | ]); 60 | } 61 | super.dispose(); 62 | } 63 | 64 | @override 65 | Widget build(BuildContext context) { 66 | return Scaffold( 67 | body: PlayView( 68 | controller, 69 | allowFullScreen: !widget.isFullScreen, 70 | callback: widget.callback 71 | ), 72 | ); 73 | } 74 | } 75 | 76 | class PlayView extends StatefulWidget { 77 | VideoPlayerController controller; 78 | bool allowFullScreen; 79 | final callback; 80 | PlayView(this.controller, {this.allowFullScreen: true,this.callback}); 81 | 82 | @override 83 | _PlayViewState createState() => _PlayViewState(); 84 | } 85 | 86 | class _PlayViewState extends State { 87 | VideoPlayerController get controller => widget.controller; 88 | bool hideBottom = true; 89 | 90 | void onClickPlay() { 91 | if (!controller.value.initialized) { 92 | return; 93 | } 94 | setState(() { 95 | hideBottom = false; 96 | }); 97 | 98 | if(widget.callback!=null){ 99 | widget.callback(); 100 | } 101 | if (controller.value.isPlaying) { 102 | controller.pause(); 103 | } else { 104 | Future.delayed(const Duration(seconds: 0), () { 105 | if (!mounted) { 106 | return; 107 | } 108 | if (!controller.value.initialized) { 109 | return; 110 | } 111 | if (controller.value.isPlaying && !hideBottom) { 112 | setState(() { 113 | hideBottom = true; 114 | }); 115 | } 116 | }); 117 | controller.play(); 118 | } 119 | } 120 | 121 | void onClickFullScreen() { 122 | if (MediaQuery.of(context).orientation == Orientation.portrait) { 123 | // current portrait , enter fullscreen 124 | SystemChrome.setEnabledSystemUIOverlays([]); 125 | SystemChrome.setPreferredOrientations([ 126 | DeviceOrientation.landscapeLeft, 127 | DeviceOrientation.landscapeRight, 128 | ]); 129 | Navigator.of(context) 130 | .push(PageRouteBuilder( 131 | settings: RouteSettings(isInitialRoute: false), 132 | pageBuilder: ( 133 | BuildContext context, 134 | Animation animation, 135 | Animation secondaryAnimation, 136 | ) { 137 | return AnimatedBuilder( 138 | animation: animation, 139 | builder: (BuildContext context, Widget child) { 140 | return Scaffold( 141 | resizeToAvoidBottomPadding: false, 142 | body: PlayView(controller), 143 | ); 144 | }, 145 | ); 146 | }, 147 | )) 148 | .then((value) { 149 | // exit fullscreen 150 | SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values); 151 | SystemChrome.setPreferredOrientations([ 152 | DeviceOrientation.portraitUp, 153 | DeviceOrientation.portraitDown, 154 | DeviceOrientation.landscapeLeft, 155 | DeviceOrientation.landscapeRight, 156 | ]); 157 | }); 158 | } 159 | } 160 | 161 | void onClickExitFullScreen() { 162 | if (MediaQuery.of(context).orientation == Orientation.landscape) { 163 | // current landscape , exit fullscreen 164 | Navigator.of(context).pop(); 165 | } 166 | } 167 | 168 | @override 169 | Widget build(BuildContext context) { 170 | Color primaryColor = Theme.of(context).primaryColor; 171 | if (controller.value.initialized) { 172 | final Size size = controller.value.size; 173 | return GestureDetector( 174 | child: Container( 175 | color: Colors.black, 176 | child: Stack( 177 | children: [ 178 | 179 | Center( 180 | child: AspectRatio( 181 | aspectRatio: size.width / size.height, 182 | child: VideoPlayer(controller), 183 | )), 184 | Visibility( 185 | child: Container( 186 | decoration: BoxDecoration( 187 | gradient: LinearGradient( 188 | begin: Alignment.topCenter, 189 | end: Alignment.bottomCenter, 190 | colors: [ 191 | Colors.black87, 192 | Colors.transparent, 193 | Colors.black87, 194 | ], 195 | ), 196 | ), 197 | ), 198 | visible:!controller.value.isPlaying , 199 | ), 200 | Align( 201 | alignment: Alignment.bottomCenter, 202 | child: hideBottom 203 | ? Container() 204 | : Opacity( 205 | opacity: 1.0, 206 | child: Container( 207 | height: 25.0, 208 | margin:EdgeInsets.all(5.0), 209 | color: Colors.transparent, 210 | child: Row( 211 | mainAxisSize: MainAxisSize.max, 212 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 213 | children: [ 214 | // GestureDetector( 215 | // child: Container( 216 | // child: controller.value.isPlaying 217 | // ? Icon( 218 | // Icons.pause, 219 | // color: primaryColor, 220 | // ) 221 | // : Icon( 222 | // Icons.play_arrow, 223 | // color: primaryColor, 224 | // ), 225 | // ), 226 | // onTap: onClickPlay, 227 | // ), 228 | Container( 229 | padding: EdgeInsets.symmetric( 230 | horizontal: 5.0), 231 | child: Center( 232 | child: Text( 233 | "${controller.value.position.toString().split(".")[0]}/${controller.value.duration.toString().split(".")[0]}", 234 | style: 235 | TextStyle(color: Colors.white), 236 | ), 237 | )), 238 | // Expanded( 239 | // child:ClipRRect( 240 | // borderRadius: BorderRadius.circular(20), 241 | // child:VideoProgressIndicator( 242 | // controller, 243 | // allowScrubbing: true, 244 | // padding: EdgeInsets.symmetric( 245 | // horizontal: 0.1, vertical: 0.1), 246 | // colors: VideoProgressColors( 247 | // bufferedColor:Colors.black12, 248 | // playedColor: Colors.redAccent), 249 | // )) , 250 | // 251 | // 252 | // ), 253 | 254 | // Container( 255 | // padding: EdgeInsets.symmetric( 256 | // horizontal: 5.0), 257 | // child: Center( 258 | // child: Text( 259 | // "${controller.value.duration.toString().split(".")[0]}", 260 | // style: 261 | // TextStyle(color: Colors.white), 262 | // ), 263 | // )), 264 | Container( 265 | child: widget.allowFullScreen 266 | ? Container( 267 | child: MediaQuery.of(context) 268 | .orientation == 269 | Orientation.portrait 270 | ? GestureDetector( 271 | child: Icon( 272 | Icons.fullscreen, 273 | color: primaryColor, 274 | ), 275 | onTap: onClickFullScreen, 276 | ) 277 | : GestureDetector( 278 | child: Icon( 279 | Icons.fullscreen_exit, 280 | color: primaryColor, 281 | ), 282 | onTap: 283 | onClickExitFullScreen, 284 | ), 285 | ) 286 | : Container(), 287 | ) 288 | ], 289 | )), 290 | )), 291 | Align( 292 | alignment: Alignment.center, 293 | child: controller.value.isPlaying 294 | ? Container() 295 | : Icon( 296 | Icons.play_arrow, 297 | color: primaryColor, 298 | size: 48.0, 299 | ), 300 | ), 301 | Align( 302 | alignment: Alignment.bottomCenter, 303 | child:SizedBox( 304 | height:3.0, 305 | child:ClipRRect( 306 | borderRadius: BorderRadius.circular(0), 307 | child: VideoProgressIndicator( 308 | controller, 309 | allowScrubbing: true, 310 | padding: EdgeInsets.symmetric( 311 | horizontal: 0, vertical: 0), 312 | colors: VideoProgressColors( 313 | backgroundColor:Colors.transparent, 314 | bufferedColor:Colors.black12, 315 | playedColor: Colors.red), 316 | )) , 317 | ) 318 | 319 | ) 320 | ], 321 | )), 322 | onTap: onClickPlay, 323 | ); 324 | } else if (controller.value.hasError && !controller.value.isPlaying) { 325 | return Container( 326 | color: Colors.black, 327 | child: Center( 328 | child: RaisedButton( 329 | onPressed: () { 330 | controller.initialize(); 331 | 332 | controller.setLooping(true); 333 | controller.play(); 334 | }, 335 | shape: new RoundedRectangleBorder( 336 | borderRadius: new BorderRadius.circular(30.0)), 337 | child: Text("play error, try again!"), 338 | ), 339 | ), 340 | ); 341 | } else { 342 | return Container( 343 | color: Colors.black, 344 | child: Center( 345 | child: CircularProgressIndicator(), 346 | ), 347 | ); 348 | } 349 | } 350 | 351 | @override 352 | void dispose() { 353 | super.dispose(); 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.2.0" 11 | audioplayers: 12 | dependency: "direct main" 13 | description: 14 | name: audioplayers 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "0.13.2" 18 | boolean_selector: 19 | dependency: transitive 20 | description: 21 | name: boolean_selector 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.0.4" 25 | cached_network_image: 26 | dependency: "direct main" 27 | description: 28 | name: cached_network_image 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.1.2+1" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.2" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.11" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.1.1" 53 | cookie_jar: 54 | dependency: transitive 55 | description: 56 | name: cookie_jar 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "1.0.1" 60 | crypto: 61 | dependency: transitive 62 | description: 63 | name: crypto 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "2.1.3" 67 | cupertino_icons: 68 | dependency: "direct main" 69 | description: 70 | name: cupertino_icons 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "0.1.2" 74 | dio: 75 | dependency: "direct main" 76 | description: 77 | name: dio 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "2.2.2" 81 | flutter: 82 | dependency: "direct main" 83 | description: flutter 84 | source: sdk 85 | version: "0.0.0" 86 | flutter_cache_manager: 87 | dependency: transitive 88 | description: 89 | name: flutter_cache_manager 90 | url: "https://pub.dartlang.org" 91 | source: hosted 92 | version: "1.1.3" 93 | flutter_test: 94 | dependency: "direct dev" 95 | description: flutter 96 | source: sdk 97 | version: "0.0.0" 98 | http: 99 | dependency: "direct main" 100 | description: 101 | name: http 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "0.12.0+2" 105 | http_parser: 106 | dependency: transitive 107 | description: 108 | name: http_parser 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "3.1.3" 112 | loading: 113 | dependency: "direct main" 114 | description: 115 | name: loading 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "1.0.1" 119 | matcher: 120 | dependency: transitive 121 | description: 122 | name: matcher 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "0.12.5" 126 | meta: 127 | dependency: transitive 128 | description: 129 | name: meta 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "1.1.6" 133 | path: 134 | dependency: transitive 135 | description: 136 | name: path 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "1.6.2" 140 | path_provider: 141 | dependency: "direct main" 142 | description: 143 | name: path_provider 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "1.4.0" 147 | pedantic: 148 | dependency: transitive 149 | description: 150 | name: pedantic 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "1.7.0" 154 | platform: 155 | dependency: transitive 156 | description: 157 | name: platform 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "2.2.1" 161 | quiver: 162 | dependency: transitive 163 | description: 164 | name: quiver 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "2.0.3" 168 | screen: 169 | dependency: "direct main" 170 | description: 171 | name: screen 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "0.0.5" 175 | sky_engine: 176 | dependency: transitive 177 | description: flutter 178 | source: sdk 179 | version: "0.0.99" 180 | source_span: 181 | dependency: transitive 182 | description: 183 | name: source_span 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "1.5.5" 187 | sqflite: 188 | dependency: transitive 189 | description: 190 | name: sqflite 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "1.1.6+5" 194 | stack_trace: 195 | dependency: transitive 196 | description: 197 | name: stack_trace 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "1.9.3" 201 | stream_channel: 202 | dependency: transitive 203 | description: 204 | name: stream_channel 205 | url: "https://pub.dartlang.org" 206 | source: hosted 207 | version: "2.0.0" 208 | string_scanner: 209 | dependency: transitive 210 | description: 211 | name: string_scanner 212 | url: "https://pub.dartlang.org" 213 | source: hosted 214 | version: "1.0.4" 215 | synchronized: 216 | dependency: transitive 217 | description: 218 | name: synchronized 219 | url: "https://pub.dartlang.org" 220 | source: hosted 221 | version: "2.1.0+1" 222 | term_glyph: 223 | dependency: transitive 224 | description: 225 | name: term_glyph 226 | url: "https://pub.dartlang.org" 227 | source: hosted 228 | version: "1.1.0" 229 | test_api: 230 | dependency: transitive 231 | description: 232 | name: test_api 233 | url: "https://pub.dartlang.org" 234 | source: hosted 235 | version: "0.2.5" 236 | typed_data: 237 | dependency: transitive 238 | description: 239 | name: typed_data 240 | url: "https://pub.dartlang.org" 241 | source: hosted 242 | version: "1.1.6" 243 | uuid: 244 | dependency: transitive 245 | description: 246 | name: uuid 247 | url: "https://pub.dartlang.org" 248 | source: hosted 249 | version: "2.0.2" 250 | vector_math: 251 | dependency: transitive 252 | description: 253 | name: vector_math 254 | url: "https://pub.dartlang.org" 255 | source: hosted 256 | version: "2.0.8" 257 | video_player: 258 | dependency: "direct main" 259 | description: 260 | name: video_player 261 | url: "https://pub.dartlang.org" 262 | source: hosted 263 | version: "0.10.2+5" 264 | sdks: 265 | dart: ">=2.2.2 <3.0.0" 266 | flutter: ">=1.5.0 <2.0.0" 267 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_vmusic 2 | description: vip 音乐播放器 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # In Android, build-name is used as versionName while build-number used as versionCode. 10 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 11 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. 12 | # Read more about iOS versioning at 13 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 14 | version: 1.0.0+1 15 | 16 | environment: 17 | sdk: ">=2.2.2 <=3.0.0" 18 | 19 | dependencies: 20 | flutter: 21 | sdk: flutter 22 | 23 | # The following adds the Cupertino Icons font to your application. 24 | # Use with the CupertinoIcons class for iOS style icons. 25 | cupertino_icons: ^0.1.2 26 | audioplayers: ^0.13.1 27 | http: ^0.12.0 # 网络请求 这个毫无疑问 28 | dio: ^2.1.16 # latest version 29 | cached_network_image: ^1.1.1 #网络图片缓存 30 | path_provider: ^1.1.0 #这是获取 安卓 IOS 文件缓存路径 31 | video_player: ^0.10.2+3 #视频播放 32 | screen: ^0.0.4 #全屏 33 | loading: ^1.0.1 #加载动画 34 | 35 | 36 | 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | 41 | 42 | # For information on the generic Dart part of this file, see the 43 | # following page: https://dart.dev/tools/pub/pubspec 44 | 45 | # The following section is specific to Flutter. 46 | flutter: 47 | 48 | # The following line ensures that the Material Icons font is 49 | # included with your application, so that you can use the icons in 50 | # the material Icons class. 51 | uses-material-design: true 52 | 53 | # To add assets to your application, add an assets section, like this: 54 | assets: 55 | # - images/a_dot_burr.jpeg 56 | # - images/a_dot_ham.jpeg 57 | - assets/image/disc.png 58 | - assets/image/disc_light.png 59 | - assets/image/needle.png 60 | 61 | # An image asset can refer to one or more resolution-specific "variants", see 62 | # https://flutter.dev/assets-and-images/#resolution-aware. 63 | 64 | # For details regarding adding assets from package dependencies, see 65 | # https://flutter.dev/assets-and-images/#from-packages 66 | 67 | # To add custom fonts to your application, add a fonts section here, 68 | # in this "flutter" section. Each entry in this list should have a 69 | # "family" key with the font family name, and a "fonts" key with a 70 | # list giving the asset and other descriptors for the font. For 71 | # example: 72 | # fonts: 73 | # - family: Schyler 74 | # fonts: 75 | # - asset: fonts/Schyler-Regular.ttf 76 | # - asset: fonts/Schyler-Italic.ttf 77 | # style: italic 78 | # - family: Trajan Pro 79 | # fonts: 80 | # - asset: fonts/TrajanPro.ttf 81 | # - asset: fonts/TrajanPro_Bold.ttf 82 | # weight: 700 83 | # 84 | # For details regarding fonts from package dependencies, 85 | # see https://flutter.dev/custom-fonts/#from-packages 86 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_vmusic/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | --------------------------------------------------------------------------------