用Flutter寫一個畢設(shè)app的經(jīng)驗(flutter 設(shè)計稿)
Flutter我也不是很了解,但自從我接觸了這門跨端技術(shù)開發(fā)之后,我是深深地喜歡上了它的UI、它的交互和動畫。很多人會吐槽它的無限嵌套,這是它有所不足的地方。喜歡一項技術(shù),就像喜歡一個人一樣,看到Ta的優(yōu)點(diǎn)的同時也能包容它的缺點(diǎn)。
我認(rèn)識一位才女,她在今年的畢業(yè)設(shè)計中,設(shè)計了一款app的UI。我當(dāng)時有個想法,把她的畢設(shè)開發(fā)出一個app。后面聯(lián)系的機(jī)會也比較少,各自忙各自的去啦!直到2021.08.23號,我跟她拿了設(shè)計稿,并通過新技術(shù)開始了一個周的開發(fā)經(jīng)歷。
看到設(shè)計稿,我第一感覺是:在設(shè)計和配色方面讓人聯(lián)想到文化和古風(fēng)
應(yīng)用介紹:運(yùn)用扁平風(fēng)插畫的形式來表現(xiàn)苗族特有的民族特征,文化和樂器,以最新潮的方式推廣給每一位用戶,讓《苗韻》成為一款趣味性的科普APP。
應(yīng)用開發(fā)后的交互視頻
經(jīng)驗分享:
1.實現(xiàn)引導(dǎo)頁
引導(dǎo)頁面
兩種選擇,可以選擇pageview自行搭建自己的引導(dǎo)頁;
stack(children:[ Positioned.fill( // 根據(jù)需求構(gòu)建自己的應(yīng)到也 child: PageView.builder() ) ] // 指示器內(nèi)容 buildDot())
另外一種選擇是可以選擇使用插件:這個插件構(gòu)建出來的引導(dǎo)頁也不錯
intro_views_flutter
2.實現(xiàn)組件突出效果
像這種卡片上移的效果,對于flutter來說不太好實現(xiàn),而且實現(xiàn)之后還需要注意層級會遮擋。
兩種實現(xiàn)方式:
使用stack布局,有一個缺點(diǎn),如果內(nèi)容是長列表,那會導(dǎo)致里面的內(nèi)容無法正常的滑動,比較適合小卡片布局。比如上圖中向外突出的樂器。
stack(children:[ position( top:-30, //關(guān)鍵代碼在這里 ), ])
使用 配合transform: Matrix4.translationValues(0.0, -100.0, 0.0),配合ClipPath
ClipPath( //定義裁切路徑 clipper: BackgroundClipper(), child: Container( ), )
BackgroundClipper 用于裁剪上移后,容器下面部分的內(nèi)容。
4.實現(xiàn)全局加載動畫和單個加載動畫
插件推薦:
# loading動畫 flutter_spinkit: ^5.0.0
全局加載模態(tài)
import 'package:flutter/material.dart';import 'package:flutter_miaoyun/utills/color_utils.dart';import 'package:flutter_spinkit/flutter_spinkit.dart';class Loading extends StatelessWidget { static void show(BuildContext context) { showDialog( barrierDismissible: true, context: context, builder: (ctx) => Theme( data: Theme.of(ctx).copyWith(dialogBackgroundColor: Colors.transparent), child: Loading(), ), ); } static void dismiss(context) { Navigator.pop(context); } @override Widget build(BuildContext context) { return Container( color: Colors.transparent, child: Center( child: Container( decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(5), ), width: 60, height: 60, alignment: Alignment.center, child: Column( mainAxisSize: MainAxisSize.min, children: <Widget>[ SpinKitCubeGrid( color: gColor("#C8AD6B"), size: 46.0, ) ], ), ), ), ); }}// 調(diào)用 Loading.show() 隱藏 Loading.dismiss()
單個加載動畫,可參考插件的demo 自行選用
5.實現(xiàn)點(diǎn)擊菜單移動到對應(yīng)的內(nèi)容區(qū)域
插件:
# 跳轉(zhuǎn)到對應(yīng)的頁面 scroll_to_index: ^2.0.0
6.音樂播放的實現(xiàn)
在使用這個插件的時候,我在編譯的時候遇到了kotlin版本不匹配問題,我的解決方案是看插件的example使用的是哪個版本,然后再Android Stadio 中更新kotlin到對應(yīng)的版本。之后就完美解決。
# 音頻播放 audioplayers: ^0.19.1
音頻播放的邏輯。大概的分享一下。我是將audio的實例對象初始化后存到controller中,這樣就可以在多個頁面對音樂進(jìn)行控制。
7.制作閃屏頁
插件推薦
flutter_native_splash配置dev_dependencies: flutter_test: sdk: flutter flutter_native_splash: ^1.2.0flutter_native_splash: # color: "#42a5f5" #image: assets/splash/icon.png background_image: assets/guides/splash.png # color_dark: "#042a49" #image_dark: assets/splash/icon.png background_image_dark: assets/guides/splash.png android: true ios: true web: true fullscreen: true使用:三步走flutter clean flutter pub getflutter pub run flutter_native_splash:create
8.本地存儲
插件推薦
shared_preferences
封裝
import 'dart:convert';import 'package:shared_preferences/shared_preferences.dart';class SPUtils { // 靜態(tài)實例 static SharedPreferences _sharedPreferences; // 應(yīng)用啟動時需要調(diào)用 // 初始化 static Future<bool> init() async { _sharedPreferences = await SharedPreferences.getInstance(); return true; } // 清除數(shù)據(jù) static void remove(String key) async { if (_sharedPreferences.containsKey(key)) { _sharedPreferences.remove(key); } } // 異步保存數(shù)據(jù)類型 static Future save(String key, dynamic value) async { if (value is String) { _sharedPreferences.setString(key, value); } else if (value is bool) { _sharedPreferences.setBool(key, value); } else if (value is double) { _sharedPreferences.setDouble(key, value); } else if (value is int) { _sharedPreferences.setInt(key, value); } else if (value is List<String>) { _sharedPreferences.setStringList(key, value); } } // 異步讀取數(shù)據(jù) static Future<String> getString(String key) async { return _sharedPreferences.getString(key); } static Future<int> getInt(String key) async { return _sharedPreferences.getInt(key); } static Future<bool> getBool(String key) async { return _sharedPreferences.getBool(key); } static Future<double> getDouble(String key) async { return _sharedPreferences.getDouble(key); } // 保存自定義對象 static Future saveObject(String key, dynamic value) async { // 通過json 將Object對象編譯成String類型保存 _sharedPreferences.setString(key, json.encode(value)); } // 獲取自定義對象 static dynamic getObject(String key) { String _data = _sharedPreferences.getString(key); return (_data == null || _data.isEmpty) ? null : json.decode(_data); } // 保存類表數(shù)據(jù) static Future<bool> putObjectList(String key, List<Object> list) { List<String> _dataList = list?.map((value) { return json.encode(value); })?.toList(); return _sharedPreferences.setStringList(key, _dataList); } // 獲取對象集合數(shù)據(jù) // 返回的是List <Map<String ,dynamic>>類型 static List<Map> getObjectList(String key) { if (_sharedPreferences == null) return null; List<String> dataList = _sharedPreferences.getStringList(key); return dataList?.map((value) { Map _dataMap = json.decode(value); return _dataMap; })?.toList(); }}
以上是在開發(fā)過程中覺得不錯,可以分享給flutter愛好者的相關(guān)知識。也是我在開發(fā)過程中遇到的一些技術(shù)難點(diǎn)。淡然有很多。要開發(fā)一個app,遇到的問題絕不是上面的這一點(diǎn)點(diǎn)。大家有不懂得多多交流。